From b7cddda740378ed94ee9425f63c269ac0322a131 Mon Sep 17 00:00:00 2001 From: "Fazio.Bai" Date: Mon, 22 Aug 2016 21:04:36 +0800 Subject: [PATCH] First commit on github! --- .gitignore | 86 + 01-cgminer.rules | 52 + A1-board-selector-CCD.c | 117 + A1-board-selector-CCR.c | 186 + A1-board-selector.h | 50 + A1-common.h | 91 + A1-desk-board-selector.c | 158 + A1-trimpot-mcp4x.c | 116 + A1-trimpot-mcp4x.h | 19 + API-README | 1852 +++ API.class | Bin 0 -> 3772 bytes API.java | 166 + ASIC-README | 763 ++ AUTHORS | 7 + COPYING | 674 + ChangeLog | 6 + FPGA-README | 271 + LICENSE | 3 + MCast.class | Bin 0 -> 5678 bytes MCast.java | 333 + Makefile.am | 169 + NEWS | 7017 ++++++++++ README | 1058 ++ api-example.c | 334 + api-example.php | 116 + api-example.py | 52 + api-example.rb | 38 + api.c | 5256 +++++++ arg-nonnull.h | 26 + autogen.sh | 11 + bench_block.h | 170 + bitforce-firmware-flash.c | 108 + bitmain-readme.txt | 58 + bitstreams/COPYING_fpgaminer | 23 + bitstreams/README | 1 + bitstreams/fpgaminer_top_fixed7_197MHz.ncd | Bin 0 -> 3682603 bytes c++defs.h | 271 + ccan/Makefile.am | 4 + ccan/compiler/LICENSE | 165 + ccan/compiler/_info | 64 + ccan/compiler/compiler.h | 216 + ccan/compiler/test/compile_fail-printf.c | 22 + ccan/compiler/test/run-is_compile_constant.c | 15 + ccan/opt/LICENSE | 339 + ccan/opt/_info | 67 + ccan/opt/helpers.c | 193 + ccan/opt/opt.c | 255 + ccan/opt/opt.h | 365 + ccan/opt/parse.c | 132 + ccan/opt/private.h | 19 + ccan/opt/test/compile_ok-const-arg.c | 13 + ccan/opt/test/run-checkopt.c | 144 + ccan/opt/test/run-correct-reporting.c | 49 + ccan/opt/test/run-helpers.c | 440 + ccan/opt/test/run-iter.c | 88 + ccan/opt/test/run-no-options.c | 33 + ccan/opt/test/run-usage.c | 112 + ccan/opt/test/run.c | 297 + ccan/opt/test/utils.c | 110 + ccan/opt/test/utils.h | 19 + ccan/opt/usage.c | 111 + ccan/typesafe_cb/LICENSE | 510 + ccan/typesafe_cb/_info | 151 + .../compile_fail-cast_if_type-promotable.c | 23 + .../test/compile_fail-typesafe_cb-int.c | 27 + .../test/compile_fail-typesafe_cb.c | 34 + .../compile_fail-typesafe_cb_cast-multi.c | 43 + .../test/compile_fail-typesafe_cb_cast.c | 25 + .../test/compile_fail-typesafe_cb_postargs.c | 27 + .../test/compile_fail-typesafe_cb_preargs.c | 28 + .../test/compile_ok-typesafe_cb-NULL.c | 17 + .../test/compile_ok-typesafe_cb-undefined.c | 49 + .../test/compile_ok-typesafe_cb-vars.c | 52 + .../test/compile_ok-typesafe_cb_cast.c | 41 + ccan/typesafe_cb/test/run.c | 109 + ccan/typesafe_cb/typesafe_cb.h | 133 + cgminer.c | 11279 ++++++++++++++++ compat.h | 88 + compat/.gitignore | 3 + compat/Makefile.am | 8 + compat/jansson-2.6/CHANGES | 583 + compat/jansson-2.6/LICENSE | 19 + compat/jansson-2.6/Makefile.am | 17 + compat/jansson-2.6/README.rst | 63 + compat/jansson-2.6/configure.ac | 101 + compat/jansson-2.6/jansson.pc.in | 10 + compat/jansson-2.6/m4/.gitignore | 0 compat/jansson-2.6/src/Makefile.am | 24 + compat/jansson-2.6/src/dump.c | 456 + compat/jansson-2.6/src/error.c | 63 + compat/jansson-2.6/src/hashtable.c | 360 + compat/jansson-2.6/src/hashtable.h | 180 + compat/jansson-2.6/src/jansson.def | 63 + compat/jansson-2.6/src/jansson.h | 281 + compat/jansson-2.6/src/jansson_config.h.in | 39 + compat/jansson-2.6/src/jansson_private.h | 93 + compat/jansson-2.6/src/load.c | 1077 ++ compat/jansson-2.6/src/memory.c | 56 + compat/jansson-2.6/src/pack_unpack.c | 762 ++ compat/jansson-2.6/src/strbuffer.c | 116 + compat/jansson-2.6/src/strbuffer.h | 33 + compat/jansson-2.6/src/strconv.c | 134 + compat/jansson-2.6/src/utf.c | 190 + compat/jansson-2.6/src/utf.h | 39 + compat/jansson-2.6/src/value.c | 950 ++ compat/libusb-1.0/AUTHORS | 46 + compat/libusb-1.0/COPYING | 504 + compat/libusb-1.0/Makefile.am | 24 + compat/libusb-1.0/NEWS | 65 + compat/libusb-1.0/PORTING | 95 + compat/libusb-1.0/README | 22 + compat/libusb-1.0/THANKS | 8 + compat/libusb-1.0/TODO | 9 + compat/libusb-1.0/configure.ac | 229 + compat/libusb-1.0/libusb-1.0.pc.in | 12 + compat/libusb-1.0/libusb/Makefile.am | 55 + compat/libusb-1.0/libusb/core.c | 2049 +++ compat/libusb-1.0/libusb/descriptor.c | 872 ++ compat/libusb-1.0/libusb/hotplug.c | 298 + compat/libusb-1.0/libusb/hotplug.h | 77 + compat/libusb-1.0/libusb/io.c | 2503 ++++ compat/libusb-1.0/libusb/libusb-1.0.def | 120 + compat/libusb-1.0/libusb/libusb-1.0.rc | 56 + compat/libusb-1.0/libusb/libusb.h | 1779 +++ compat/libusb-1.0/libusb/libusbi.h | 974 ++ compat/libusb-1.0/libusb/os/darwin_usb.c | 1788 +++ compat/libusb-1.0/libusb/os/darwin_usb.h | 175 + compat/libusb-1.0/libusb/os/linux_netlink.c | 231 + compat/libusb-1.0/libusb/os/linux_udev.c | 242 + compat/libusb-1.0/libusb/os/linux_usbfs.c | 2613 ++++ compat/libusb-1.0/libusb/os/linux_usbfs.h | 168 + compat/libusb-1.0/libusb/os/openbsd_usb.c | 727 + compat/libusb-1.0/libusb/os/poll_posix.h | 10 + compat/libusb-1.0/libusb/os/poll_windows.c | 745 + compat/libusb-1.0/libusb/os/poll_windows.h | 117 + compat/libusb-1.0/libusb/os/threads_posix.c | 55 + compat/libusb-1.0/libusb/os/threads_posix.h | 48 + compat/libusb-1.0/libusb/os/threads_windows.c | 208 + compat/libusb-1.0/libusb/os/threads_windows.h | 88 + compat/libusb-1.0/libusb/os/windows_usb.c | 2998 ++++ compat/libusb-1.0/libusb/os/windows_usb.h | 608 + compat/libusb-1.0/libusb/sync.c | 322 + compat/libusb-1.0/libusb/version.h | 18 + compat/libusb-1.0/m4/.gitignore | 5 + configure.ac | 832 ++ crc.h | 23 + crc16.c | 45 + driver-SPI-bitmine-A1.c | 1123 ++ driver-avalon.c | 1716 +++ driver-avalon.h | 207 + driver-avalon2.c | 1097 ++ driver-avalon2.h | 163 + driver-avalon4.c | 1637 +++ driver-avalon4.h | 200 + driver-bab.c | 3062 +++++ driver-bflsc.c | 2370 ++++ driver-bflsc.h | 392 + driver-bitforce.c | 752 ++ driver-bitfury.c | 1660 +++ driver-bitfury.h | 116 + driver-bitmain.c | 2463 ++++ driver-bitmain.h | 314 + driver-blockerupter.c | 501 + driver-blockerupter.h | 130 + driver-bmsc.c | 2031 +++ driver-btm-c5.c | 4824 +++++++ driver-btm-c5.h | 560 + driver-cointerra.c | 1376 ++ driver-cointerra.h | 251 + driver-drillbit.c | 1092 ++ driver-drillbit.h | 56 + driver-hashfast.c | 2066 +++ driver-hashfast.h | 163 + driver-hashratio.c | 876 ++ driver-hashratio.h | 131 + driver-icarus.c | 2462 ++++ driver-klondike.c | 1555 +++ driver-knc.c | 865 ++ driver-minion.c | 5380 ++++++++ driver-modminer.c | 1141 ++ driver-spondoolies-sp10-p.c | 44 + driver-spondoolies-sp10-p.h | 92 + driver-spondoolies-sp10.c | 446 + driver-spondoolies-sp10.h | 84 + driver-spondoolies-sp30-p.c | 47 + driver-spondoolies-sp30-p.h | 83 + driver-spondoolies-sp30.c | 489 + driver-spondoolies-sp30.h | 85 + elist.h | 256 + example.conf | 25 + fpgautils.c | 610 + fpgautils.h | 84 + hexdump.c | 77 + hf_protocol.h | 407 + hf_protocol_be.h | 267 + i2c-context.c | 102 + i2c-context.h | 26 + klist.c | 430 + klist.h | 94 + knc-asic.c | 551 + knc-asic.h | 88 + knc-transport-spi.c | 148 + knc-transport.h | 23 + lib/Makefile.am | 367 + lib/dummy.c | 42 + lib/memchr.c | 172 + lib/memchr.valgrind | 14 + lib/memmem.c | 76 + lib/sig-handler.h | 44 + lib/sigaction.c | 204 + lib/signal.in.h | 378 + lib/sigprocmask.c | 329 + lib/stddef.in.h | 87 + lib/stdint.in.h | 592 + lib/str-two-way.h | 453 + lib/string.in.h | 981 ++ libbitfury.c | 387 + libbitfury.h | 34 + linux-usb-cgminer | 305 + logging.c | 123 + logging.h | 130 + m4/00gnulib.m4 | 30 + m4/extensions.m4 | 118 + m4/gnulib-cache.m4 | 38 + m4/gnulib-common.m4 | 282 + m4/gnulib-comp.m4 | 268 + m4/gnulib-tool.m4 | 57 + m4/include_next.m4 | 244 + m4/longlong.m4 | 113 + m4/memchr.m4 | 88 + m4/memmem.m4 | 145 + m4/mmap-anon.m4 | 55 + m4/multiarch.m4 | 62 + m4/onceonly.m4 | 91 + m4/sigaction.m4 | 43 + m4/signal_h.m4 | 58 + m4/signalblocking.m4 | 40 + m4/stddef_h.m4 | 47 + m4/stdint.m4 | 480 + m4/string_h.m4 | 116 + m4/warn-on-use.m4 | 45 + m4/wchar_t.m4 | 24 + mcp2210.c | 366 + mcp2210.h | 73 + miner.h | 1670 +++ miner.php | 3306 +++++ mknsis.sh | 34 + noncedup.c | 99 + sha2.c | 208 + sha2.h | 70 + sha2_c5.c | 310 + sha2_c5.h | 96 + spi-context.c | 94 + spi-context.h | 48 + usbtest.py | 86 + usbutils.c | 4456 ++++++ usbutils.h | 610 + uthash.h | 948 ++ util.c | 4050 ++++++ util.h | 181 + warn-on-use.h | 109 + windows-build.txt | 348 + 262 files changed, 134763 insertions(+) create mode 100644 .gitignore create mode 100644 01-cgminer.rules create mode 100644 A1-board-selector-CCD.c create mode 100644 A1-board-selector-CCR.c create mode 100644 A1-board-selector.h create mode 100644 A1-common.h create mode 100644 A1-desk-board-selector.c create mode 100644 A1-trimpot-mcp4x.c create mode 100644 A1-trimpot-mcp4x.h create mode 100644 API-README create mode 100644 API.class create mode 100644 API.java create mode 100644 ASIC-README create mode 100644 AUTHORS create mode 100644 COPYING create mode 100644 ChangeLog create mode 100644 FPGA-README create mode 100644 LICENSE create mode 100644 MCast.class create mode 100644 MCast.java create mode 100644 Makefile.am create mode 100644 NEWS create mode 100644 README create mode 100644 api-example.c create mode 100644 api-example.php create mode 100644 api-example.py create mode 100644 api-example.rb create mode 100644 api.c create mode 100644 arg-nonnull.h create mode 100644 autogen.sh create mode 100644 bench_block.h create mode 100644 bitforce-firmware-flash.c create mode 100644 bitmain-readme.txt create mode 100644 bitstreams/COPYING_fpgaminer create mode 100644 bitstreams/README create mode 100644 bitstreams/fpgaminer_top_fixed7_197MHz.ncd create mode 100644 c++defs.h create mode 100644 ccan/Makefile.am create mode 100644 ccan/compiler/LICENSE create mode 100644 ccan/compiler/_info create mode 100644 ccan/compiler/compiler.h create mode 100644 ccan/compiler/test/compile_fail-printf.c create mode 100644 ccan/compiler/test/run-is_compile_constant.c create mode 100644 ccan/opt/LICENSE create mode 100644 ccan/opt/_info create mode 100644 ccan/opt/helpers.c create mode 100644 ccan/opt/opt.c create mode 100644 ccan/opt/opt.h create mode 100644 ccan/opt/parse.c create mode 100644 ccan/opt/private.h create mode 100644 ccan/opt/test/compile_ok-const-arg.c create mode 100644 ccan/opt/test/run-checkopt.c create mode 100644 ccan/opt/test/run-correct-reporting.c create mode 100644 ccan/opt/test/run-helpers.c create mode 100644 ccan/opt/test/run-iter.c create mode 100644 ccan/opt/test/run-no-options.c create mode 100644 ccan/opt/test/run-usage.c create mode 100644 ccan/opt/test/run.c create mode 100644 ccan/opt/test/utils.c create mode 100644 ccan/opt/test/utils.h create mode 100644 ccan/opt/usage.c create mode 100644 ccan/typesafe_cb/LICENSE create mode 100644 ccan/typesafe_cb/_info create mode 100644 ccan/typesafe_cb/test/compile_fail-cast_if_type-promotable.c create mode 100644 ccan/typesafe_cb/test/compile_fail-typesafe_cb-int.c create mode 100644 ccan/typesafe_cb/test/compile_fail-typesafe_cb.c create mode 100644 ccan/typesafe_cb/test/compile_fail-typesafe_cb_cast-multi.c create mode 100644 ccan/typesafe_cb/test/compile_fail-typesafe_cb_cast.c create mode 100644 ccan/typesafe_cb/test/compile_fail-typesafe_cb_postargs.c create mode 100644 ccan/typesafe_cb/test/compile_fail-typesafe_cb_preargs.c create mode 100644 ccan/typesafe_cb/test/compile_ok-typesafe_cb-NULL.c create mode 100644 ccan/typesafe_cb/test/compile_ok-typesafe_cb-undefined.c create mode 100644 ccan/typesafe_cb/test/compile_ok-typesafe_cb-vars.c create mode 100644 ccan/typesafe_cb/test/compile_ok-typesafe_cb_cast.c create mode 100644 ccan/typesafe_cb/test/run.c create mode 100644 ccan/typesafe_cb/typesafe_cb.h create mode 100644 cgminer.c create mode 100644 compat.h create mode 100644 compat/.gitignore create mode 100644 compat/Makefile.am create mode 100644 compat/jansson-2.6/CHANGES create mode 100644 compat/jansson-2.6/LICENSE create mode 100644 compat/jansson-2.6/Makefile.am create mode 100644 compat/jansson-2.6/README.rst create mode 100644 compat/jansson-2.6/configure.ac create mode 100644 compat/jansson-2.6/jansson.pc.in create mode 100644 compat/jansson-2.6/m4/.gitignore create mode 100644 compat/jansson-2.6/src/Makefile.am create mode 100644 compat/jansson-2.6/src/dump.c create mode 100644 compat/jansson-2.6/src/error.c create mode 100644 compat/jansson-2.6/src/hashtable.c create mode 100644 compat/jansson-2.6/src/hashtable.h create mode 100644 compat/jansson-2.6/src/jansson.def create mode 100644 compat/jansson-2.6/src/jansson.h create mode 100644 compat/jansson-2.6/src/jansson_config.h.in create mode 100644 compat/jansson-2.6/src/jansson_private.h create mode 100644 compat/jansson-2.6/src/load.c create mode 100644 compat/jansson-2.6/src/memory.c create mode 100644 compat/jansson-2.6/src/pack_unpack.c create mode 100644 compat/jansson-2.6/src/strbuffer.c create mode 100644 compat/jansson-2.6/src/strbuffer.h create mode 100644 compat/jansson-2.6/src/strconv.c create mode 100644 compat/jansson-2.6/src/utf.c create mode 100644 compat/jansson-2.6/src/utf.h create mode 100644 compat/jansson-2.6/src/value.c create mode 100644 compat/libusb-1.0/AUTHORS create mode 100644 compat/libusb-1.0/COPYING create mode 100644 compat/libusb-1.0/Makefile.am create mode 100644 compat/libusb-1.0/NEWS create mode 100644 compat/libusb-1.0/PORTING create mode 100644 compat/libusb-1.0/README create mode 100644 compat/libusb-1.0/THANKS create mode 100644 compat/libusb-1.0/TODO create mode 100644 compat/libusb-1.0/configure.ac create mode 100644 compat/libusb-1.0/libusb-1.0.pc.in create mode 100644 compat/libusb-1.0/libusb/Makefile.am create mode 100644 compat/libusb-1.0/libusb/core.c create mode 100644 compat/libusb-1.0/libusb/descriptor.c create mode 100644 compat/libusb-1.0/libusb/hotplug.c create mode 100644 compat/libusb-1.0/libusb/hotplug.h create mode 100644 compat/libusb-1.0/libusb/io.c create mode 100644 compat/libusb-1.0/libusb/libusb-1.0.def create mode 100644 compat/libusb-1.0/libusb/libusb-1.0.rc create mode 100644 compat/libusb-1.0/libusb/libusb.h create mode 100644 compat/libusb-1.0/libusb/libusbi.h create mode 100644 compat/libusb-1.0/libusb/os/darwin_usb.c create mode 100644 compat/libusb-1.0/libusb/os/darwin_usb.h create mode 100644 compat/libusb-1.0/libusb/os/linux_netlink.c create mode 100644 compat/libusb-1.0/libusb/os/linux_udev.c create mode 100644 compat/libusb-1.0/libusb/os/linux_usbfs.c create mode 100644 compat/libusb-1.0/libusb/os/linux_usbfs.h create mode 100644 compat/libusb-1.0/libusb/os/openbsd_usb.c create mode 100644 compat/libusb-1.0/libusb/os/poll_posix.h create mode 100644 compat/libusb-1.0/libusb/os/poll_windows.c create mode 100644 compat/libusb-1.0/libusb/os/poll_windows.h create mode 100644 compat/libusb-1.0/libusb/os/threads_posix.c create mode 100644 compat/libusb-1.0/libusb/os/threads_posix.h create mode 100644 compat/libusb-1.0/libusb/os/threads_windows.c create mode 100644 compat/libusb-1.0/libusb/os/threads_windows.h create mode 100644 compat/libusb-1.0/libusb/os/windows_usb.c create mode 100644 compat/libusb-1.0/libusb/os/windows_usb.h create mode 100644 compat/libusb-1.0/libusb/sync.c create mode 100644 compat/libusb-1.0/libusb/version.h create mode 100644 compat/libusb-1.0/m4/.gitignore create mode 100644 configure.ac create mode 100644 crc.h create mode 100644 crc16.c create mode 100644 driver-SPI-bitmine-A1.c create mode 100644 driver-avalon.c create mode 100644 driver-avalon.h create mode 100644 driver-avalon2.c create mode 100644 driver-avalon2.h create mode 100644 driver-avalon4.c create mode 100644 driver-avalon4.h create mode 100644 driver-bab.c create mode 100644 driver-bflsc.c create mode 100644 driver-bflsc.h create mode 100644 driver-bitforce.c create mode 100644 driver-bitfury.c create mode 100644 driver-bitfury.h create mode 100644 driver-bitmain.c create mode 100644 driver-bitmain.h create mode 100644 driver-blockerupter.c create mode 100644 driver-blockerupter.h create mode 100644 driver-bmsc.c create mode 100644 driver-btm-c5.c create mode 100644 driver-btm-c5.h create mode 100644 driver-cointerra.c create mode 100644 driver-cointerra.h create mode 100644 driver-drillbit.c create mode 100644 driver-drillbit.h create mode 100644 driver-hashfast.c create mode 100644 driver-hashfast.h create mode 100644 driver-hashratio.c create mode 100644 driver-hashratio.h create mode 100644 driver-icarus.c create mode 100644 driver-klondike.c create mode 100644 driver-knc.c create mode 100644 driver-minion.c create mode 100644 driver-modminer.c create mode 100644 driver-spondoolies-sp10-p.c create mode 100644 driver-spondoolies-sp10-p.h create mode 100644 driver-spondoolies-sp10.c create mode 100644 driver-spondoolies-sp10.h create mode 100644 driver-spondoolies-sp30-p.c create mode 100644 driver-spondoolies-sp30-p.h create mode 100644 driver-spondoolies-sp30.c create mode 100644 driver-spondoolies-sp30.h create mode 100644 elist.h create mode 100644 example.conf create mode 100644 fpgautils.c create mode 100644 fpgautils.h create mode 100644 hexdump.c create mode 100644 hf_protocol.h create mode 100644 hf_protocol_be.h create mode 100644 i2c-context.c create mode 100644 i2c-context.h create mode 100644 klist.c create mode 100644 klist.h create mode 100644 knc-asic.c create mode 100644 knc-asic.h create mode 100644 knc-transport-spi.c create mode 100644 knc-transport.h create mode 100644 lib/Makefile.am create mode 100644 lib/dummy.c create mode 100644 lib/memchr.c create mode 100644 lib/memchr.valgrind create mode 100644 lib/memmem.c create mode 100644 lib/sig-handler.h create mode 100644 lib/sigaction.c create mode 100644 lib/signal.in.h create mode 100644 lib/sigprocmask.c create mode 100644 lib/stddef.in.h create mode 100644 lib/stdint.in.h create mode 100644 lib/str-two-way.h create mode 100644 lib/string.in.h create mode 100644 libbitfury.c create mode 100644 libbitfury.h create mode 100644 linux-usb-cgminer create mode 100644 logging.c create mode 100644 logging.h create mode 100644 m4/00gnulib.m4 create mode 100644 m4/extensions.m4 create mode 100644 m4/gnulib-cache.m4 create mode 100644 m4/gnulib-common.m4 create mode 100644 m4/gnulib-comp.m4 create mode 100644 m4/gnulib-tool.m4 create mode 100644 m4/include_next.m4 create mode 100644 m4/longlong.m4 create mode 100644 m4/memchr.m4 create mode 100644 m4/memmem.m4 create mode 100644 m4/mmap-anon.m4 create mode 100644 m4/multiarch.m4 create mode 100644 m4/onceonly.m4 create mode 100644 m4/sigaction.m4 create mode 100644 m4/signal_h.m4 create mode 100644 m4/signalblocking.m4 create mode 100644 m4/stddef_h.m4 create mode 100644 m4/stdint.m4 create mode 100644 m4/string_h.m4 create mode 100644 m4/warn-on-use.m4 create mode 100644 m4/wchar_t.m4 create mode 100644 mcp2210.c create mode 100644 mcp2210.h create mode 100644 miner.h create mode 100644 miner.php create mode 100644 mknsis.sh create mode 100644 noncedup.c create mode 100644 sha2.c create mode 100644 sha2.h create mode 100644 sha2_c5.c create mode 100644 sha2_c5.h create mode 100644 spi-context.c create mode 100644 spi-context.h create mode 100644 usbtest.py create mode 100644 usbutils.c create mode 100644 usbutils.h create mode 100644 uthash.h create mode 100644 util.c create mode 100644 util.h create mode 100644 warn-on-use.h create mode 100644 windows-build.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..19efe46 --- /dev/null +++ b/.gitignore @@ -0,0 +1,86 @@ +nbproject +ccan/opt/.dirstamp +compat/jansson-2.6/jansson.pc +compat/jansson-2.6/libtool +compat/jansson-2.6/ltmain.sh +compat/jansson-2.6/m4/libtool.m4 +compat/jansson-2.6/m4/ltoptions.m4 +compat/jansson-2.6/m4/ltsugar.m4 +compat/jansson-2.6/m4/ltversion.m4 +compat/jansson-2.6/m4/lt~obsolete.m4 +compat/jansson-2.6/src/.libs/ +compat/jansson-2.6/src/dump.lo +compat/jansson-2.6/src/error.lo +compat/jansson-2.6/src/hashtable.lo +compat/jansson-2.6/src/jansson_config.h +compat/jansson-2.6/src/libjansson.la +compat/jansson-2.6/src/load.lo +compat/jansson-2.6/src/memory.lo +compat/jansson-2.6/src/pack_unpack.lo +compat/jansson-2.6/src/strbuffer.lo +compat/jansson-2.6/src/strconv.lo +compat/jansson-2.6/src/utf.lo +compat/jansson-2.6/src/value.lo +compat/libusb-1.0/libtool +compat/libusb-1.0/libusb-1.0.pc +compat/libusb-1.0/libusb/.libs/ +compat/libusb-1.0/libusb/os/.dirstamp +compat/libusb-1.0/ltmain.sh +libtool +ltmain.sh +m4/libtool.m4 +m4/ltoptions.m4 +m4/ltsugar.m4 +m4/ltversion.m4 +m4/lt~obsolete.m4 + + + +cgminer +cgminer.exe +minerd +minerd.exe +*.o +*.bin + +autom4te.cache +.deps + +Makefile +Makefile.in +INSTALL +aclocal.m4 +configure +depcomp +missing +install-sh +stamp-h1 +cpuminer-config.h* +compile +config.log +config.status +config.guess +config.sub + +mingw32-config.cache + +*~ + +ext_deps +config.h.in +config.h + + +ccan/libccan.a +lib/arg-nonnull.h +lib/c++defs.h +lib/libgnu.a +lib/signal.h +lib/string.h +lib/stdint.h +lib/warn-on-use.h + +mkinstalldirs + +*.swp +*.pre diff --git a/01-cgminer.rules b/01-cgminer.rules new file mode 100644 index 0000000..93c25a8 --- /dev/null +++ b/01-cgminer.rules @@ -0,0 +1,52 @@ +# Butterfly Labs FPGA and ASIC devices +ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6014", SUBSYSTEM=="usb", ACTION=="add", MODE="0666", GROUP="plugdev" + +# ModMinerQuad +ATTRS{idVendor}=="1fc9", ATTRS{idProduct}=="0003", SUBSYSTEM=="usb", ACTION=="add", MODE="0666", GROUP="plugdev" + +# Lancelot and Avalon +ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", SUBSYSTEM=="usb", ACTION=="add", MODE="0666", GROUP="plugdev" + +# Icarus +ATTRS{idVendor}=="067b", ATTRS{idProduct}=="2303", SUBSYSTEM=="usb", ACTION=="add", MODE="0666", GROUP="plugdev" +ATTRS{idVendor}=="1fc9", ATTRS{idProduct}=="0083", SUBSYSTEM=="usb", ACTION=="add", MODE="0666", GROUP="plugdev" + +# AsicminerUSB and Antminer U1 +ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", SUBSYSTEM=="usb", ACTION=="add", MODE="0666", GROUP="plugdev" + +# Cairnsmore1 +ATTRS{idVendor}=="067b", ATTRS{idProduct}=="0230", SUBSYSTEM=="usb", ACTION=="add", MODE="0666", GROUP="plugdev" + +# Cairnsmore1-2 +ATTRS{idVendor}=="0403", ATTRS{idProduct}=="8350", SUBSYSTEM=="usb", ACTION=="add", MODE="0666", GROUP="plugdev" + +# Ztex +ATTRS{idVendor}=="221a", ATTRS{idProduct}=="0100", SUBSYSTEM=="usb", ACTION=="add", MODE="0666", GROUP="plugdev" + +# BF1 +ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="204b", SUBSYSTEM=="usb", ACTION=="add", MODE="0666", GROUP="plugdev" + +# Klondike +ATTRS{idVendor}=="04d8", ATTRS{idProduct}=="f60a", SUBSYSTEM=="usb", ACTION=="add", MODE="0666", GROUP="plugdev" + +# HashFast +ATTRS{idVendor}=="297c", ATTRS{idProduct}=="0001", SUBSYSTEM=="usb", ACTION=="add", MODE="0666", GROUP="plugdev" +ATTRS{idVendor}=="297c", ATTRS{idProduct}=="8001", SUBSYSTEM=="usb", ACTION=="add", MODE="0666", GROUP="plugdev" + +# BXF +ATTRS{idVendor}=="198c", ATTRS{idProduct}=="b1f1", SUBSYSTEM=="usb", ACTION=="add", MODE="0666", GROUP="plugdev" + +# NF1 +ATTRS{idVendor}=="04d8", ATTRS{idProduct}=="00de", SUBSYSTEM=="usb", ACTION=="add", MODE="0666", GROUP="plugdev" + +# ANT_S1 +ATTRS{idVendor}=="4254", ATTRS{idProduct}=="4153", SUBSYSTEM=="usb", ACTION=="add", MODE="0666", GROUP="plugdev" + +# Cointerra +ATTRS{idVendor}=="1cbe", ATTRS{idProduct}=="0003", SUBSYSTEM=="usb", ACTION=="add", MODE="0666", GROUP="plugdev" + +# Drillbit Thumb +ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2404", SUBSYSTEM=="usb", ACTION=="add", MODE="0666", GROUP="plugdev" + +# Avalon4 +ATTRS{idVendor}=="29f1", ATTRS{idProduct}=="33f2", SUBSYSTEM=="usb", ACTION=="add", MODE="0666", GROUP="plugdev" diff --git a/A1-board-selector-CCD.c b/A1-board-selector-CCD.c new file mode 100644 index 0000000..d0e173f --- /dev/null +++ b/A1-board-selector-CCD.c @@ -0,0 +1,117 @@ +/* + * board selector support for TCA9535 used in Bitmine's CoinCraft Desk + * + * Copyright 2014 Zefir Kurtisi + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "miner.h" + +#include "A1-board-selector.h" +#include "i2c-context.h" + +static struct board_selector ccd_selector; + +struct i2c_ctx *U1_tca9535; +uint8_t chain_mask = 0xff; +uint8_t active_chain = 255; +pthread_mutex_t lock; + + +#define UNUSED_BITS 0xe0 + +static void ccd_unlock(void) +{ + mutex_unlock(&lock); +} + +static void ccd_exit(void) +{ + if (U1_tca9535 != NULL) + U1_tca9535->exit(U1_tca9535); +} +uint8_t retval = 0; + +extern struct board_selector *ccd_board_selector_init(void) +{ + mutex_init(&lock); + U1_tca9535 = i2c_slave_open(I2C_BUS, 0x27); + if (U1_tca9535 == NULL) + return NULL; + bool retval = U1_tca9535->write(U1_tca9535, 0x06, 0xe0) && + U1_tca9535->write(U1_tca9535, 0x07, 0xe0) && + U1_tca9535->write(U1_tca9535, 0x02, 0x1f) && + U1_tca9535->write(U1_tca9535, 0x03, 0x00); + if (retval) + return &ccd_selector; + ccd_exit(); + return NULL; +} + +static bool ccd_select(uint8_t chain) +{ + if (chain >= CCD_MAX_CHAINS) + return false; + + mutex_lock(&lock); + if (active_chain == chain) + return true; + + active_chain = chain; + chain_mask = 1 << active_chain; + return U1_tca9535->write(U1_tca9535, 0x02, ~chain_mask); +} + +static bool __ccd_board_selector_reset(uint8_t mask) +{ + if (!U1_tca9535->write(U1_tca9535, 0x03, mask)) + return false; + cgsleep_ms(RESET_LOW_TIME_MS); + if (!U1_tca9535->write(U1_tca9535, 0x03, 0x00)) + return false; + cgsleep_ms(RESET_HI_TIME_MS); + return true; +} +// we assume we are already holding the mutex +static bool ccd_reset(void) +{ + return __ccd_board_selector_reset(chain_mask); +} + +static bool ccd_reset_all(void) +{ + mutex_lock(&lock); + bool retval = __ccd_board_selector_reset(0xff & ~UNUSED_BITS); + mutex_unlock(&lock); + return retval; +} + + +static struct board_selector ccd_selector = { + .select = ccd_select, + .release = ccd_unlock, + .exit = ccd_exit, + .reset = ccd_reset, + .reset_all = ccd_reset_all, + /* don't have a temp sensor dedicated to chain */ + .get_temp = dummy_get_temp, +}; + diff --git a/A1-board-selector-CCR.c b/A1-board-selector-CCR.c new file mode 100644 index 0000000..1752d3c --- /dev/null +++ b/A1-board-selector-CCR.c @@ -0,0 +1,186 @@ +/* + * board selector support for TCA9535 used in Bitmine's CoinCraft Desk + * + * Copyright 2014 Zefir Kurtisi + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + + +#include "miner.h" + +#include "A1-board-selector.h" +#include "i2c-context.h" + + +static struct board_selector ccr_selector; + +static struct i2c_ctx *U1_tca9548; +static struct i2c_ctx *U3_tca9535; +static struct i2c_ctx *U4_tca9535; +static uint8_t active_chain; +static pthread_mutex_t lock; + +struct chain_mapping { + uint8_t chain_id; + uint8_t U1; + uint8_t U3p0; + uint8_t U3p1; +}; + +static const struct chain_mapping chain_mapping[CCR_MAX_CHAINS] = { + { 0, 0x01, 0x01, 0x00, }, + { 1, 0x01, 0x00, 0x80, }, + { 2, 0x02, 0x02, 0x00, }, + { 3, 0x02, 0x00, 0x40, }, + { 4, 0x04, 0x04, 0x00, }, + { 5, 0x04, 0x00, 0x20, }, + { 6, 0x08, 0x08, 0x00, }, + { 7, 0x08, 0x00, 0x10, }, + { 8, 0x10, 0x10, 0x00, }, + { 9, 0x10, 0x00, 0x08, }, + { 10, 0x20, 0x20, 0x00, }, + { 11, 0x20, 0x00, 0x04, }, + { 12, 0x40, 0x40, 0x00, }, + { 13, 0x40, 0x00, 0x02, }, + { 14, 0x80, 0x80, 0x00, }, + { 15, 0x80, 0x00, 0x01, }, +}; + +static void ccr_unlock(void) +{ + mutex_unlock(&lock); +} + +static void ccr_exit(void) +{ + if (U1_tca9548 != NULL) + U1_tca9548->exit(U1_tca9548); + if (U3_tca9535 != NULL) + U3_tca9535->exit(U3_tca9535); + if (U4_tca9535 != NULL) + U4_tca9535->exit(U4_tca9535); +} + + +extern struct board_selector *ccr_board_selector_init(void) +{ + mutex_init(&lock); + applog(LOG_INFO, "ccr_board_selector_init()"); + + /* detect all i2c slaves */ + U1_tca9548 = i2c_slave_open(I2C_BUS, 0x70); + U3_tca9535 = i2c_slave_open(I2C_BUS, 0x23); + U4_tca9535 = i2c_slave_open(I2C_BUS, 0x22); + if (U1_tca9548 == NULL || U3_tca9535 == NULL || U4_tca9535 == NULL) + goto fail; + + /* init I2C multiplexer */ + bool res = U1_tca9548->write(U1_tca9548, 0x00, 0x00) && + /* init reset selector */ + U3_tca9535->write(U3_tca9535, 0x06, 0x00) && + U3_tca9535->write(U3_tca9535, 0x07, 0x00) && + U3_tca9535->write(U3_tca9535, 0x02, 0x00) && + U3_tca9535->write(U3_tca9535, 0x03, 0x00) && + /* init chain selector */ + U4_tca9535->write(U4_tca9535, 0x06, 0x00) && + U4_tca9535->write(U4_tca9535, 0x07, 0x00) && + U4_tca9535->write(U4_tca9535, 0x02, 0x00) && + U4_tca9535->write(U4_tca9535, 0x03, 0x00); + + if (!res) + goto fail; + + return &ccr_selector; + +fail: + ccr_exit(); + return NULL; +} + +static bool ccr_select(uint8_t chain) +{ + if (chain >= CCR_MAX_CHAINS) + return false; + + mutex_lock(&lock); + if (active_chain == chain) + return true; + + active_chain = chain; + const struct chain_mapping *cm = &chain_mapping[chain]; + + if (!U1_tca9548->write(U1_tca9548, cm->U1, cm->U1)) + return false; + + if (!U4_tca9535->write(U4_tca9535, 0x02, cm->U3p0) || + !U4_tca9535->write(U4_tca9535, 0x03, cm->U3p1)) + return false; + + /* sanity check: ensure i2c command has been written before we leave */ + uint8_t tmp; + if (!U4_tca9535->read(U4_tca9535, 0x02, &tmp) || tmp != cm->U3p0) { + applog(LOG_ERR, "ccr_select: wrote 0x%02x, read 0x%02x", + cm->U3p0, tmp); + } + applog(LOG_DEBUG, "selected chain %d", chain); + return true; +} + +static bool __ccr_board_selector_reset(uint8_t p0, uint8_t p1) +{ + if (!U3_tca9535->write(U3_tca9535, 0x02, p0) || + !U3_tca9535->write(U3_tca9535, 0x03, p1)) + return false; + cgsleep_ms(RESET_LOW_TIME_MS); + if (!U3_tca9535->write(U3_tca9535, 0x02, 0x00) || + !U3_tca9535->write(U3_tca9535, 0x03, 0x00)) + return false; + cgsleep_ms(RESET_HI_TIME_MS); + return true; +} +// we assume we are already holding the mutex +static bool ccr_reset(void) +{ + const struct chain_mapping *cm = &chain_mapping[active_chain]; + applog(LOG_DEBUG, "resetting chain %d", cm->chain_id); + bool retval = __ccr_board_selector_reset(cm->U3p0, cm->U3p1); + return retval; +} + +static bool ccr_reset_all(void) +{ + mutex_lock(&lock); + bool retval = __ccr_board_selector_reset(0xff, 0xff); + mutex_unlock(&lock); + return retval; +} + +static uint8_t ccr_get_temp(uint8_t sensor_id) +{ + if ((active_chain & 1) != 0 || sensor_id != 0) + return 0; + + struct i2c_ctx *U7 = i2c_slave_open(I2C_BUS, 0x4c); + if (U7 == NULL) + return 0; + + uint8_t retval = 0; + if (!U7->read(U7, 0, &retval)) + retval = 0; + U7->exit(U7); + return retval; +} + +static struct board_selector ccr_selector = { + .select = ccr_select, + .release = ccr_unlock, + .exit = ccr_exit, + .reset = ccr_reset, + .reset_all = ccr_reset_all, + .get_temp = ccr_get_temp, +}; + diff --git a/A1-board-selector.h b/A1-board-selector.h new file mode 100644 index 0000000..bcadf7d --- /dev/null +++ b/A1-board-selector.h @@ -0,0 +1,50 @@ +#ifndef A1_BOARD_SELECTOR_H +#define A1_BOARD_SELECTOR_H + +#include +#include + +#define RESET_LOW_TIME_MS 200 +#define RESET_HI_TIME_MS 100 + +struct board_selector { + /* destructor */ + void (*exit)(void); + /* select board and chip chain for given chain index*/ + bool (*select)(uint8_t chain); + /* release access to selected chain */ + void (*release)(void); + /* reset currently selected chain */ + bool (*reset)(void); + /* reset all chains on board */ + bool (*reset_all)(void); + /* get temperature for selected chain at given sensor */ + uint8_t (*get_temp)(uint8_t sensor); + /* prepare board (voltage) for given sys_clock */ + bool (*prepare_clock)(int clock_khz); +}; + +static bool dummy_select(uint8_t b) { (void)b; return true; } +static void dummy_void(void) { }; +static bool dummy_bool(void) { return true; } +//static uint8_t dummy_u8(void) { return 0; } +static uint8_t dummy_get_temp(uint8_t s) { (void)s; return 0; } +static bool dummy_prepare_clock(int c) { (void)c; return true; } + +static const struct board_selector dummy_board_selector = { + .exit = dummy_void, + .select = dummy_select, + .release = dummy_void, + .reset = dummy_bool, + .reset_all = dummy_bool, + .get_temp = dummy_get_temp, + .prepare_clock = dummy_prepare_clock, +}; + +/* CoinCraft Desk and Rig board selector constructors */ +#define CCD_MAX_CHAINS 5 +#define CCR_MAX_CHAINS 16 +extern struct board_selector *ccd_board_selector_init(void); +extern struct board_selector *ccr_board_selector_init(void); + +#endif /* A1_BOARD_SELECTOR_H */ diff --git a/A1-common.h b/A1-common.h new file mode 100644 index 0000000..b048a99 --- /dev/null +++ b/A1-common.h @@ -0,0 +1,91 @@ +#ifndef A1_COMMON_H +#define A1_COMMON_H + +#include +#include +#include + +/********** work queue */ +struct work_ent { + struct work *work; + struct list_head head; +}; + +struct work_queue { + int num_elems; + struct list_head head; +}; + +/********** chip and chain context structures */ +/* the WRITE_JOB command is the largest (2 bytes command, 56 bytes payload) */ +#define WRITE_JOB_LENGTH 58 +#define MAX_CHAIN_LENGTH 64 +/* + * For commands to traverse the chain, we need to issue dummy writes to + * keep SPI clock running. To reach the last chip in the chain, we need to + * write the command, followed by chain-length words to pass it through the + * chain and another chain-length words to get the ACK back to host + */ +#define MAX_CMD_LENGTH (WRITE_JOB_LENGTH + MAX_CHAIN_LENGTH * 2 * 2) + +struct A1_chip { + int num_cores; + int last_queued_id; + struct work *work[4]; + /* stats */ + int hw_errors; + int stales; + int nonces_found; + int nonce_ranges_done; + + /* systime in ms when chip was disabled */ + int cooldown_begin; + /* number of consecutive failures to access the chip */ + int fail_count; + /* mark chip disabled, do not try to re-enable it */ + bool disabled; +}; + +struct A1_chain { + int chain_id; + struct cgpu_info *cgpu; + struct mcp4x *trimpot; + int num_chips; + int num_cores; + int num_active_chips; + int chain_skew; + uint8_t spi_tx[MAX_CMD_LENGTH]; + uint8_t spi_rx[MAX_CMD_LENGTH]; + struct spi_ctx *spi_ctx; + struct A1_chip *chips; + pthread_mutex_t lock; + + struct work_queue active_wq; + + /* mark chain disabled, do not try to re-enable it */ + bool disabled; + uint8_t temp; + int last_temp_time; +}; + +#define MAX_CHAINS_PER_BOARD 2 +struct A1_board { + int board_id; + int num_chains; + struct A1_chain *chain[MAX_CHAINS_PER_BOARD]; +}; + +/********** config paramters */ +struct A1_config_options { + int ref_clk_khz; + int sys_clk_khz; + int spi_clk_khz; + /* limit chip chain to this number of chips (testing only) */ + int override_chip_num; + int wiper; +}; + +/* global configuration instance */ +extern struct A1_config_options A1_config_options; + +#endif /* A1_COMMON_H */ diff --git a/A1-desk-board-selector.c b/A1-desk-board-selector.c new file mode 100644 index 0000000..fdc1a6d --- /dev/null +++ b/A1-desk-board-selector.c @@ -0,0 +1,158 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "miner.h" + +struct pcf8575_ctx { + uint8_t addr; + uint8_t p0; + uint8_t p1; + int file; + uint8_t active_board; + pthread_mutex_t lock; +}; + +static struct pcf8575_ctx board_ctx = { 0x27, 0xff, 0xff, -1, .active_board = 255,}; + + +#define UNUSED_BITS 0xe0 +#define SLEEP_MS_AFTER_CS 0 +static bool pcf8575_write(void) +{ + union i2c_smbus_data data; + data.byte = board_ctx.p1 | UNUSED_BITS; + + struct i2c_smbus_ioctl_data args; + __s32 err; + + args.read_write = I2C_SMBUS_WRITE; + args.command = board_ctx.p0 | UNUSED_BITS; + args.size = I2C_SMBUS_BYTE_DATA; + args.data = &data; + + err = ioctl(board_ctx.file, I2C_SMBUS, &args); + if (err == -1) { + fprintf(stderr, + "Error: Failed to write: %s\n", + strerror(errno)); + err = -errno; + } else { + applog(LOG_DEBUG, "written: 0x%02x, 0x%02x", board_ctx.p0, board_ctx.p1); +// usleep(25000); + cgsleep_ms(SLEEP_MS_AFTER_CS); + } + return err == 0; +} + +void lock_board_selector(void) +{ +// applog(LOG_WARNING, "lock_board_selector()"); + mutex_lock(&board_ctx.lock); +} + +void unlock_board_selector(void) +{ +// applog(LOG_WARNING, "unlock_board_selector()"); + mutex_unlock(&board_ctx.lock); +} + +bool a1_board_selector_init(void) +{ + mutex_init(&board_ctx.lock); + applog(LOG_WARNING, "a1_board_selector_init()"); + + board_ctx.file = open("/dev/i2c-1", O_RDWR); + if (board_ctx.file < 0) { + fprintf(stderr, + "Error: Could not open i2c-1: %s\n", + board_ctx.addr, strerror(errno)); + return false; + } + + if (ioctl(board_ctx.file, I2C_SLAVE, board_ctx.addr) < 0) { + fprintf(stderr, + "Error: Could not set address to 0x%02x: %s\n", + board_ctx.addr, strerror(errno)); + return false; + } + return pcf8575_write(); +} + +void a1_board_selector_exit(void) +{ + close(board_ctx.file); + board_ctx.file = -1; +} + +bool a1_board_selector_select_board(uint8_t board) +{ + if (board > 7) + return false; + +// applog(LOG_WARNING, "board_selector_select_board(%d)", board); + lock_board_selector(); + if (board_ctx.active_board == board) + return true; + + board_ctx.active_board = board; + board_ctx.p0 = 1 << board_ctx.active_board; + board_ctx.p1 = 0xff; + bool retval = pcf8575_write(); + return retval; +} + +static bool __board_selector_reset(void) +{ + board_ctx.p1 = ~board_ctx.p0; + if (!pcf8575_write()) + return false; + usleep(1000000); + board_ctx.p1 = 0xff; + if (!pcf8575_write()) + return false; + usleep(1000000); + return true; +} +// we assume we are already holding the mutex +bool a1_board_selector_reset_board(void) +{ +// lock_board_selector(); + bool retval = __board_selector_reset(); +// unlock_board_selector(); + return retval; +} + +bool a1_board_selector_reset_all_boards(void) +{ + lock_board_selector(); + board_ctx.p1 = 0; + bool retval = __board_selector_reset(); + unlock_board_selector(); + return retval; +} + +#if 0 +int main(void) +{ + if (init_pcf8575(&board_ctx)) { + if (!pcf8575_write(&g_ctx)) { + fprintf(stderr, + "Error: Failed to write: %s\n", + strerror(errno)); + } + a1_board_selector_exit(&g_ctx); + } + return 0; +} +#endif +///////////////////////////////////////////////////////////////////////////// diff --git a/A1-trimpot-mcp4x.c b/A1-trimpot-mcp4x.c new file mode 100644 index 0000000..a9244fd --- /dev/null +++ b/A1-trimpot-mcp4x.c @@ -0,0 +1,116 @@ +/* + * support for MCP46x digital trimpot used in Bitmine's products + * + * Copyright 2014 Zefir Kurtisi + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "miner.h" + +#include "A1-trimpot-mcp4x.h" + + +static bool mcp4x_check_status(int file) +{ + union i2c_smbus_data data; + struct i2c_smbus_ioctl_data args; + + args.read_write = I2C_SMBUS_READ; + args.command = ((5 & 0x0f) << 4) | 0x0c; + args.size = I2C_SMBUS_WORD_DATA; + args.data = &data; + + return ioctl(file, I2C_SMBUS, &args) >= 0; +} + +static uint16_t mcp4x_get_wiper(struct mcp4x *me, uint8_t id) +{ + assert(id < 2); + union i2c_smbus_data data; + struct i2c_smbus_ioctl_data args; + + args.read_write = I2C_SMBUS_READ; + args.command = ((id & 0x0f) << 4) | 0x0c; + args.size = I2C_SMBUS_WORD_DATA; + args.data = &data; + + if (ioctl(me->file, I2C_SMBUS, &args) < 0) { + applog(LOG_ERR, "Failed to read id %d: %s\n", id, + strerror(errno)); + return 0xffff; + } + return htobe16(data.word & 0xffff); +} + +static bool mcp4x_set_wiper(struct mcp4x *me, uint8_t id, uint16_t w) +{ + assert(id < 2); + union i2c_smbus_data data; + data.word = w; + + struct i2c_smbus_ioctl_data args; + + args.read_write = I2C_SMBUS_WRITE; + args.command = (id & 0x0f) << 4; + args.size = I2C_SMBUS_WORD_DATA; + args.data = &data; + + if (ioctl(me->file, I2C_SMBUS, &args) < 0) { + applog(LOG_ERR, "Failed to read id %d: %s\n", id, + strerror(errno)); + return false; + } + return me->get_wiper(me, id) == w; +} + +void mcp4x_exit(struct mcp4x *me) +{ + close(me->file); + free(me); +} + +struct mcp4x *mcp4x_init(uint8_t addr) +{ + struct mcp4x *me; + int file = open("/dev/i2c-1", O_RDWR); + if (file < 0) { + applog(LOG_INFO, "Failed to open i2c-1: %s\n", strerror(errno)); + return NULL; + } + + if (ioctl(file, I2C_SLAVE, addr) < 0) + return NULL; + + if (!mcp4x_check_status(file)) + return NULL; + + me = malloc(sizeof(*me)); + assert(me != NULL); + + me->addr = addr; + me->file = file; + me->exit = mcp4x_exit; + me->get_wiper = mcp4x_get_wiper; + me->set_wiper = mcp4x_set_wiper; + return me; +} + diff --git a/A1-trimpot-mcp4x.h b/A1-trimpot-mcp4x.h new file mode 100644 index 0000000..b7ff600 --- /dev/null +++ b/A1-trimpot-mcp4x.h @@ -0,0 +1,19 @@ +#ifndef TRIMPOT_MPC4X_H +#define TRIMPOT_MPC4X_H + +#include +#include + + +struct mcp4x { + uint16_t (*get_wiper)(struct mcp4x *me, uint8_t id); + bool (*set_wiper)(struct mcp4x *me, uint8_t id, uint16_t w); + void (*exit)(struct mcp4x *me); + uint8_t addr; + int file; +}; + +/* constructor */ +extern struct mcp4x *mcp4x_init(uint8_t addr); + +#endif /* TRIMPOT_MPC4X_H */ diff --git a/API-README b/API-README new file mode 100644 index 0000000..78564b0 --- /dev/null +++ b/API-README @@ -0,0 +1,1852 @@ + +This README contains details about the cgminer RPC API + +It also includes some detailed information at the end, +about using miner.php + + +If you start cgminer with the "--api-listen" option, it will listen on a +simple TCP/IP socket for single string API requests from the same machine +running cgminer and reply with a string and then close the socket each time +If you add the "--api-network" option, it will accept API requests from any +network attached computer. + +You can only access the comands that reply with data in this mode. +By default, you cannot access any privileged command that affects the miner - +you will receive an access denied status message see --api-allow below. + +You can specify IP addresses/prefixes that are only allowed to access the API +with the "--api-allow" option e.g. --api-allow W:192.168.0.1,10.0.0/24 +will allow 192.168.0.1 or any address matching 10.0.0.*, but nothing else +IP addresses are automatically padded with extra '.0's as needed +Without a /prefix is the same as specifying /32 +0/0 means all IP addresses. +The 'W:' on the front gives that address/subnet privileged access to commands +that modify cgminer (thus all API commands) +Without it those commands return an access denied status. +See --api-groups below to define other groups like W: +Privileged access is checked in the order the IP addresses were supplied to +"--api-allow" +The first match determines the privilege level. +Using the "--api-allow" option overides the "--api-network" option if they +are both specified +With "--api-allow", 127.0.0.1 is not by default given access unless specified + +If you start cgminer also with the "--api-mcast" option, it will listen for +a multicast message and reply to it with a message containing it's API port +number, but only if the IP address of the sender is allowed API access + +More groups (like the privileged group W:) can be defined using the +--api-groups command +Valid groups are only the letters A-Z (except R & W are predefined) and are +not case sensitive +The R: group is the same as not privileged access +The W: group is (as stated) privileged access (thus all API commands) +To give an IP address/subnet access to a group you use the group letter +in front of the IP address instead of W: e.g. P:192.168.0/32 +An IP address/subnet can only be a member of one group +A sample API group would be: + --api-groups + P:switchpool:enablepool:addpool:disablepool:removepool:poolpriority:* +This would create a group 'P' that can do all current pool commands and all +non-priviliged commands - the '*' means all non-priviledged commands +Without the '*' the group would only have access to the pool commands +Defining multiple groups example: + --api-groups Q:quit:restart:*,S:save +This would define 2 groups: + Q: that can 'quit' and 'restart' as well as all non-priviledged commands + S: that can only 'save' and no other commands + +The RPC API request can be either simple text or JSON. + +If the request is JSON (starts with '{'), it will reply with a JSON formatted +response, otherwise it replies with text formatted as described further below. + +The JSON request format required is '{"command":"CMD","parameter":"PARAM"}' +(though of course parameter is not required for all requests) +where "CMD" is from the "Request" column below and "PARAM" would be e.g. +the ASC/PGA number if required. + +An example request in both formats to disable Hotplug: + hotplug|0 + {"command":"hotplug","parameter":"0"} + +The format of each reply (unless stated otherwise) is a STATUS section +followed by an optional detail section + +From API version 1.7 onwards, reply strings in JSON and Text have the +necessary escaping as required to avoid ambiguity - they didn't before 1.7 +For JSON the 2 characters '"' and '\' are escaped with a '\' before them +For Text the 4 characters '|' ',' '=' and '\' are escaped the same way + +Only user entered information will contain characters that require being +escaped, such as Pool URL, User and Password or the Config save filename, +when they are returned in messages or as their values by the API + +For API version 1.4 and later: + +The STATUS section is: + + STATUS=X,When=NNN,Code=N,Msg=string,Description=string| + + STATUS=X Where X is one of: + W - Warning + I - Informational + S - Success + E - Error + F - Fatal (code bug) + + When=NNN + Standard long time of request in seconds + + Code=N + Each unique reply has a unique Code (See api.c - #define MSG_NNNNNN) + + Msg=string + Message matching the Code value N + + Description=string + This defaults to the cgminer version but is the value of --api-description + if it was specified at runtime. + +With API V3.1 you can also request multiple report replies in a single command +request +e.g. to request both summary and devs, the command would be summary+devs + +This is only available for report commands that don't need parameters, +and is not available for commands that change anything +Any parameters supplied will be ignored + +The extra formatting of the result is to have a section for each command +e.g. CMD=summary|STATUS=....|CMD=devs|STATUS=... +With JSON, each result is within a section of the command name +e.g. {"summary":{"STATUS":[{"STATUS":"S"...}],"SUMMARY":[...],"id":1}, + "devs":{"STATUS":[{"STATUS:"S"...}],"DEVS":[...],"id":1},"id":1} + +As before, if you supply bad JSON you'll just get a single 'E' STATUS section +in the old format, since it doesn't switch to using the new format until it +correctly processes the JSON and can match a '+' in the command + +If you request a command multiple times, e.g. devs+devs +you'll just get it once +If this results in only one command, it will still use the new layout +with just the one command + +If you request a command that can't be used due to requiring parameters, +a command that isn't a report, or an invalid command, you'll get an 'E' STATUS +for that one but it will still attempt to process all other commands supplied + +Blank/missing commands are ignored e.g. +devs++ +will just show 'devs' using the new layout + +For API version 1.10 and later: + +The list of requests - a (*) means it requires privileged access - and replies: + + Request Reply Section Details + ------- ------------- ------- + version VERSION CGMiner=cgminer, version + API=API| version + + config CONFIG Some miner configuration information: + ASC Count=N, <- the number of ASCs + PGA Count=N, <- the number of PGAs + Pool Count=N, <- the number of Pools + Strategy=Name, <- the current pool strategy + Log Interval=N, <- log interval (--log N) + Device Code=ICA , <- spaced list of compiled + device drivers + OS=Linux/Apple/..., <- operating System + Failover-Only=true/false, <- failover-only setting + ScanTime=N, <- --scan-time setting + Queue=N, <- --queue setting + Expiry=N| <- --expiry setting + + summary SUMMARY The status summary of the miner + e.g. Elapsed=NNN,Found Blocks=N,Getworks=N,...| + + pools POOLS The status of each pool e.g. + Pool=0,URL=http://pool.com:6311,Status=Alive,...| + + devs DEVS Each available PGA and ASC with their details + e.g. ASC=0,Accepted=NN,MHS av=NNN,...,Intensity=D| + Last Share Time=NNN, <- standand long time in sec + (or 0 if none) of last accepted share + Last Share Pool=N, <- pool number (or -1 if none) + Last Valid Work=NNN, <- standand long time in sec + of last work returned that wasn't an HW: + Will not report PGAs if PGA mining is disabled + Will not report ASCs if ASC mining is disabled + + edevs[|old] DEVS The same as devs, except it ignores blacklisted + devices and zombie devices + If you specify the optional 'old' parameter, then + the output will include zombie devices that became + zombies less than 'old' seconds ago + A value of zero for 'old', which is the default, + means ignore all zombies + It will return an empty list of devices if all + devices are blacklisted or zombies + + pga|N PGA The details of a single PGA number N in the same + format and details as for DEVS + This is only available if PGA mining is enabled + Use 'pgacount' or 'config' first to see if there + are any + + pgacount PGAS Count=N| <- the number of PGAs + Always returns 0 if PGA mining is disabled + + switchpool|N (*) + none There is no reply section just the STATUS section + stating the results of switching pool N to the + highest priority (the pool is also enabled) + The Msg includes the pool URL + + enablepool|N (*) + none There is no reply section just the STATUS section + stating the results of enabling pool N + The Msg includes the pool URL + + addpool|URL,USR,PASS (*) + none There is no reply section just the STATUS section + stating the results of attempting to add pool N + The Msg includes the pool number and URL + Use '\\' to get a '\' and '\,' to include a comma + inside URL, USR or PASS + + poolpriority|N,... (*) + none There is no reply section just the STATUS section + stating the results of changing pool priorities + See usage below + + poolquota|N,Q (*) + none There is no reply section just the STATUS section + stating the results of changing pool quota to Q + + disablepool|N (*) + none There is no reply section just the STATUS section + stating the results of disabling pool N + The Msg includes the pool URL + + removepool|N (*) + none There is no reply section just the STATUS section + stating the results of removing pool N + The Msg includes the pool URL + N.B. all details for the pool will be lost + + save|filename (*) + none There is no reply section just the STATUS section + stating success or failure saving the cgminer + config to filename + The filename is optional and will use the cgminer + default if not specified + + quit (*) none Status is a single "BYE" reply before cgminer + quits + + notify NOTIFY The last status and history count of each devices + problem + This lists all devices including those not + supported by the 'devs' command e.g. + NOTIFY=0,Name=ASC,ID=0,Last Well=1332432290,...| + + privileged (*) + none There is no reply section just the STATUS section + stating an error if you do not have privileged + access to the API and success if you do have + privilege + The command doesn't change anything in cgminer + + pgaenable|N (*) + none There is no reply section just the STATUS section + stating the results of the enable request + You cannot enable a PGA if it's status is not WELL + This is only available if PGA mining is enabled + + pgadisable|N (*) + none There is no reply section just the STATUS section + stating the results of the disable request + This is only available if PGA mining is enabled + + pgaidentify|N (*) + none There is no reply section just the STATUS section + stating the results of the identify request + This is only available if PGA mining is enabled + and currently only BFL singles and Cairnsmore1's + with the appropriate firmware support this command + On a BFL single it will flash the led on the front + of the device for appoximately 4s + All other non BFL,ICA PGA devices will return a + warning status message stating that they dont + support it. Non-CMR ICAs will ignore the command. + This adds a 4s delay to the BFL share being + processed so you may get a message stating that + procssing took longer than 7000ms if the request + was sent towards the end of the timing of any work + being worked on + e.g.: BFL0: took 8438ms - longer than 7000ms + You should ignore this + + devdetails DEVDETAILS Each device with a list of their static details + This lists all devices including those not + supported by the 'devs' command + e.g. DEVDETAILS=0,Name=ASC,ID=0,Driver=yuu,...| + + restart (*) none Status is a single "RESTART" reply before cgminer + restarts + + stats STATS Each device or pool that has 1 or more getworks + with a list of stats regarding getwork times + The values returned by stats may change in future + versions thus would not normally be displayed + Device drivers are also able to add stats to the + end of the details returned + + estats[|old] STATS The same as stats, except it ignores blacklisted + devices, zombie devices and pools + If you specify the optional 'old' parameter, then + the output will include zombie devices that became + zombies less than 'old' seconds ago + A value of zero for 'old', which is the default, + means ignore all zombies + It will return an empty list of devices if all + devices are blacklisted or zombies + + check|cmd CHECK Exists=Y/N, <- 'cmd' exists in this version + Access=Y/N| <- you have access to use 'cmd' + + failover-only|true/false (*) + none There is no reply section just the STATUS section + stating what failover-only was set to + + coin COIN Coin mining information: + Hash Method=sha256/scrypt, + Current Block Time=N.N, <- 0 means none + Current Block Hash=XXXX..., <- blank if none + LP=true/false, <- LP is in use on at least 1 pool + Network Difficulty=NN.NN| + + debug|setting (*) + DEBUG Debug settings + The optional commands for 'setting' are the same + as the screen curses debug settings + You can only specify one setting + Only the first character is checked - case + insensitive: + Silent, Quiet, Verbose, Debug, RPCProto, + PerDevice, WorkTime, Normal + The output fields are (as above): + Silent=true/false, + Quiet=true/false, + Verbose=true/false, + Debug=true/false, + RPCProto=true/false, + PerDevice=true/false, + WorkTime=true/false| + + setconfig|name,N (*) + none There is no reply section just the STATUS section + stating the results of setting 'name' to N + The valid values for name are currently: + queue, scantime, expiry + N is an integer in the range 0 to 9999 + + usbstats USBSTATS Stats of all LIBUSB mining devices except ztex + e.g. Name=MMQ,ID=0,Stat=SendWork,Count=99,...| + + pgaset|N,opt[,val] (*) + none There is no reply section just the STATUS section + stating the results of setting PGA N with + opt[,val] + This is only available if PGA mining is enabled + + If the PGA does not support any set options, it + will always return a WARN stating pgaset isn't + supported + + If opt=help it will return an INFO status with a + help message about the options available + + The current options are: + MMQ opt=clock val=160 to 230 (a multiple of 2) + CMR opt=clock val=100 to 220 + + zero|Which,true/false (*) + none There is no reply section just the STATUS section + stating that the zero, and optional summary, was + done + If Which='all', all normal cgminer and API + statistics will be zeroed other than the numbers + displayed by the usbstats and stats commands + If Which='bestshare', only the 'Best Share' values + are zeroed for each pool and the global + 'Best Share' + The true/false option determines if a full summary + is shown on the cgminer display like is normally + displayed on exit. + + hotplug|N (*) none There is no reply section just the STATUS section + stating that the hotplug setting succeeded + If the code is not compiled with hotplug in it, + the the warning reply will be + 'Hotplug is not available' + If N=0 then hotplug will be disabled + If N>0 && <=9999, then hotplug will check for new + devices every N seconds + + asc|N ASC The details of a single ASC number N in the same + format and details as for DEVS + This is only available if ASC mining is enabled + Use 'asccount' or 'config' first to see if there + are any + + ascenable|N (*) + none There is no reply section just the STATUS section + stating the results of the enable request + You cannot enable a ASC if it's status is not WELL + This is only available if ASC mining is enabled + + ascdisable|N (*) + none There is no reply section just the STATUS section + stating the results of the disable request + This is only available if ASC mining is enabled + + ascidentify|N (*) + none There is no reply section just the STATUS section + stating the results of the identify request + This is only available if ASC mining is enabled + and currently only BFL ASICs support this command + On a BFL single it will flash the led on the front + of the device for appoximately 4s + All other non BFL ASIC devices will return a + warning status message stating that they dont + support it + + asccount ASCS Count=N| <- the number of ASCs + Always returns 0 if ASC mining is disabled + + ascset|N,opt[,val] (*) + none There is no reply section just the STATUS section + stating the results of setting ASC N with + opt[,val] + This is only available if ASC mining is enabled + + If the ASC does not support any set options, it + will always return a WARN stating ascset isn't + supported + + If opt=help it will return an INFO status with a + help message about the options available + + The current options are: + AVA+BTB opt=freq val=256 to 1024 - chip frequency + BTB opt=millivolts val=1000 to 1400 - corevoltage + MBA opt=reset val=0 to chipcount - reset a chip + BMA opt=volt val=0-9 opt=clock val=0-15 + MBA opt=freq val=0-chip:100-1400 - set chip freq + MBA opt=ledcount val=0-100 - chip count for led + MBA opt=ledlimit val=0-200 - led off below GHs + MBA opt=spidelay val=0-9999 - SPI per I/O delay + MBA opt=spireset i|s0-9999 - SPI regular reset + MBA opt=spisleep val=0-9999 - SPI reset sleep ms + + lcd LCD An all-in-one short status summary of the miner + e.g. Elapsed,GHS av,GHS 5m,GHS 5s,Temp, + Last Share Difficulty,Last Share Time, + Best Share,Last Valid Work,Found Blocks, + Pool,User| + + lockstats (*) none There is no reply section just the STATUS section + stating the results of the request + A warning reply means lock stats are not compiled + into cgminer + The API writes all the lock stats to stderr + +When you enable, disable or restart a PGA or ASC, you will also get +Thread messages in the cgminer status window + +The 'poolpriority' command can be used to reset the priority order of multiple +pools with a single command - 'switchpool' only sets a single pool to first +priority +Each pool should be listed by id number in order of preference (first = most +preferred) +Any pools not listed will be prioritised after the ones that are listed, in the +priority order they were originally +If the priority change affects the miner's preference for mining, it may switch +immediately + +When you switch to a different pool to the current one (including by priority +change), you will get a 'Switching to URL' message in the cgminer status +windows + +Obviously, the JSON format is simply just the names as given before the '=' +with the values after the '=' + +If you enable cgminer debug (-D or --debug) or, when cgminer debug is off, +turn on debug with the API command 'debug|debug' you will also get messages +showing some details of the requests received and the replies + +There are included 4 program examples for accessing the API: + +api-example.php - a php script to access the API + usAge: php api-example.php command + by default it sends a 'summary' request to the miner at 127.0.0.1:4028 + If you specify a command it will send that request instead + You must modify the line "$socket = getsock('127.0.0.1', 4028);" at the + beginning of "function request($cmd)" to change where it looks for cgminer + +api-example.rb - a Ruby script to access the API. + usage: ruby api-example.rb command[:parameter] [HOST [PORT]] +This script prints the parsed cgminer API response + +API.java/API.class + a java program to access the API (with source code) + usAge is: java API command address port + Any missing or blank parameters are replaced as if you entered: + java API summary 127.0.0.1 4028 + +api-example.c - a 'C' program to access the API (with source code) + usAge: api-example [command [ip/host [port]]] + again, as above, missing or blank parameters are replaced as if you entered: + api-example summary 127.0.0.1 4028 + +miner.php - an example web page to access the API + This includes buttons and inputs to attempt access to the privileged commands + See the end of this API-README for details of how to tune the display + and also to use the option to display a multi-rig summary + +---------- + +Feature Changelog for external applications using the API: + +--------- + +API V3.5 (cgminer v4.7.0) + +- Made quit and restart return valid JSON as a STATUS mirroring the reqest. +- Made addpool return what pool number the added pool is. + +--------- + +API V3.4 (cgminer v4.3.?) + +Added API commands: + 'lcd' - An all-in-one short status summary of the miner + +--------- + +API V3.3 (cgminer v4.2.0) + +Added API commands: + 'edevs' - Only enabled devices, for 'devs' + 'estats' - Only enabled devices, for 'stats' + +--------- + +API V3.2 (cgminer v4.1.0) + +Fix for: +HEX32 data type in the API version v3.1 JSON - since cgminer v3.12.1 - +returns an incorrect formatted json data element for the API stats command +for HashFast hardware + +--------- + +API V3.1 (cgminer v3.12.1) + +Multiple report request command with '+' e.g. summary+devs + +--------- + +API V3.0 (cgminer v3.11.0) + +Allow unlimited size replies + +--------- + +API V2.0 (cgminer v3.8.0) + +Removed all GPU related commands and information from the replies + +--------- + +API V1.32 (cgminer v3.6.5) + +Modified API commands: + 'devs' 'gpu' 'pga' and 'asc' - add 'Device Elapsed' + +--------- + +API V1.31 (cgminer v3.6.3) + +Added API command: + 'lockstats' - display cgminer dev lock stats if compiled in + +Modified API command: + 'summary' - add 'MHS %ds' (where %d is the log interval) + +--------- + +API V1.30 (cgminer v3.4.3) + +Added API command: + 'poolquota' - Set pool quota for load-balance strategy. + +Modified API command: + 'pools' - add 'Quota' + +--------- + +API V1.29 (cgminer v3.4.1) + +Muticast identification added to the API + +---------- + +API V1.28 (cgminer v3.3.4) + +Modified API commands: + 'devs', 'pga', 'asc', 'gpu' - add 'Device Hardware%' and 'Device Rejected%' + 'pools' - add 'Pool Rejected%' and 'Pool Stale%' + 'summary' - add 'Device Hardware%', 'Device Rejected%', 'Pool Rejected%', + 'Pool Stale%' + +---------- + +API V1.27 (cgminer v3.3.2) + +Added API commands: + 'ascset' - with: BTB opt=millivolts val=1000 to 1310 - core voltage + AVA+BTB opt=freq val=256 to 450 - chip frequency + +---------- + +API V1.26 (cgminer v3.2.3) + +Remove all CPU support (cgminer v3.0.0) + +Added API commands: + 'asc' + 'ascenable' + 'ascdisable' + 'ascidentify|N' (only works for BFL ASICs so far) + 'asccount' + +Various additions to the debug 'stats' command + +---------- + +API V1.25 + +Added API commands: + 'hotplug' + +Modified API commands: + 'devs' 'gpu' and 'pga' - add 'Last Valid Work' + 'devs' - list ASIC devices + 'config' - add 'Hotplug', 'ASC Count' + 'coin' - add 'Network Difficulty' + +---------- + +API V1.24 (cgminer v2.11.0) + +Added API commands: + 'zero' + +Modified API commands: + 'pools' - add 'Best Share' + 'devs' and 'pga' - add 'No Device' for PGAs if MMQ or BFL compiled + 'stats' - add pool: 'Net Bytes Sent', 'Net Bytes Recv' + +---------- + +API V1.23 (cgminer v2.10.2) + +Added API commands: + 'pgaset' - with: MMQ opt=clock val=160 to 230 (and a multiple of 2) + +---------- + +API V1.22 (cgminer v2.10.1) + +Enforced output limitation: + all extra records beyond the output limit of the API (~64k) are ignored + and chopped off at the record boundary before the limit is reached + however, JSON brackets will be correctly closed and the JSON id will be + set to 0 (instead of 1) if any data was truncated + +Modified API commands: + 'stats' - add 'Times Sent', 'Bytes Sent', 'Times Recv', 'Bytes Recv' + +---------- + +API V1.21 (cgminer v2.10.0) + +Added API commands: + 'usbstats' + +Modified API commands: + 'summary' - add 'Best Share' + +Modified output: + each MMQ shows up as 4 devices, each with it's own stats + +---------- + +API V1.20 (cgminer v2.8.5) + +Modified API commands: + 'pools' - add 'Has Stratum', 'Stratum Active', 'Stratum URL' + +---------- + +API V1.19 (cgminer v2.7.6) + +Added API commands: + 'debug' + 'pgaidentify|N' (only works for BFL Singles so far) + 'setconfig|name,N' + +Modified API commands: + 'devs' - add 'Diff1 Work', 'Difficulty Accepted', 'Difficulty Rejected', + 'Last Share Difficulty' to all devices + 'gpu|N' - add 'Diff1 Work', 'Difficulty Accepted', + 'Difficulty Rejected', 'Last Share Difficulty' + 'pga|N' - add 'Diff1 Work', 'Difficulty Accepted', + 'Difficulty Rejected', 'Last Share Difficulty' + 'notify' - add '*Dev Throttle' (for BFL Singles) + 'pools' - add 'Proxy Type', 'Proxy', 'Difficulty Accepted', + 'Difficulty Rejected', 'Difficulty Stale', + 'Last Share Difficulty' + 'config' - add 'Queue', 'Expiry' + 'stats' - add 'Work Diff', 'Min Diff', 'Max Diff', 'Min Diff Count', + 'Max Diff Count' to the pool stats + +---------- + +API V1.18 (cgminer v2.7.4) + +Modified API commands: + 'stats' - add 'Work Had Roll Time', 'Work Can Roll', 'Work Had Expire', + 'Work Roll Time' to the pool stats + 'config' - include 'ScanTime' + +---------- + +API V1.17 (cgminer v2.7.1) + +Added API commands: + 'coin' + +Modified API commands: + 'summary' - add 'Work Utility' + 'pools' - add 'Diff1 Shares' + +---------- + +API V1.16 (cgminer v2.6.5) + +Added API commands: + 'failover-only' + +Modified API commands: + 'config' - include failover-only state + +---------- + +API V1.15 (cgminer v2.6.1) + +Added API commands: + 'poolpriority' + +---------- + +API V1.14 (cgminer v2.5.0) + +Modified API commands: + 'stats' - more icarus timing stats added + 'notify' - include new device comms error counter + +The internal code for handling data was rewritten (~25% of the code) +Completely backward compatible + +---------- + +API V1.13 (cgminer v2.4.4) + +Added API commands: + 'check' + +Support was added to cgminer for API access groups with the --api-groups option +It's 100% backward compatible with previous --api-access commands + +---------- + +API V1.12 (cgminer v2.4.3) + +Modified API commands: + 'stats' - more pool stats added + +Support for the ModMinerQuad FPGA was added + +---------- + +API V1.11 (cgminer v2.4.2) + +Modified API commands: + 'save' no longer requires a filename (use default if not specified) + +'save' incorrectly returned status E (error) on success before. +It now correctly returns S (success) + +---------- + +API V1.10 (cgminer v2.4.1) + +Added API commands: + 'stats' + +N.B. the 'stats' command can change at any time so any specific content +present should not be relied upon. +The data content is mainly used for debugging purposes or hidden options +in cgminer and can change as development work requires + +Modified API commands: + 'pools' added "Last Share Time" + +---------- + +API V1.9 (cgminer v2.4.0) + +Added API commands: + 'restart' + +Modified API commands: + 'notify' corrected invalid JSON + +---------- + +API V1.8 (cgminer v2.3.5) + +Added API commands: + 'devdetails' + +Support for the ZTex FPGA was added + +---------- + +API V1.7 (cgminer v2.3.4) + +Added API commands: + 'removepool' + +Modified API commands: + 'pools' added "User" + +From API version 1.7 onwards, reply strings in JSON and Text have the +necessary escaping as required to avoid ambiguity +For JSON the 2 characters '"' and '\' are escaped with a '\' before them +For Text the 4 characters '|' ',' '=' and '\' are escaped the same way + +---------- + +API V1.6 (cgminer v2.3.2) + +Added API commands: + 'pga' + 'pgaenable' + 'pgadisable' + 'pgacount' + +Modified API commands: + 'devs' now includes Icarus and Bitforce FPGA devices + 'notify' added "*" to the front of the name of all numeric error fields + 'config' correct "Log Interval" to use numeric (not text) type for JSON + +Support for Icarus and Bitforce FPGAs was added + +---------- + +API V1.5 was not released + +---------- + +API V1.4 (Kano's interim release of cgminer v2.3.1) + +Added API commands: + 'notify' + +Modified API commands: + 'config' added "Device Code" and "OS" + +Added "When" to the STATUS reply section of all commands + +---------- + +API V1.3 (cgminer v2.3.1-2) + +Added API commands: + 'addpool' + +Modified API commands: + 'devs'/'gpu' added "Total MH" for each device + 'summary' added "Total MH" + +---------- + +API V1.2 (cgminer v2.3.0) + +Added API commands: + 'enablepool' + 'disablepool' + 'privileged' + +Modified API commands: + 'config' added "Log Interval" + +Starting with API V1.2, any attempt to access a command that requires +privileged security, from an IP address that does not have privileged +security, will return an "Access denied" Error Status + +---------- + +API V1.1 (cgminer v2.2.4) + +There were no changes to the API commands in cgminer v2.2.4, +however support was added to cgminer for IP address restrictions +with the --api-allow option + +---------- + +API V1.1 (cgminer v2.2.2) + +Prior to V1.1, devs/gpu incorrectly reported GPU0 Intensity for all GPUs + +Modified API commands: + 'devs'/'gpu' added "Last Share Pool" and "Last Share Time" for each device + +---------- + +API V1.0 (cgminer v2.2.0) + +Remove default CPU support + +Added API commands: + 'config' + 'gpucount' + 'cpucount' + 'switchpool' + 'gpuintensity' + 'gpumem' + 'gpuengine' + 'gpufan' + 'gpuvddc' + 'save' + +---------- + +API V0.7 (cgminer v2.1.0) + +Initial release of the API in the main cgminer git + +Commands: + 'version' + 'devs' + 'pools' + 'summary' + 'gpuenable' + 'gpudisable' + 'gpurestart' + 'gpu' + 'cpu' + 'gpucount' + 'cpucount' + 'quit' + +---------------------------------------- + +miner.php +========= + +miner.php is a PHP based interface to the cgminer RPC API +(referred to simply as the API below) + +It can show rig details, summaries and input fields to allow you to change +cgminer +You can also create custom summary pages with it + +It has two levels to the security: +1) cgminer can be configured to allow or disallow API access and access level + security for miner.php +2) miner.php can be configured to allow or disallow privileged cgminer + access, if cgminer is configured to allow privileged access for miner.php + +--------- + +To use miner.php requires a web server with PHP + +Basics: On xubuntu 11.04, to install apache2 and php, the commands are: + sudo apt-get install apache2 + sudo apt-get install php5 + sudo /etc/init.d/apache2 reload + +On Fedora 17: + yum install httpd php + systemctl restart httpd.service + systemctl enable httpd.service --system + +On windows there are a few options. +Try one of these (apparently the first one is easiest - thanks jborkl) + http://www.easyphp.org/ + http://www.apachefriends.org/en/xampp.html + http://www.wampserver.com/en/ + +--------- + +The basic cgminer option to enable the API is: + + --api-listen + +or in your cgminer.conf + + "api-listen" : true, + +(without the ',' on the end if it is the last item) + +If the web server is running on the cgminer computer, the above +is the only change required to give miner.php basic access to +the cgminer API + +- + +If the web server runs on a different computer to cgminer, +you will also need to tell cgminer to allow the web server +to access cgminer's API and tell miner.php where cgminer is + +Assuming a.b.c.d is the IP address of the web server, you +would add the following to cgminer: + + --api-listen --api-allow a.b.c.d + +or in your cgminer.conf + + "api-listen" : true, + "api-allow" : "a.b.c.d", + +to tell cgminer to give the web server read access to the API + +You also need to tell miner.php where cgminer is. +Assuming cgminer is at IP address e.f.g.h, then you would +edit miner.php and change the line + + $rigs = array('127.0.0.1:4028'); + +to + + $rigs = array('e.f.g.h:4028'); + +See --api-network or --api-allow for more access details +and how to give write access + +You can however, also tell miner.php to find your cgminer rigs automatically +on the local subnet + +Add the following to each cgminer: + + --api-mcast + +or in your cgminer.conf + + "api-mcast" : true, + +And in miner.php set $mcast = true; + +This will ignore the value of $rigs and overwrite it with the list of zero or +more rigs found on the network in the timeout specified +A rig will not reply if the API settings would mean it would also ignore an +API request from the web server running miner.php + +--------- + +Once you have a web server with PHP running + + copy your miner.php to the main web folder + +On Xubuntu 11.04 + /var/www/ + +On Fedora 17 + /var/www/html/ + +On Windows + see your windows Web/PHP documentation + +Assuming the IP address of the web server is a.b.c.d +Then in your web browser go to: + + http://a.b.c.d/miner.php + +Done :) + +--------- + +The rest of this documentation deals with the more complex +functions of miner.php, using myminer.php, creaing custom +summaries and displaying multiple cgminer rigs + +--------- + +If you create a file called myminer.php in the same web folder +where you put miner.php, miner.php will load it when it runs + +This is useful, to put any changes you need to make to miner.php +instead of changing miner.php +Thus if you update/get a new miner.php, you won't lose the changes +you have made if you put all your changes in myminer.php +(and don't change miner.php at all) + +A simple example myminer.php that defines 2 rigs +(that I will keep referring to further below) is: + + + +Changes in myminer.php superscede what is in miner.php +However, this is only valid for variables in miner.php before the +2 lines where myminer.php is included by miner.php: + + if (file_exists('myminer.php')) + include_once('myminer.php'); + +Every variable in miner.php above those 2 lines, can be changed by +simply defining them in your myminer.php + +So although miner.php originally contains the line + + $rigs = array('127.0.0.1:4028'); + +if you created the example myminer.php given above, it would actually +change the value of $rigs that is used when miner.php is running +i.e. you don't have to remove or comment out the $rigs line in miner.php +It will be superceded by myminer.php + +--------- + +The example myminer.php above also shows how to define more that one rig +to be shown my miner.php + +Each rig string is 2 or 3 values seperated by colons ':' +They are simply an IP address or host name, followed by the +port number (usually 4028) and an optional Name string + +miner.php displays rig buttons that will show the defails of a single +rig when you click on it - the button shows either the rig number, +or the 'Name' string if you provide it + +PHP arrays contain each string seperated by a comma, but no comma after +the last one + +So an example for 3 rigs would be: + + $rigs = array('192.168.0.100:4028:A', '192.168.0.102:4028:B', + '192.168.0.110:4028:C'); + +Of course each of the rigs listed would also have to have the API +running and be set to allow the web server to access the API - as +explained before + +--------- + +So basically, any variable explained below can be put in myminer.php +if you wanted to set it to something different to it's default value +and did not want to change miner.php itself every time you updated it + +Below is each variable that can be changed and an explanation of each + +--------- + +Default: + $dfmt = 'H:i:s j-M-Y \U\T\CP'; + +Define the date format used to print full length dates +If you get the string 'UTCP' on the end of your dates shown, that +means you are using an older version of PHP and you can instead use: + $dfmt = 'H:i:s j-M-Y \U\T\CO'; + +The PHP documentation on the date format is here: + http://us.php.net/manual/en/function.date.php + +--------- + +Default: + $title = 'Mine'; + +Web page title +If you know PHP you can of course use code to define it e.g. + $title = 'My Rig at: '.date($dfmt); + +Which would set the web page title to something like: + My Rig at: 10:34:00 22-Aug-2012 UTC+10:00 + +--------- + +Default: + $readonly = false; + +Set $readonly to true to force miner.php to be readonly +This means it won't allow you to change cgminer even if the cgminer API +options allow it to + +If you set $readonly to false then it will check cgminer 'privileged' +and will show input fields and buttons on the single rig page +allowing you to change devices, pools and even quit or restart +cgminer + +However, if the 'privileged' test fails, the code will set $readonly to +true + +--------- + +Default: + $userlist = null; + +Define password checking and default access + null means there is no password checking + +$userlist is an array of 3 arrays e.g. +$userlist = array('sys' => array('boss' => 'bpass'), + 'usr' => array('user' => 'upass', 'pleb' => 'ppass'), + 'def' => array('Pools')); + +'sys' is an array of system users and passwords (full access) +'usr' is an array of user level users and passwords (readonly access) +'def' is an array of custompages that anyone not logged in can view + +Any of the 3 can be null, meaning there are none of that item + +All validated 'usr' users are given $readonly = true; access +All validated 'sys' users are given the $readonly access you defined + +If 'def' has one or more values, and allowcustompages is true, then +anyone without a password can see the list of custompage buttons given +in 'def' and will see the first one when they go to the web page, with +a login button at the top right + +From the login page, if you login with no username or password, it will +show the first 'def' custompage (if there are any) + +If you are logged in, it will show a logout button at the top right + +--------- + +Default: + $notify = true; + +Set $notify to false to NOT attempt to display the notify command +table of data + +Set $notify to true to attempt to display the notify command on +the single rig page +If your older version of cgminer returns an 'Invalid command' +coz it doesn't have notify - it just shows the error status table + +--------- + +Default: + $checklastshare = true; + +Set $checklastshare to true to do the following checks: +If a device's last share is 12x expected ago then display as an error +If a device's last share is 8x expected ago then display as a warning +If either of the above is true, also display the whole line highlighted +This assumes shares are 1 difficulty shares + +Set $checklastshare to false to not do the above checks + +'expected' is calculated from the device MH/s value +So for example, a device that hashes at 380MH/s should (on average) +find a share every 11.3s +If the last share was found more than 11.3 x 12 seconds (135.6s) ago, +it is considered an error and highlighted +If the last share was found more than 11.3 x 8 seconds (90.4s) ago, +it is considered a warning and highlighted + +The default highlighting is very subtle + +--------- + +Default: + $poolinputs = false; + +Set $poolinputs to true to show the input fields for adding a pool +and changing the pool priorities on a single rig page +However, if $readonly is true, it will not display them + +--------- + +Default: + $rigport = 4028; + +Default port to use if any $rigs entries don't specify the port number + +--------- + +Default: + $rigs = array('127.0.0.1:4028'); + +Set $rigs to an array of your cgminer rigs that are running + format: 'IP' or 'Host' or 'IP:Port' or 'Host:Port' or 'Host:Port:Name' +If you only have one rig, it will just show the detail of that rig +If you have more than one rig it will show a summary of all the rigs + with buttons to show the details of each rig - + the button contents will be 'Name' rather than rig number, if you + specify 'Name' +If Port is missing or blank, it will try $rigport +e.g. $rigs = array('127.0.0.1:4028','myrig.com:4028:Sugoi'); + +--------- + +Default: + $rignames = false; + +Set $rignames to false to not affect the display. +Set $rignames to one of 'ip' or 'ipx' to alter the name displayed +if the rig doesn't have a 'name' in $rigs +Currently: + 'ip' means use the 4th byte of the rig IP address as an integer + 'ipx' means use the 4th byte of the rig IP address as 2 hex bytes + +--------- + +Default: + $rigbuttons = true; + +Set $rigbuttons to false to display a link rather than a button on +the left of any summary table with rig buttons, in order to reduce +the height of the table cells + +--------- + +Default: + $mcast = false; + +Set $mcast to true to look for your rigs and ignore $rigs + +--------- + +Default: + $mcastexpect = 0; + +The minimum number of rigs expected to be found when $mcast is true +If fewer are found, an error will be included at the top of the page + +--------- + +Default: + $mcastaddr = '224.0.0.75'; + +API Multicast address all cgminers are listening on + +--------- + +Default: + $mcastport = 4028; + +API Multicast UDP port all cgminers are listening on + +--------- + +Default: + $mcastcode = 'FTW'; + +The code all cgminers expect in the Multicast message sent +The message sent is "cgm-code-listport" +Don't use the '-' character if you change it + +--------- + +Default: + $mcastlistport = 4027; + +UDP port number that is added to the broadcast message sent +that specifies to the cgminers the port to reply on + +--------- + +Default: + $mcasttimeout = 1.5; + +Set $mcasttimeout to the number of seconds (floating point) +to wait for replies to the Multicast message +N.B. the accuracy of the timing used to wait for the replies is +~0.1s so there's no point making it more than one decimal place + +--------- + +Default: + $mcastretries = 0; + +Set $mcastretries to the number of times to retry the multicast + +If $mcastexpect is 0, this is simply the number of extra times +that it will send the multicast request +N.B. cgminer doesn't listen for multicast requests for 1000ms after +each one it hears + +If $mcastexpect is > 0, it will stop looking for replies once it +has found at least $mcastexpect rigs, but it only checks this rig +limit each time it reaches the $mcasttimeout limit, thus it can find +more than $mcastexpect rigs if more exist +It will send the multicast message up to $mcastretries extra times or +until it has found at least $mcastexpect rigs +However, when using $mcastretries, it is possible for it to sometimes +ignore some rigs on the network if $mcastexpect is less than the +number of rigs on the network and some rigs are too slow to reply + +--------- + +Default: + $allowgen = false; + +Set $allowgen to true to allow customsummarypages to use 'gen' and 'bgen' +false means ignore any 'gen' or 'bgen' options +This is disabled by default due to the possible security risk of using it +See the end of this document for an explanation + +--------- + +Default: + $rigipsecurity = true; + +Set $rigipsecurity to false to show the IP/Port of the rig +in the socket error messages and also show the full socket message + +--------- + +Default: + $rigtotals = true; + $forcerigtotals = false; + +Set $rigtotals to true to display totals on the single rig page +'false' means no totals (and ignores $forcerigtotals) + +If $rigtotals is true, all data is also right aligned +With false, it's as before, left aligned + +This option is just here to allow people to set it to false +if they prefer the old non-total display when viewing a single rig + +Also, if there is only one line shown in any section, then no +total will be shown (to save screen space) +You can force it to always show rig totals on the single rig page, +even if there is only one line, by setting $forcerigtotals = true; + +--------- + +Default: + $socksndtimeoutsec = 10; + $sockrcvtimeoutsec = 40; + +The numbers are integer seconds + +The defaults should be OK for most cases +However, the longer SND is, the longer you have to wait while +php hangs if the target cgminer isn't runnning or listening + +RCV should only ever be relevant if cgminer has hung but the +API thread is still running, RCV would normally be >= SND + +Feel free to increase SND if your network is very slow +or decrease RCV if that happens often to you + +Also, on some windows PHP, apparently the $usec is ignored +(so usec can't be specified) + +--------- + +Default: + $hidefields = array(); + +List of fields NOT to be displayed +You can use this to hide data you don't want to see or don't want +shown on a public web page +The list of sections are: + SUMMARY, POOL, PGA, GPU, NOTIFY, CONFIG, DEVDETAILS, DEVS +See the web page for the list of field names (the table headers) +It is an array of 'SECTION.Field Name' => 1 + +This example would hide the slightly more sensitive pool information: +Pool URL and pool username: + $hidefields = array('POOL.URL' => 1, 'POOL.User' => 1); + +If you just want to hide the pool username: + $hidefields = array('POOL.User' => 1); + +--------- + +Default: + $ignorerefresh = false; + $changerefresh = true; + $autorefresh = 0; + +Auto-refresh of the page (in seconds) - integers only + +$ignorerefresh = true/false always ignore refresh parameters +$changerefresh = true/false show buttons to change the value +$autorefresh = default value, 0 means dont auto-refresh + +--------- + +Default: + $miner_font_family = 'verdana,arial,sans'; + $miner_font_size = '13pt'; + +Change these to set the font and font size used on the web page + +--------- + +Default: + $add_css_names = array(); + +List of CSS names to add to the CSS style object + e.g. array('td.cool' => false); +true/false to not include the default $miner_font +The CSS name/value pairs must be defined in $colouroverride below + +This allows you to create multiple complete CSS styles, optionally +using a different font to the default used/specified for all other +styles, and then when using the class name in a custom formatting +function (fmt) in a customsummarypage, it can use this style + +--------- + +Default: + $colouroverride = array(); + +Use this to change the web page colour scheme + +See $colourtable in miner.php for the list of possible names to change + +Simply put in $colouroverride, just the colours you wish to change + +e.g. to change the colour of the header font and background +you could do the following: + + $colouroverride = array( + 'td.h color' => 'green', + 'td.h background' => 'blue' + ); + +You can also add your own CSS styles to be used by a customsummarypage +custom format function, if you specify the class name in $add_css_names +and put the class styles in $colouroverride + +--------- + +Default: + $placebuttons = 'top'; + +Where to place the Refresh, Summary, Custom Pages, Quit, etc. buttons + +Valid values are: 'top' 'bot' 'both' + anything else means don't show them - case sensitive + +--------- + +Default: + $allowcustompages = true; + +Should we allow custom pages? +(or just completely ignore them and don't display the buttons) + +--------- + +OK this part is more complex: Custom Summary Pages + +A custom summary page in an array of 'section' => array('FieldA','FieldB'...) + +The section defines what data you want in the summary table and the Fields +define what data you want shown from that section + +Standard sections are: + SUMMARY, POOL, PGA, GPU, NOTIFY, CONFIG, DEVDETAILS, DEVS, EDEVS, STATS, + ESTATS, COIN + +Fields are the names as shown on the headers on the normal pages + +There is a special field name '#' that will total to the number of rows +displayed in the custom summary page +In the actual row output it is a row counter per rig + +Fields can be 'name=new name' to display 'name' with a different heading +'new name' + +There are also now joined sections: + SUMMARY+POOL, SUMMARY+DEVS, SUMMARY+EDEVS, DEVS+STATS, EDEVS+ESTATS, + POOL+STATS plus many more +See the miner.php function joinsections() for the full list + +These sections are an SQL join of the two sections and the fields in them +are named section.field where section. is the section the field comes from +See the example further down + +Also note: +- empty tables are not shown +- empty columns (e.g. an unknown field) are not shown +- missing field data shows as blank +- the field name '*' matches all fields except in joined sections + (useful for STATS and COIN) + +There are 2 hard coded sections: + DATE - displays a date table like at the start of 'Summary' + RIGS - displays a rig table like at the start of 'Summary' + +Each custom summary requires a second array, that can be empty, listing fields +to be totaled for each section +If there is no matching total data, no total will show + +--------- + +Looking at the Mobile example: + + $mobilepage = array( + 'DATE' => null, + 'RIGS' => null, + 'SUMMARY' => array('Elapsed', 'MHS av', 'Found Blocks=Blks', + Accepted', 'Rejected=Rej', 'Utility'), + 'DEVS+NOTIFY' => array('DEVS.Name=Name', 'DEVS.ID=ID', 'DEVS.Status=Status', + 'DEVS.Temperature=Temp', 'DEVS.MHS av=MHS av', + 'DEVS.Accepted=Accept', 'DEVS.Rejected=Rej', + 'DEVS.Utility=Utility', 'NOTIFY.Last Not Well=Not Well'), + 'POOL' => array('POOL', 'Status', 'Accepted', 'Rejected=Rej', + 'Last Share Time')); + + $mobilesum = array( + 'SUMMARY' => array('MHS av', 'Found Blocks', 'Accepted', 'Rejected', + 'Utility'), + 'DEVS+NOTIFY' => array('DEVS.MHS av', 'DEVS.Accepted', 'DEVS.Rejected', + 'DEVS.Utility'), + 'POOL' => array('Accepted', 'Rejected')); + + $customsummarypages = array('Mobile' => array($mobilepage, $mobilesum)); + +This will show 5 tables (according to $mobilepage) +Each table will have the chosen details for all the rigs specified in $rigs + + DATE + A single box with the web server's current date and time + + RIGS + A table of the rigs: description, time, versions etc + + SUMMARY + + This will use the API 'summary' command and show the selected fields: + Elapsed, MHS av, Found Blocks, Accepted, Rejected and Utility + However, 'Rejected=Rej' means that the header displayed for the 'Rejected' + field will be 'Rej', instead of 'Rejected' (to save space) + Same for 'Found Blocks=Blks' - to save space + + DEVS+NOTIFY + + This will list each of the devices on each rig and display the list of + fields as shown + It will also include the 'Last Not Well' field from the 'notify' command + so you know when the device was last not well + + You will notice that you need to rename each field e.g. 'DEVS.Name=Name' + since each field name in the join between DEVS and NOTIFY is actually + section.fieldname, not just fieldname + + The join code automatically adds 2 fields to each GPU device: 'Name' and 'ID' + They don't exist in the API 'devs' output but I can correctly calculate + them from the GPU device data + These two fields are used to join DEVS to NOTIFY i.e. find the NOTIFY + record that has the same Name and ID as the DEVS record and join them + + POOL + + This will use the API 'pools' command and show the selected fields: + POOL, Status, Accepted, Rejected, Last Share Time + Again, I renamed the 'Rejected' field using 'Rejected=Rej', to save space + +$mobilesum lists the sections and fields that should have a total +You can't define them for 'DATE' or 'RIGS' since they are hard coded tables +The example given: + + SUMMARY + Show a total at the bottom of the columns for: + MHS av, Found Blocks, Accepted, Rejected, Utility + + Firstly note that you use the original name i.e. for 'Rejected=Rej' + you use 'Rejected', not 'Rej' and not 'Rejected=Rej' + + Secondly note that it simply adds up the fields + If you ask for a total of a string field you will get the numerical + sum of the string data + + DEVS+NOTIFY + + Simply note in this join example that you must use the original field + names which are section.fieldname, not just fieldname + + POOL + Show a total at the bottom of the columns for: + Accepted and Rejected + + Again remember to use the original field name 'Rejected' + +--------- + +With cgminer 2.10.2 and later, miner.php includes an extension to +the custom pages that allows you to apply SQL style commands to +the data: where, group, and having +cgminer 3.4.2 and later also includes another option 'gen' +cgminer 4.2.0 and later also includes another option 'fmt' +cgminer 4.2.1 and later also includes another option 'bgen' + +An example of an 'ext' section in a more complex custom summary page: + +$poolsext = array( + 'POOL+STATS' => array( + 'where' => null, + 'group' => array('POOL.URL', 'POOL.Has Stratum', + 'POOL.Stratum Active', 'POOL.Has GBT'), + 'calc' => array('POOL.Difficulty Accepted' => 'sum', + 'POOL.Difficulty Rejected' => 'sum', + 'STATS.Times Sent' => 'sum', + 'STATS.Bytes Sent' => 'sum', + 'STATS.Times Recv' => 'sum', + 'STATS.Bytes Recv' => 'sum'), + 'gen' => array('AvShr', 'POOL.Difficulty Accepted/max(POOL.Accepted,1)), + 'having' => array(array('STATS.Bytes Recv', '>', 0)), + 'fmt' => 'myfmtfunc')); + +function myfmtfunc($section, $name, $value, $when, $alldata, + $warnclass, $errorclass, $hiclass, $loclass, $totclass); +{ + $ret = ''; + $class = ''; + switch ($section.'.'.$name) + { + case 'GEN.AvShr': + $ret = number_format((float)$value, 2); + if ($value == 0) + $class = $errorclass; + break; + // Nonsence example :) since total would show the sum of the averages + case 'total.AvShr': + $ret = $value; + if ($value == 0) + $class = $warnclass; + break; + } + return array($ret, $class); +} + +This allows you to group records together from one or more rigs +In the example, you'll get each Pool (with the same URL+Stratum+GBT settings) +listed once for all rigs and a sum of each of the fields listed in 'calc' + + +'where' and 'having' are an array of fields and restrictions to apply + +In the above example, it will only display the rows where it contains the +'STATS.Bytes Recv' field with a value greater than zero +If the row doesn't have the field, it will always be included +All restrictions must be true in order for the row to be included +Any restiction that is invalid or unknown is true +An empty array, or null, means there are no restrictions + +A restriction is formatted as: array('Field', 'restriction', 'value') +Field is the simple field name as normally displayed, or SECTION.Field +if it is a joined section (as in this case 'POOL+STATS') +The list of restrictions are: +'set' - true if the row contains the 'Field' ('value' is not required or used) +'=', '<', '<=', '>', '>' - a numerical comparison +'eq', 'lt', 'le', 'gt', 'ge' - a case insensitive string comparison + +You can have multiple restrictions on a 'Field' - but all must be true to +include the row containing the 'Field' +e.g. a number range between 0 and 10 would be: +array('STATS.Bytes Recv', '>', 0), array('STATS.Bytes Recv', '<', 10) + +The difference between 'where' and 'having' is that 'where' is applied to the +data before grouping it and 'having' is applied to the data after grouping it +- otherwise they work the same + + +'group' lists the fields to group over and 'calc' lists the function to apply +to other fields that are not part of 'group' + +You can only see fields listed in 'group' and 'calc' + +A 'calc' is formatted as: 'Field' => 'function' +The current list of operations available for 'calc' are: +'sum', 'avg', 'min', 'max', 'lo', 'hi', 'count', 'any' +The first 4 are as expected - the numerical sum, average, minimum or maximum +'lo' is the first string of the list, sorted ignoring case +'hi' is the last string of the list, sorted ignoring case +'count' is the number of rows in the section specified in the calc e.g. + ('DEVS.Name' => 'count') would be the number of DEVS selected in the 'where' + of course any valid 'DEVS.Xyz' would give the same 'count' value +'any' is effectively random: the field value in the 1st row of the grouped data +An unrecognised 'function' uses 'any' + + +A 'fmt' allows you to specify a function to be called by miner.php to format +data to be displayed in the output html +If the function doesn't exist in miner.php or myminer.php, then it will be +ignored +If the function returns a $ret value (see the example 'myfmtfunc' above) then +that will be displayed, however if $ret is empty, then the normal formatting +code will process the data to be displayed +Thus, if there is no formatting code in miner.php for the field value, then it +will be displayed as it was received from the API +i.e. this allows you to either supply some php code to format field values +that are not formatted by miner.php, or you can also override the formatting +done by miner.php itself for your chosen list of field data +You can return an ' ' if you wish to force it to display as blank +Use the example 'myfmtfunc' above as a template to write your own +Note that your provided function will be called for all data being displayed, +so you should use the 'case' layout as in the example to select the data fields +you wish to format, but return '' for fields you don't wish to change the way +they are formatted +The 2nd return field is the name of a CSS class in $colourtable or created in +your own $add_css_names and $colouroverride +The value you return can stay in effect even if you return an empty $ret, if +the default formatting function for the field doesn't set the $class variable +The fields passed to your function by miner.php: + $warnclass, $errorclass, $hiclass, $loclass, $totclass +contain the default class names used for formatting + + +A 'gen' or 'bgen' allows you to generate new fields from any php valid function +of any of the other fields + e.g. 'gen' => array('AvShr', 'POOL.Difficulty Accepted/max(POOL.Accepted,1)), +will generate a new field called GEN.AvShr that is the function shown, which +in this case is the average difficulty of each share submitted + +The difference between 'bgen' and 'gen' is that 'bgen' is done before doing +the 'group' and 'calc', however 'gen' is done after doing 'group' and 'calc' +This means that 'group' and 'calc' can also use 'bgen' fields +As before, 'gen' fields act on the results of the 'group' and 'calc' +If there is no 'group' or 'calc' then they both will produce the same results +Note that 'gen' fields are called 'GEN.field' and 'bgen' fields, 'BGEN.field' + +THERE IS A SECURITY RISK WITH HOW GEN/BGEN WORKS +It simply replaces all the variables with their values and then requests PHP +to execute the formula - thus if a field value returned from a cgminer API +request contained PHP code, it could be executed by your web server +Of course cgminer doesn't do this, but if you do not control the cgminer that +returns the data in the API calls, someone could modify cgminer to return a +PHP string in a field you use in 'gen' or 'bgen' +Thus use 'gen' and 'bgen' at your own risk +If someone feels the urge to write a mathematical interpreter in PHP to get +around this risk, feel free to write one and submit it to the API author for +consideration diff --git a/API.class b/API.class new file mode 100644 index 0000000000000000000000000000000000000000..0fdf7342d8fdab5cd6c75bb10b556419b0a42d6c GIT binary patch literal 3772 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 literal 0 HcmV?d00001 diff --git a/API.java b/API.java new file mode 100644 index 0000000..8ba0991 --- /dev/null +++ b/API.java @@ -0,0 +1,166 @@ +/* + * + * Copyright (C) Andrew Smith 2012-2013 + * + * Usage: java API command ip port + * + * If any are missing or blank they use the defaults: + * + * command = 'summary' + * ip = '127.0.0.1' + * port = '4028' + * + */ + +import java.net.*; +import java.io.*; + +class API +{ + static private final int MAXRECEIVESIZE = 65535; + + static private Socket socket = null; + + private void closeAll() throws Exception + { + if (socket != null) + { + socket.close(); + socket = null; + } + } + + public void display(String result) throws Exception + { + String value; + String name; + String[] sections = result.split("\\|", 0); + + for (int i = 0; i < sections.length; i++) + { + if (sections[i].trim().length() > 0) + { + String[] data = sections[i].split(",", 0); + + for (int j = 0; j < data.length; j++) + { + String[] nameval = data[j].split("=", 2); + + if (j == 0) + { + if (nameval.length > 1 + && Character.isDigit(nameval[1].charAt(0))) + name = nameval[0] + nameval[1]; + else + name = nameval[0]; + + System.out.println("[" + name + "] =>"); + System.out.println("("); + } + + if (nameval.length > 1) + { + name = nameval[0]; + value = nameval[1]; + } + else + { + name = "" + j; + value = nameval[0]; + } + + System.out.println(" ["+name+"] => "+value); + } + System.out.println(")"); + } + } + } + + public void process(String cmd, InetAddress ip, int port) throws Exception + { + StringBuffer sb = new StringBuffer(); + char buf[] = new char[MAXRECEIVESIZE]; + int len = 0; + +System.out.println("Attempting to send '"+cmd+"' to "+ip.getHostAddress()+":"+port); + + try + { + socket = new Socket(ip, port); + PrintStream ps = new PrintStream(socket.getOutputStream()); + ps.print(cmd.toCharArray()); + ps.flush(); + + InputStreamReader isr = new InputStreamReader(socket.getInputStream()); + while (0x80085 > 0) + { + len = isr.read(buf, 0, MAXRECEIVESIZE); + if (len < 1) + break; + sb.append(buf, 0, len); + if (buf[len-1] == '\0') + break; + } + + closeAll(); + } + catch (IOException ioe) + { + System.err.println(ioe.toString()); + closeAll(); + return; + } + + String result = sb.toString(); + + System.out.println("Answer='"+result+"'"); + + display(result); + } + + public API(String command, String _ip, String _port) throws Exception + { + InetAddress ip; + int port; + + try + { + ip = InetAddress.getByName(_ip); + } + catch (UnknownHostException uhe) + { + System.err.println("Unknown host " + _ip + ": " + uhe); + return; + } + + try + { + port = Integer.parseInt(_port); + } + catch (NumberFormatException nfe) + { + System.err.println("Invalid port " + _port + ": " + nfe); + return; + } + + process(command, ip, port); + } + + public static void main(String[] params) throws Exception + { + String command = "summary"; + String ip = "127.0.0.1"; + String port = "4028"; + + if (params.length > 0 && params[0].trim().length() > 0) + command = params[0].trim(); + + if (params.length > 1 && params[1].trim().length() > 0) + ip = params[1].trim(); + + if (params.length > 2 && params[2].trim().length() > 0) + port = params[2].trim(); + + new API(command, ip, port); + } +} diff --git a/ASIC-README b/ASIC-README new file mode 100644 index 0000000..f290032 --- /dev/null +++ b/ASIC-README @@ -0,0 +1,763 @@ +SUPPORTED DEVICES + +Currently supported devices include: +- Antminer U1/U2/U2+/U3 USB +- Antminer S1 +- ASICMINER block erupters +- ASICMINER Tube/Prisma +- Avalon (including BitBurner and Klondike) +- Avalon2/3 +- Avalon4 +- BFx2 USB +- Butterfly Labs SC 65/28nm range +- BF1 (bitfury) USB (red and blue) +- BlackArrow Bitfury +- BlackArrow Minion +- Bi*fury USB +- Cointerra +- Hashfast Babyjet and Sierra +- Hashratio +- Hexfury USB +- KnCminer Mercury, Saturn and Jupiter +- Nanofury USB +- Other bitfury USB devices +- Onestring miner USB +- Rockminer R-Box/RK-Box/T1/New R-Box +- Spondoolies SP10, SP30 + + +No COM ports on windows or TTY devices will be used by cgminer as it +communicates directly with them via USB so it is normal for them to not exist or +be disconnected when cgminer is running. + +The BFL devices should come up as one of the following: + +BAJ: BFL ASIC Jalapeño +BAL: BFL ASIC Little Single +BAS: BFL ASIC Single +BAM: BFL ASIC Minirig +BMA: BFL Monarch + +BFL devices need the --enable-bflsc option when compiling cgminer yourself. + +Avalon will come up as AVA. + +Avalon devices need the --enable-avalon option when compiling cgminer. + +Avalon2/3 will come up as AV2. + +Avalon2/3 devices need the --enable-avalon2 option when compiling cgminer. + +Avalon4 will come up as AV4. + +Avalon4 devies need the --enable-avalon4 option when compiling cgminer. + +Klondike will come up as KLN. + +Klondike devices need the --enable-klondike option when compiling cgminer. + +ASICMINER block erupters will come up as AMU. + +ASICMINER devices need the --enable-icarus option when compiling cgminer. +Also note that the AMU is managed by the Icarus driver which is detailed +in the FPGA-README. Configuring them uses the same mechanism as outlined +below for getting started with USB ASICs. + +ASICMINER BlockErupter Tube/Prisma will come up as BET. + +ASICMINER Tube/Prisma devices need the --enable-blockerupter option when +compiling cgminer. + +BlackArrow Bitfury devices + +BlackArrow Bitfury devices need the --enable-bab option when compiling cgminer. + +The current BlackArrow Bitfury devices are similar to the Bitfury GPIO mining +boards, with both V1 and V2 controllers, and come up as BaB. + + +BlackArrow Minion devices + +BlackArrow Minion devices need the --enable-minion option when compiling +cgminer. + +BlackArrow Minion devices are SPI/GPIO mining devices and come up as MBA + + +BITFURY devices + +Bitfury devices need the --enable-bitfury option when compiling cgminer. + +Currently the BPMC/BGMC BF1 devices AKA redfury/bluefury are supported and +come up as BF1, along with the Bi*fury USB devices which come up as BXF. +Nanofury devices come up as NF1. BFx2 devices come up as BXM. + +Bitfury USB devices are also set up as per the USB ASICs below. + + +COINTERRA devices + +Cointerra devices need the --enable-cointerra option when compiling cgminer. + +Cointerra devices come up as CTA devices and currently take only hidden command +line arguments for power settings. + +Cointerra USB devices are set up as per the USB ASIC instructions below. + + +HASHFAST devices + +Hashfast devices need the --enable-hashfast option when compiling cgminer. + +All current HFA devices are supported and are recognised with the name HFA +in the --usb commands. After initialisation, cgminer will determine what type +they are and give them the following names: + +HFB: Hashfast Babyjet +HFS: Hashfast Sierra +HFA: Hashfast non standard (eg. a Babyjet with an added board, Habanero) + + +HASHRATIO devices + +Hashratio devices need the --enable-hashratio option when compiling cgminer. + + +ANTMINER U1/U2+/U3 devices + +Antminer devices need the --enable-icarus option when compiling cgminer. + +Currently the U1/2/3 USB sticks are supported and come up as the following +devices: + +ANU: Antminer U1/U2/U2+ +AU3: Antminer U3 + +They are also set up as per the USB ASICs below. + +ANTMINER S1 devices + +Antminer S1 devices need the --enable-ants1 option when compiling cgminer. + +They are custom OpenWRT linux devices + +They are recognised with the name ANT + + +BITMINE A1 devices + +Bitmine A1 devices need the --enable-bitmine_A1 compile option set. + + +Rockminer R*Box + +Rockminer R*Box devices need the --enable-icarus compile option set. + +They appear with the following names: +LIN: R-Box +LIR: New R-Box + +--- +GETTING STARTED WITH USB ASICS + +Unlike other software, cgminer uses direct USB communication instead of the +ancient serial USB communication to be much faster, more reliable and use a +lot less CPU. For this reason, setting up for mining with cgminer on these +devices requires different drivers. + + +WINDOWS: + +On windows, the direct USB support requires the installation of a WinUSB +driver (NOT the ftdi_sio driver), and attach it to the chosen USB device. +When configuring your device, plug it in and wait for windows to attempt to +install a driver on its own. It may think it has succeeded or failed but wait +for it to finish regardless. This is NOT the driver you want installed. At this +point you need to associate your device with the WinUSB driver. The easiest +way to do this is to use the zadig utility which you must right click on and +run as administrator. Then once you plug in your device you can choose the +"list all devices" from the "option" menu and you should be able to see the +device as something like: "BitFORCE SHA256 SC". Choose the install or replace +driver option and select WinUSB. You can either google for zadig or download +it from the cgminer directory in the DOWNLOADS link above. + +When you first switch a device over to WinUSB with zadig and it shows that +correctly on the left of the zadig window, but it still gives permission +errors, you may need to unplug the USB miner and then plug it back in. Some +users may need to reboot at this point. + + +LINUX: + +On linux, the direct USB support requires no drivers at all. However due to +permissions issues, you may not be able to mine directly on the devices as a +regular user without giving the user access to the device or by mining as +root (administrator). In order to give your regular user access, you can make +him a member of the plugdev group with the following commands: + + sudo usermod -G plugdev -a `whoami` + +If your distribution does not have the plugdev group you can create it with: + + sudo groupadd plugdev + +In order for the BFL devices to instantly be owned by the plugdev group and +accessible by anyone from the plugdev group you can copy the file +"01-cgminer.rules" from the cgminer archive into the /etc/udev/rules.d +directory with the following command: + + sudo cp 01-cgminer.rules /etc/udev/rules.d/ + +After this you can either manually restart udev and re-login, or more easily +just reboot. + + +OSX: + +On OSX, like Linux, no drivers need to be installed. However some devices +like the bitfury USB sticks automatically load a driver thinking they're a +modem and the driver needs to be unloaded for cgminer to work: + + sudo kextunload -b com.apple.driver.AppleUSBCDC + sudo kextunload -b com.apple.driver.AppleUSBCDCACMData + +There may be a limit to the number of USB devices that you are allowed to start. +The following set of commands, followed by a reboot will increase that: + + sudo su + touch /etc/sysctl.conf + echo kern.sysv.semume=100 >> /etc/sysctl.conf + chown root:wheel /etc/sysctl.conf + chmod 0644 /etc/sysctl.conf + +Some devices need superuser access to mine on them so cgminer may need to +be started with sudo +i.e.: + sudo cgminer + + +--- + +ASIC SPECIFIC COMMANDS + +--anu-freq Set AntminerU1/2 frequency in MHz, range 125-500 (default: 250.0) +--au3-freq Set AntminerU3 frequency in MHz, range 100-250 (default: 225.0) +--au3-volt Set AntminerU3 voltage in mv, range 725-850, 0 to not set (default: 750) +--avalon-auto Adjust avalon overclock frequency dynamically for best hashrate +--avalon-cutoff Set avalon overheat cut off temperature (default: 60) +--avalon-fan Set fanspeed percentage for avalon, single value or range (default: 20-100) +--avalon-freq Set frequency range for avalon-auto, single value or range +--avalon-options Set avalon options baud:miners:asic:timeout:freq:tech +--avalon-temp Set avalon target temperature (default: 50) +--avalon2-freq Set frequency range for Avalon2, single value or range +--avalon2-voltage Set Avalon2 core voltage, in millivolts +--avalon2-fan Set Avalon2 target fan speed +--avalon2-cutoff Set Avalon2 overheat cut off temperature (default: 88) +--avalon2-fixed-speed Set Avalon2 fan to fixed speed +--avalon4-automatic-voltage Automatic adjust voltage base on module DH +--avalon4-voltage Set Avalon4 core voltage, in millivolts, step: 125 +--avalon4-freq Set frequency for Avalon4, 1 to 3 values, example: 445:385:370 +--avalon4-fan Set Avalon4 target fan speed range +--avalon4-temp Set Avalon4 target temperature (default: 42) +--avalon4-cutoff Set Avalon4 overheat cut off temperature (default: 65) +--avalon4-polling-delay Set Avalon4 polling delay value (ms) (default: 20) +--avalon4-ntime-offset Set Avalon4 MM ntime rolling max offset (default: 4) +--avalon4-aucspeed Set Avalon4 AUC IIC bus speed (default: 400000) +--avalon4-aucxdelay Set Avalon4 AUC IIC xfer read delay, 4800 ~= 1ms (default: 9600) +--bab-options Set BaB options max:def:min:up:down:hz:delay:trf +--bet-clk Set clockspeed of ASICMINER Tube/Prisma to (arg+1)*10MHz (default: 23) +--bflsc-overheat Set overheat temperature where BFLSC devices throttle, 0 to disable (default: 90) +--bitburner-fury-options Override avalon-options for BitBurner Fury boards baud:miners:asic:timeout:freq +--bitburner-fury-voltage Set BitBurner Fury core voltage, in millivolts +--bitburner-voltage Set BitBurner (Avalon) core voltage, in millivolts +--bitmain-auto Adjust bitmain overclock frequency dynamically for best hashrate +--bitmain-cutoff Set bitmain overheat cut off temperature +--bitmain-fan Set fanspeed percentage for bitmain, single value or range (default: 20-100) +--bitmain-freq Set frequency range for bitmain-auto, single value or range +--bitmain-hwerror Set bitmain device detect hardware error +--bitmain-options Set bitmain options baud:miners:asic:timeout:freq +--bitmain-temp Set bitmain target temperature +--bxf-bits Set max BXF/HXF bits for overclocking (default: 54) +--bxf-temp-target Set target temperature for BXF/HXF devices (default: 82) +--bxm-bits Set BXM bits for overclocking (default: 54) +--hfa-hash-clock Set hashfast clock speed (default: 550) +--hfa-fail-drop Set how many MHz to drop clockspeed each failure on an overlocked hashfast device (default: 10) +--hfa-fan Set fanspeed percentage for hashfast, single value or range (default: 10-85) +--hfa-name Set a unique name for a single hashfast device specified with --usb or the first device found +--hfa-noshed Disable hashfast dynamic core disabling feature +--hfa-options Set hashfast options name:clock or name:clock@voltage (comma separated) +--hfa-temp-overheat Set the hashfast overheat throttling temperature (default: 95) +--hfa-temp-target Set the hashfast target temperature (0 to disable) (default: 88) +--hro-freq Set the hashratio clock frequency (default: 280) +--klondike-options Set klondike options clock:temptarget +--minion-chipreport Seconds to report chip 5min hashrate, range 0-100 (default: 0=disabled) +--minion-freq Set minion chip frequencies in MHz, single value or comma list, range 100-1400 (default: 1200) +--minion-freqchange Millisecond total time to do frequency changes (default: 1000) +--minion-freqpercent Percentage to use when starting up a chip (default: 70%) +--minion-idlecount Report when IdleCount is >0 or changes +--minion-ledcount Turn off led when more than this many chips below the ledlimit (default: 0) +--minion-ledlimit Turn off led when chips GHs are below this (default: 90) +--minion-idlecount Report when IdleCount is >0 or changes +--minion-noautofreq Disable automatic frequency adjustment +--minion-overheat Enable directly halting any chip when the status exceeds 100C +--minion-spidelay Add a delay in microseconds after each SPI I/O +--minion-spireset SPI regular reset: iNNN for I/O count or sNNN for seconds - 0 means none +--minion-spisleep Sleep time in milliseconds when doing an SPI reset +--minion-temp Set minion chip temperature threshold, single value or comma list, range 120-160 (default: 135C) +--nfu-bits Set nanofury bits for overclocking, range 32-63 (default: 50) +--rock-freq Set RockMiner frequency in MHz, range 125-500 (default: 270) + + +ANTMINER S1 DEVICES + +--bitmain-auto Adjust bitmain overclock frequency dynamically for best hashrate +--bitmain-cutoff Set bitmain overheat cut off temperature +--bitmain-fan Set fanspeed percentage for bitmain, single value or range (default: 20-100) +--bitmain-freq Set frequency range for bitmain-auto, single value or range +--bitmain-hwerror Set bitmain device detect hardware error +--bitmain-options Set bitmain options baud:miners:asic:timeout:freq +--bitmain-temp Set bitmain target temperature + +The Antminer S1 device comes with its own operating system and a preinstalled +version of cgminer as part of the flash firmware. No configuration should be +necessary. + + +ANTMINER U1/2/3 DEVICES + +--anu-freq Set AntminerU1 frequency in MHz, range 150-500 (default: 200) +--au3-freq Set AntminerU3 frequency in MHz, range 100-250 (default: 225.0) +--au3-volt Set AntminerU3 voltage in mv, range 725-850, 0 to not set (default: 750) + +By default, Antminer U1 devices run at a clockspeed of 200. This command allows +you to specify a chosen frequency to attempt to run all ANU devices at. Cgminer +will try to find the nearest frequency the device supports and will report if +the frequency is not exactly as requested. Note that cgminer reports hashrate +ONLY FROM VALID HASHES so if you increase the frequency but your hashrate does +not increase or it decreases and hardware errors start showing up, you have +overclocked it too much. In the worst case scenario it will fail to start at too +high a speed. Most will run happily up to 250. + +ASICMINER BlockErupter Tube/Prisma DEVICES + +--bet-clk Set clockspeed of ASICMINER Tube/Prisma to (arg+1)*10MHz (default: 23) + +Default clockspeed for Tube/Prisma is 240MHz. This command allows to set clockspeed +of on board BE200 chips in range from 200MHz to 320MHz. For Tube devices, you can +try overclocking to 270MHz or even higher, but NOT recommended for Prisma devices. +If you notice hash rate drops or board fails to start, restart cgminer with lower +clockspeed. + + +AVALON AND BITBURNER DEVICES + +Currently all known Avalon devices come with their own operating system and +a preinstalled version of cgminer as part of the flash firmware, based on the +most current cgminer version so no configuration should be necessary. It is +possible to plug a USB cable from a PC into the Avalon device and mine using +cgminer as per any other device. It will autodetect and hotplug using default +options. You can customise the avalon behaviour by using the avalon-options +command, and adjust its fan control-temperature relationship with avalon-temp. +By default the avalon will also cut off when its temperature reaches 60 +degrees. + +All current BitBurner devices (BitBurner X, BitBurner XX and BitBurner Fury) +emulate Avalon devices, whether or not they use Avalon chips. + +Avalon commands: + +--avalon-auto Adjust avalon overclock frequency dynamically for best hashrate +--avalon-cutoff Set avalon overheat cut off temperature (default: 60) +--avalon-fan Set fanspeed percentage for avalon, single value or range (default: 20-100) +--avalon-freq Set frequency range for avalon-auto, single value or range +--avalon-options Set avalon options baud:miners:asic:timeout:freq:tech +--avalon-temp Set avalon target temperature (default: 50) +--bitburner-fury-options Override avalon-options for BitBurner Fury boards baud:miners:asic:timeout:freq +--bitburner-fury-voltage Set BitBurner Fury core voltage, in millivolts +--bitburner-voltage Set BitBurner (Avalon) core voltage, in millivolts + + +Avalon auto will enable dynamic overclocking gradually increasing and +decreasing the frequency till the highest hashrate that keeps hardware errors +under 2% is achieved. This WILL run your avalon beyond its normal specification +so the usual warnings apply. When avalon-auto is enabled, the avalon-options +for frequency and timeout are used as the starting point only. + +eg: +--avalon-fan 50 +--avalon-fan 40-80 + +By default the avalon fans will be adjusted to maintain a target temperature +over a range from 20 to 100% fanspeed. avalon-fan allows you to limit the +range of fanspeeds to a single value or a range of values. + +eg: +--avalon-freq 300-350 + +In combination with the avalon-auto command, the avalon-freq command allows you +to limit the range of frequencies which auto will adjust to. + +eg: +--avalon-temp 55 + +This will adjust fanspeed to keep the temperature at or slightly below 55. +If you wish the fans to run at maximum speed, setting the target temperature +very low such as 0 will achieve this. This option can be added to the "More +options" entry in the web interface if you do not have a direct way of setting +it. + +eg: +--avalon-cutoff 65 + +This will cut off the avalon should it get up to 65 degrees and will then +re-enable it when it gets to the target temperature as specified by avalon-temp. + +eg: +--avalon-options 115200:24:10:D:1500:55 + +The values are baud : miners : asic count : timeout : frequency : technology. + +Baud: +The device is pretty much hard coded to emulate 115200 baud so you shouldn't +change this. + +Miners: +Most Avalons are 3 module devices, which come to 24 miners. 4 module devices +would use 32 here. + +For BitBurner X and BitBurner XX devices you should use twice the number of +boards in the stack. e.g. for a two-board stack you would use 4. For +BitBurner Fury devices you should use the total number of BitFury chips in the +stack (i.e. 16 times the number of boards). e.g. for a two-board stack you +would use 32. + +Asic count: +Virtually all have 10, so don't change this. BitBurner devices use 10 here +even if the boards have some other number of ASICs. + +Timeout: +This is how long the device will work on a work item before accepting new work +to replace it. It should be changed according to the frequency (last setting). +It is possible to set this a little lower if you are trying to tune for short +block mining (eg p2pool) but much lower and the device will start creating +duplicate shares. +A value of 'd' means cgminer will calculate it for you based on the frequency +and is highly recommended. + +Sample settings for valid different frequencies (last 3 values) for 110nm AVAs: +34:375:110 * +36:350:110 * +43:300:110 +45:282:110 (default) +50:256:110 + +Note that setting a value with an asterisk next to it will be using your +avalon outside its spec and you do so at your own risk. + +For 55nm AVAs, the usual values are 8:1500 + +Frequency: +This is the clock speed of the devices. For Avalon 110nm devices, values from +256 upwards are valid with the default being 282 and the maximum practical +being approximately 350. For 55nm devices values from 1000-2000 are valid with +1500 being the default. + +Technology: +What sized technology ASICs are in use in the avalon, choices are 55 or 110, +corresponding to the nm technology chips in use. + +The default frequency for BitBurner X and BitBurner XX boards is 282. The +default frequency for BitBurner Fury boards is 256. Overclocking is +possible - please consult the product documentation and/or manufacturer for +information on safe values. Values outside this range are used at your own +risk. Underclocking is also possible, at least with the X and XX boards. + +eg: +--bitburner-fury-options Override avalon-options for BitBurner Fury boards baud:miners:asic:timeout:freq + +This option takes the same format as --avalon-options. When specified, it +will be used for BitBurner Fury boards in preference to the values specified +in --avalon-options. (If not specified, BitBurner Fury boards will be +controlled by the values used in --avalon options.) See --avalon-options for +a detailed description of the fields. + +This option is particularly useful when using a mixture of different BitBurner +devices as BitBurner Fury devices generally require significantly different +clock frequencies from Avalon-based devices. This option is only available +for boards with recent firmware that are recognized by cgminer as BBF. + +eg: +--bitburner-fury-voltage Set BitBurner Fury core voltage, in millivolts + +Sets the core voltage for the BitBurner Fury boards. The default value is +900. Overvolting is possible - please consult the product documentation +and/or manufaturer about the safe range of values. Values outside this range +are used at your own risk. + +This option is only available for boards with recent firmware that are +recognized by cgminer as BBF. For boards recognized as BTB, see +--bitburner-voltage + +eg: +--bitburner-voltage Set BitBurner (Avalon) core voltage, in millivolts + +Sets the core voltage for the Avalon-based BitBurner X and BitBurner XX +boards. The default value is 1200. Overvolting and undervolting is +possible - please consult the product documentation and/or the manufacturer +for information about the safe range. Values outside this range are used at +your own risk. + +Older BitBurner Fury firmware emulates a BitBurner XX board and is identified +by cgminer as BTB. On these devices, --bitburner-voltage is used to control +the voltage of the BitBurner Fury board. The actual core voltage will be +300mV less than the requested voltage, so to run a BitBurner Fury board at +950mV use --bitburner-voltage 1250. The default value of 1200 therefore +corresponds to the default core voltage of 900mV. + + +If you use the full curses based interface with Avalons you will get this +information: +AVA 0: 22/ 46C 2400R + +The values are: +ambient temp / highest device temp lowest detected ASIC cooling fan RPM. + +Use the API for more detailed information than this. + + +Avalon2 Devices + +--avalon2-freq Set frequency range for Avalon2, single value or range +--avalon2-voltage Set Avalon2 core voltage, in millivolts +--avalon2-fan Set Avalon2 target fan speed +--avalon2-cutoff Set Avalon2 overheat cut off temperature (default: 88) +--avalon2-fixed-speed Set Avalon2 fan to fixed speed + + +Avalon4 Devices + +--avalon4-automatic-voltage Automatic adjust voltage base on module DH +--avalon4-voltage Set Avalon4 core voltage, in millivolts, step: 125 +--avalon4-freq Set frequency for Avalon4, 1 to 3 values, example: 445:385:370 +--avalon4-fan Set Avalon4 target fan speed range +--avalon4-temp Set Avalon4 target temperature (default: 42) +--avalon4-cutoff Set Avalon4 overheat cut off temperature (default: 65) +--avalon4-polling-delay Set Avalon4 polling delay value (ms) (default: 20) +--avalon4-ntime-offset Set Avalon4 MM ntime rolling max offset (default: 4) +--avalon4-aucspeed Set Avalon4 AUC IIC bus speed (default: 400000) +--avalon4-aucxdelay Set Avalon4 AUC IIC xfer read delay, 4800 ~= 1ms (default: 9600) + + +BFLSC Devices + +--bflsc-overheat Set overheat temperature where BFLSC devices throttle, 0 to disable (default: 90) + +This will allow you to change or disable the default temperature where cgminer +throttles BFLSC devices by allowing them to temporarily go idle. + + +BITFURY Devices + +--bxf-bits Set max BXF/HXF bits for overclocking (default: 54) + +In combination with the dynamic clocking on Bi*fury devices, this sets the +highest bit target that cgminer will aim for. + + +--bxf-temp-target Set target temperature for BXF/HXF devices (default: 82) + +Cgminer uses dynamic clocking on Bi*fury devices to try and maintain the +temperature just below an optimal target. This option allows you to change the +target temperature. When actively cooled below this, the devices will run at +maximum speed. + +--bxm-bits Set BXM bits for overclocking (default: 54) + +Choose the overclocking bits for BFx2 devices. + + +--nfu-bits Set nanofury bits for overclocking range 32-63 (default: 50) + +Cgminer by default sets the clockspeed on nanofury devices to the highest that +is still within USB2 spec. This value allows you to alter the clockspeed, with +~54 being the optimal but requiring a higher power or USB3 port. + + +Cointerra Devices + +--cta-load (0 - 255) +--ps-load (0 - 100) + +These are undocumented. + + +Drillbit Systems Devices + +--drillbit-options Set drillbit options :clock[:clock_divider][:voltage] + +* int/ext defines the clock source - default int. Not all boards support ext. +* clock_divider must be 1 or 2 with a default of 1. Bitfury only, + ignored on Avalon. +* clock is in MHz, on Drillbit range 80-250 with a default of 200, + recommended maximum 230. On Avalon range 500-1000 with a + recommended maximum of 800. +* voltage is ASIC core voltage in millivolts, available values vary per board but + default is 850 and the recommended maximum is 950 (Bitfury) and 1000 (Avalon.) + +--drillbit-auto :[::] + +If supported by firmware and device, this feature allows cgminer to +automatically tweak each ASIC's clock rate up and down in to achieve +optimal performance. + +* every - only required param, check each ASIC after each block of + this many work units. Recommended value 100. +* gooderr - the "Good" threshold is when less hardware errors than + this per "every" work units, the clock rate will be increased. + Default value 1. +* baderr - the "Bad" threshold is when more hardware errors than + this per "every" work units, the clock rate will be decreased. + Default value 3. +* maxerr - the "Max" threshold is when more hardware errors than + this per "every" work units (including pre-empting before + "every" work units is up), the clock rate will be decreased and + will not be increased again past this point. Default value 10. + + +BlackArrow Bitfury devices + +--bab-options Set BaB options Max:Def:Min:Up:Down:Hz:Delay:Trf + +Any option left blank or starting with 'd' will use the default setting +If there are not enough options, then the remaining will be left at their +default value + +Max:Def:Min are the chip speed limits to allow, ranging from 52 to 57 + +Up:Down are the HW error % used to tune the chip speed +Up means if the HW error % is less than up, over a 5 minute period, +then increase the chip speed +Down means if the HW error % is greater than down, over 5 minutes, +then decrease the chip speed + +Hz is the SPI clock speed to use + +Delay is the us delay used between bytes for the SPI I/O - default 0 + +Trf is the us delay used between sends for the SPI I/O - default 0 + + +Hashfast devices + +--hfa-hash-clock Set hashfast clock speed (default: 550) + +This will change the initialisation clock speed on all attached hfa devices. +Note that if instability is detected by cgminer and the device has to undergo +a reset, cgminer will lower the clockspeed on resetting it each time till the +value returns to the default of 550. + +--hfa-fail-drop Set how many MHz to drop clockspeed each failure on an overlocked hashfast device (default: 10) + +If you overclock your hashfast device with --hfa-hash-clock and cgminer detects +it failing to return hashes, it will restart it at a lower clock speed if +possible. Changing this value will allow you to choose how much it will lower +the clock speed or to disable this function entirely. + +--hfa-fan Set fanspeed percentage for hashfast, single value or range (default: 10-85) + +This changes the range of fanspeeds used on hashfast devices with firmware that +supports it. Note that the fanspeed will dynamically change to try and maintain +a target temperature with --hfa-temp-target but if the target temperature is +disabled, the fanspeed will remain static. +eg: +--hfa-fan 25-100 + +--hfa-temp-overheat Set the hashfast overheat throttling temperature (default: 95) + +Cgminer will temporarily stop sending hashfast devices work once this +temperature is reached. Note that with the water cooling in these devices, +temperature recovery is likely to be very quick and the device will start +hashing again after only a very brief period. + +--hfa-temp-target Set the hashfast target temperature (0 to disable) (default: 88) + +On hashfast devices with firmware that supports dynamic fanspeed and die speeds, +cgminer will try to maintain temperature according to this target by adjusting +fanspeed and then if need be, throttle speeds on a die-by-die basis. Disabling +this feature will leave a constant fanspeed and die speed but will not disable +the temp-overheat feature. + +--hfa-name Set a unique name for a single hashfast device specified with --usb or the first device found + +This command allows you to specify the unique name stored in nvram on a single +hashfast device. This name can be queried from the API stats command and comes +up as "op name". Discrete names are used by cgminer to try to maintain settings +across restarts, unplugs/hotplugs and so on. If this command is used by itself, +the name will be given to the first hashfast device it encounters and then +cgminer will proceed to go back to regular mining. If you have multiple devices, +it is best to discretely choose the device you wish to use with the --usb +command. For example +'lsusb' on linux shows the following devices (297c:0001 is a hfa device): + Bus 001 Device 079: ID 297c:0001 + Bus 004 Device 042: ID 297c:0001 +If you wished to name the second device Slug you would add the commands: + --hfa-name Slug --usb 4:42 + +--hfa-noshed Disable hashfast dynamic core disabling feature + +Newer firmwares on hashfast devices dynamically disable cores that generate +invalid data. This command will disable this feature where possible. + +--hfa-options Set hashfast options name:clock or clock@voltage (comma separated) + +This command allows you to set options for each discrete hashfast device by +its name (if the firmware has naming support, i.e. version 0.3+). Currently +this takes as option the clock speed alone or clock speed and voltage, +although future options may be added. +e.g.: +--hfa-options "rabbit:650,turtle:550@800" + +Would set a device named rabbit to clock speed 650 MHz using default voltage +and the one named turtle to 550 MHz using a voltage of 800 mv. Starting the +device at a speed where it is most stable will give more reliable hashrates +long term and prevent it interacting with other devices, rather than depending +on the clockdown feature in cgminer. + +Note: Setting voltage cause a board reset and hotplug event on cgminer startup. + +Other undocumented hashfast command line options are for development purposes +only at this stage and serve no useful purpose to end users. + + +Hashratio Devices + +--hro-freq Set the hashratio clock frequency (default: 280) + + +Bitmine A1 Devices + +--bitmine-a1-options ::: +ref_clk: reference clock in kHz (default: 16000) +sys_clk: target system clock in kHz to be set in PLL (default: 250000) +spi_clk: SPI clock in kHz (default: 800) +max_chip: [debug/testing] limit chip chain + +Set 0 for fields you want to keep untouched to default, e.g. +--bitmine-a1-options 0:0:400 +to only set SPI clock to 400kHz + + +Rockminer R-Box Devices + +--rock-freq Set RockMiner frequency in MHz, range 125-500 (default: 270) + +Note that only a limited range is likely to be accepted (usually 200-290) + +--- + +This code is provided entirely free of charge by the programmer in his spare +time so donations would be greatly appreciated. Please consider donating to the +address below. + +Con Kolivas +15qSxP1SQcUX3o4nhkfdbgyoWEFMomJ4rZ diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..3ec6174 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,7 @@ +Current maintainers and active developers: +Main code+USB+ASIC+maintainer: Con Kolivas 15qSxP1SQcUX3o4nhkfdbgyoWEFMomJ4rZ +API+USB+FPGA+ASIC: Andrew Smith 1Jjk2LmktEQKnv8r2cZ9MvLiZwZ9gxabKm + +Legacy: +Original CPU mining software: Jeff Garzik +BitFORCE FPGA mining and refactor: Luke Dashjr 1NbRmS6a4dniwHHoSS9v3tEYUpP1Z5VVdL diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..c9f6227 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,6 @@ +See git repository ('git log') for full changelog. + +Git repo can be found at: +https://github.com/ckolivas/cgminer + +The NEWS file contains most of the changelog diff --git a/FPGA-README b/FPGA-README new file mode 100644 index 0000000..48751d3 --- /dev/null +++ b/FPGA-README @@ -0,0 +1,271 @@ + +This README contains extended details about FPGA mining with cgminer + + +For ModMinerQuad (MMQ) BitForce (BFL) and Icarus (ICA, BLT, LLT, AMU, CMR) +-------------------------------------------------------------------------- + +When mining on windows, the driver being used will determine if mining will work. + +If the driver doesn't allow mining, you will get a "USB init," error message +i.e. one of: + open device failed, err %d, you need to install a WinUSB driver for the device +or + claim interface %d failed, err %d + +The best solution for this is to use a tool called Zadig to set the driver: + http://sourceforge.net/projects/libwdi/files/zadig/ + +This allows you set the driver for the device to be WinUSB which is usually +required to make it work if you're having problems + +With Zadig, you may need to run it as administrator and if your device is +plugged in but you cannot see it, use the Menu: Options -> List All Devices + +You must also make sure you are using the latest libusb-1.0.dll supplied +with cgminer (not the libusbx version) + +When you first switch a device over to WinUSB with Zadig and it shows that +correctly on the left of the Zadig window, but it still gives permission +errors, you may need to unplug the USB miner and then plug it back in + +- + +When mining on linux, but not using 'sudo' and not logged into 'root' you +may get a USB priviledge error (-3), so you may also need to do the following: + + sudo cp 01-cgminer.rules /etc/udev/rules.d/ + +And also: + sudo usermod -G plugdev -a `whoami` + +If your linux distro doesn't have the 'plugdev' group, you can create it like: + sudo groupadd plugdev + +Then reboot ... + +- + +There is a hidden option in cgminer to dump out a lot of information +about USB that will help the developers to assist you if you are having +problems: + + --usb-dump 0 + +It will only help if you have a working FPGA device listed above + + +ModMinerQuad (MMQ) +------------------ + +The mining bitstream does not survive a power cycle, so cgminer will upload +it, if it needs to, before it starts mining (approx 7min 40sec) + +The red LED also flashes while it is uploading the bitstream + +- + +If the MMQ doesn't respond to cgminer at all, or the red LED isn't flashing +then you will need to reset the MMQ + +The red LED should always be flashing when it is mining or ready to mine + +To reset the MMQ, you are best to press the left "RESET" button on the +backplane, then unplug and replug the USB cable + +If your MMQ doesn't have a button on the "RESET" pad, you need to join +the two left pads of the "RESET" pad with conductive wire to reset it. +Cutting a small (metal) paper-clip in half works well for this + +Then unplug the USB cable, wait for 5 seconds, then plug it back in + +After you press reset, the red LED near the USB port should blink continuously + +If it still wont work, power off, wait for 5 seconds, then power on the MMQ +This of course means it will upload the bitstream again when you start cgminer + +- + +Device 0 is on the power end of the board + +- + +You must make sure you have an approriate firmware in your MMQ +Read here for official details of changing the firmware: + http://wiki.btcfpga.com/index.php?title=Firmware + +The basics of changing the firmware are: + You need two short pieces of conductive wire if your MMQ doesn't have + buttons on the "RESET" and "ISP" pads on the backplane board + Cutting a small (metal) paper-clip in half works well for this + + Join the 2 left pads of the "RESET" pad with wire and the led will dim + Without disconnecting the "RESET", join the 2 left pads of the "ISP" pad + with a wire and it will stay dim + Release "RESET" then release "ISP" and is should still be dim + Unplug the USB and when you plug it back in it will show up as a mass + storage device + Linux: (as one single line): + mcopy -i /dev/disk/by-id/usb-NXP_LPC134X_IFLASH_ISP000000000-0:0 + modminer091012.bin ::/firmware.bin + Windows: delete the MSD device file firmware.bin and copy in the new one + rename the new file and put it under the same name 'firmware.bin' + Disconnect the USB correctly (so writes are flushed first) + Join and then disconnect "RESET" and then plug the USB back in and it's done + +Best to update to one of the latest 2 listed below if you don't already +have one of them in your MMQ + +The current latest different firmware are: + + Latest for support of normal or TLM bitstream: + http://btcfpga.com/files/firmware/modminer092612-TLM.bin + + Latest with only normal bitstream support (Temps/HW Fix): + http://btcfpga.com/files/firmware/modminer091012.bin + +The code is currently tested on the modminer091012.bin firmware. +This comment will be updated when others have been tested + +- + +On many linux distributions there is an app called modem-manager that +may cause problems when it is enabled, due to opening the MMQ device +and writing to it + +The problem will typically present itself by the flashing led on the +backplane going out (no longer flashing) and it takes a power cycle to +re-enable the MMQ firmware - which then can lead to the problem happening +again + +You can either disable/uninstall modem-manager if you don't need it or: +a (hack) solution to this is to blacklist the MMQ USB device in +/lib/udev/rules.d/77-mm-usb-device-blacklist.rules + +Adding 2 lines like this (just above APC) should help +# MMQ +ATTRS{idVendor}=="1fc9", ATTRS{idProduct}=="0003", ENV{ID_MM_DEVICE_IGNORE}="1" + +The change will be lost and need to be re-done, next time you update the +modem-manager software + +TODO: check that all MMQ's have the same product ID + + +BitForce (BFL) +-------------- + +--bfl-range Use nonce range on bitforce devices if supported + +This option is only for bitforce devices. Earlier devices such as the single +did not have any way of doing small amounts of work which meant that a lot of +work could be lost across block changes. Some of the "minirigs" have support +for doing this, so less work is lost across a longpoll. However, it comes at +a cost of 1% in overall hashrate so this feature is disabled by default. It +is only recommended you enable this if you are mining with a minirig on +p2pool. + +C source is included for a bitforce firmware flash utility on Linux only: + bitforce-firmware-flash.c +Using this, you can change the bitstream firmware on bitforce singles. +It is untested with other devices. Use at your own risk! + +To compile: + make bitforce-firmware-flash +To flash your BFL, specify the BFL port and the flash file e.g.: + sudo ./bitforce-firmware-flash /dev/ttyUSB0 alphaminer_832.bfl +It takes a bit under 3 minutes to flash a BFL and shows a progress % counter +Once it completes, you may also need to wait about 15 seconds, +then power the BFL off and on again + +If you get an error at the end of the BFL flash process stating: + "Error reading response from ZBX" +it may have worked successfully anyway. +Test mining on it to be sure if it worked or not. + +You need to give cgminer about 10 minutes mining with the BFL to be sure of +the MH/s value reported with the changed firmware - and the MH/s reported +will be less than the firmware speed since you lose work on every block change. + + +Icarus (ICA, BLT, LLT, AMU, CMR) +-------------------------------- + +There are two hidden options in cgminer when Icarus support is compiled in: + +--icarus-options Set specific FPGA board configurations - one set of values for all or comma separated + baud:work_division:fpga_count + + baud The Serial/USB baud rate - 115200 or 57600 only - default 115200 + work_division The fraction of work divided up for each FPGA chip - 1, 2, 4 or 8 + e.g. 2 means each FPGA does half the nonce range - default 2 + fpga_count The actual number of FPGA working - this would normally be the same + as work_division - range is from 1 up to 'work_division' + It defaults to the value of work_division - or 2 if you don't specify + work_division + +If you define fewer comma seperated values than Icarus devices, the last values will be used +for all extra devices + +An example would be: --icarus-options 57600:2:1 +This would mean: use 57600 baud, the FPGA board divides the work in half however +only 1 FPGA actually runs on the board (e.g. like an early CM1 Icarus copy bitstream) + +--icarus-timing Set how the Icarus timing is calculated - one setting/value for all or comma separated + default[=N] Use the default Icarus hash time (2.6316ns) + short=[N] Calculate the hash time and stop adjusting it at ~315 difficulty 1 shares (~1hr) + long=[N] Re-calculate the hash time continuously + value[=N] Specify the hash time in nanoseconds (e.g. 2.6316) and abort time (e.g. 2.6316=80) + +If you define fewer comma seperated values than Icarus devices, the last values will be used +for all extra devices + +Icarus timing is required for devices that do not exactly match a default Icarus Rev3 in +processing speed +If you have an Icarus Rev3 you should not normally need to use --icarus-timing since the +default values will maximise the MH/s and display it correctly + +Icarus timing is used to determine the number of hashes that have been checked when it aborts +a nonce range (including on a LongPoll) +It is also used to determine the elapsed time when it should abort a nonce range to avoid +letting the Icarus go idle, but also to safely maximise that time + +'short' or 'long' mode should only be used on a computer that has enough CPU available to run +cgminer without any CPU delays (an active desktop or swapping computer would not be stable enough) +Any CPU delays while calculating the hash time will affect the result +'short' mode only requires the computer to be stable until it has completed ~315 difficulty 1 shares +'long' mode requires it to always be stable to ensure accuracy, however, over time it continually +corrects itself +The optional additional =N for 'short' or 'long' specifies the limit to set the timeout to in N * 100ms +thus if the timing code calculation is higher while running, it will instead use N * 100ms +This can be set to the appropriate value to ensure the device never goes idle even if the +calculation is negatively affected by system performance + +When in 'short' or 'long' mode, it will report the hash time value each time it is re-calculated +In 'short' or 'long' mode, the scan abort time starts at 5 seconds and uses the default 2.6316ns +scan hash time, for the first 5 nonce's or one minute (whichever is longer) + +In 'default' or 'value' mode the 'constants' are calculated once at the start, based on the default +value or the value specified +The optional additional =N specifies to set the default abort at N * 100ms, not the calculated +value, which is ~112 for 2.6316ns + +To determine the hash time value for a non Icarus Rev3 device or an Icarus Rev3 with a different +bitstream to the default one, use 'long' mode and give it at least a few hundred shares, or use +'short' mode and take note of the final hash time value (Hs) calculated +You can also use the RPC API 'stats' command to see the current hash time (Hs) at any time + +The Icarus code currently only works with an FPGA device that supports the same commands as +Icarus Rev3 requires and also is less than ~840MH/s and greater than 2MH/s +If an FPGA device does hash faster than ~840MH/s it should work correctly if you supply the +correct hash time nanoseconds value + +The Icarus code will automatically detect Icarus, Lancelot, AsicminerUSB and Cairnsmore1 +FPGA devices and set default settings to match those devices if you don't specify them + +The timing code itself will affect the Icarus performance since it increases the delay after +work is completed or aborted until it starts again +The increase is, however, extremely small and the actual increase is reported with the +RPC API 'stats' command (a very slow CPU will make it more noticeable) +Using the 'short' mode will remove this delay after 'short' mode completes +The delay doesn't affect the calculation of the correct hash time diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..47f9265 --- /dev/null +++ b/LICENSE @@ -0,0 +1,3 @@ +Cgminer is available under the terms of the GNU Public License version 3. + +See COPYING for details. diff --git a/MCast.class b/MCast.class new file mode 100644 index 0000000000000000000000000000000000000000..09dde329b1f55f48f04408d87d46435be7ceaf64 GIT binary patch literal 5678 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 F{y#8rPA~uf literal 0 HcmV?d00001 diff --git a/MCast.java b/MCast.java new file mode 100644 index 0000000..4ba237e --- /dev/null +++ b/MCast.java @@ -0,0 +1,333 @@ +/* + * + * Copyright (C) Andrew Smith 2013 + * + * Usage: java MCast [-v] code toaddr port replyport wait + * + * If any are missing or blank they use the defaults: + * + * -v means report how long the last reply took + * + * code = 'FTW' + * toaddr = '224.0.0.75' + * port = '4028' + * replyport = '4027' + * wait = '1000' + * + */ + +import java.net.*; +import java.io.*; +import java.util.*; + +class MCast implements Runnable +{ + static private final String MCAST_CODE = "FTW"; + static private final String MCAST_ADDR = "224.0.0.75"; + static private final int MCAST_PORT = 4028; + static private final int MCAST_REPORT = 4027; + static private final int MCAST_WAIT4 = 1000; + + static private String code = MCAST_CODE; + static private String addr = MCAST_ADDR; + static private int port = MCAST_PORT; + static private int report = MCAST_REPORT; + static private int wait4 = MCAST_WAIT4; + + private InetAddress mcast_addr = null; + + static private final Integer lock = new Integer(666); + + static private boolean ready = false; + + static private Thread listen = null; + + static public boolean verbose = false; + + static private Date start = null; + static private Date last = null; + static boolean got_last = false; + + static public void usAge() + { + System.err.println("usAge: java MCast [-v] [code [toaddr [port [replyport [wait]]]]]"); + System.err.println(" -v=report elapsed ms to last reply"); + System.err.println(" Anything below missing or blank will use it's default"); + System.err.println(" code=X in cgminer-X-Port default="+MCAST_CODE); + System.err.println(" toaddr=multicast address default="+MCAST_ADDR); + System.err.println(" port=multicast port default="+MCAST_PORT); + System.err.println(" replyport=local post to listen for replies default="+MCAST_REPORT); + System.err.println(" wait=how long to wait for replies default="+MCAST_WAIT4+"ms"); + System.exit(1); + } + + private int port(String _port, String name) + { + int tmp = 0; + + try + { + tmp = Integer.parseInt(_port); + } + catch (NumberFormatException nfe) + { + System.err.println("Invalid " + name + " - must be a number between 1 and 65535"); + usAge(); + System.exit(1); + } + + if (tmp < 1 || tmp > 65535) + { + System.err.println("Invalid " + name + " - must be a number between 1 and 65535"); + usAge(); + System.exit(1); + } + + return tmp; + } + + public void set_code(String _code) + { + if (_code.length() > 0) + code = _code; + } + + public void set_addr(String _addr) + { + if (_addr.length() > 0) + { + addr = _addr; + + try + { + mcast_addr = InetAddress.getByName(addr); + } + catch (Exception e) + { + System.err.println("ERR: Invalid multicast address"); + usAge(); + System.exit(1); + } + } + } + + public void set_port(String _port) + { + if (_port.length() > 0) + port = port(_port, "port"); + } + + public void set_report(String _report) + { + if (_report.length() > 0) + report = port(_report, "reply port"); + } + + public void set_wait(String _wait4) + { + if (_wait4.length() > 0) + { + try + { + wait4 = Integer.parseInt(_wait4); + } + catch (NumberFormatException nfe) + { + System.err.println("Invalid wait - must be a number between 0ms and 60000ms"); + usAge(); + System.exit(1); + } + + if (wait4 < 0 || wait4 > 60000) + { + System.err.println("Invalid wait - must be a number between 0ms and 60000ms"); + usAge(); + System.exit(1); + } + } + } + + public void run() // listen + { + byte[] message = new byte[1024]; + DatagramSocket socket = null; + DatagramPacket packet = null; + + try + { + socket = new DatagramSocket(report); + packet = new DatagramPacket(message, message.length); + + synchronized (lock) + { + ready = true; + } + + while (true) + { + socket.receive(packet); + + synchronized (lock) + { + last = new Date(); + } + + int off = packet.getOffset(); + int len = packet.getLength(); + + System.out.println("Got: '" + new String(message, off, len) + "' from" + packet.getSocketAddress()); + } + } + catch (Exception e) + { + socket.close(); + } + } + + public void sendMCast() + { + try + { + String message = new String("cgminer-" + code + "-" + report); + MulticastSocket socket = null; + DatagramPacket packet = null; + + socket = new MulticastSocket(); + packet = new DatagramPacket(message.getBytes(), message.length(), mcast_addr, port); + + System.out.println("About to send " + message + " to " + mcast_addr + ":" + port); + + start = new Date(); + + socket.send(packet); + + socket.close(); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + public void init() + { + MCast lis = new MCast(); + listen = new Thread(lis); + listen.start(); + + while (true) + { + synchronized (lock) + { + if (ready) + break; + } + + try + { + Thread.sleep(100); + } + catch (Exception sl1) + { + } + } + + try + { + Thread.sleep(500); + } + catch (Exception sl2) + { + } + + sendMCast(); + + try + { + Thread.sleep(wait4); + } + catch (Exception sl3) + { + } + + listen.interrupt(); + + if (verbose) + { + try + { + Thread.sleep(100); + } + catch (Exception sl4) + { + } + + synchronized (lock) + { + if (last == null) + System.out.println("No replies received"); + else + { + long diff = last.getTime() - start.getTime(); + System.out.println("Last reply took " + diff + "ms"); + } + } + } + + System.exit(0); + } + + public MCast() + { + } + + public static void main(String[] params) throws Exception + { + int p = 0; + + MCast mcast = new MCast(); + + mcast.set_addr(MCAST_ADDR); + + if (params.length > p) + { + if (params[p].equals("-?") + || params[p].equalsIgnoreCase("-h") + || params[p].equalsIgnoreCase("-help") + || params[p].equalsIgnoreCase("--help")) + MCast.usAge(); + else + { + if (params[p].equals("-v")) + { + mcast.verbose = true; + p++; + } + + if (params.length > p) + { + mcast.set_code(params[p++]); + + if (params.length > p) + { + mcast.set_addr(params[p++]); + + if (params.length > p) + { + mcast.set_port(params[p++]); + + if (params.length > p) + { + mcast.set_report(params[p++]); + if (params.length > p) + mcast.set_wait(params[p]); + } + } + } + } + } + } + + mcast.init(); + } +} diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..29de3ab --- /dev/null +++ b/Makefile.am @@ -0,0 +1,169 @@ + +ACLOCAL_AMFLAGS = -I m4 + +JANSSON_CPPFLAGS= -I$(top_builddir)/compat/jansson-2.6/src -I$(top_srcdir)/compat/jansson-2.6/src + +if WANT_USBUTILS +USBUTILS_CPPFLAGS = -I$(top_builddir)/compat/libusb-1.0/libusb -I$(top_srcdir)/compat/libusb-1.0/libusb +else +USBUTILS_CPPFLAGS = +endif + +EXTRA_DIST = example.conf linux-usb-cgminer \ + api-example.php miner.php \ + API.class API.java api-example.c windows-build.txt \ + bitstreams/README API-README FPGA-README \ + bitforce-firmware-flash.c hexdump.c ASIC-README \ + 01-cgminer.rules + +SUBDIRS = lib compat ccan + +cgminer_CPPFLAGS = $(PTHREAD_FLAGS) -fno-strict-aliasing $(JANSSON_CPPFLAGS) $(USBUTILS_CPPFLAGS) + +bin_PROGRAMS = cgminer + +cgminer_LDFLAGS = $(PTHREAD_FLAGS) +cgminer_LDADD = $(DLOPEN_FLAGS) @LIBCURL_LIBS@ @JANSSON_LIBS@ @PTHREAD_LIBS@ \ + @NCURSES_LIBS@ @PDCURSES_LIBS@ @WS2_LIBS@ \ + @LIBUSB_LIBS@ @MM_LIBS@ @RT_LIBS@ \ + @MATH_LIBS@ lib/libgnu.a ccan/libccan.a + +cgminer_CPPFLAGS += -I$(top_builddir)/lib -I$(top_srcdir)/lib + +if !HAVE_WINDOWS +cgminer_CPPFLAGS += @LIBCURL_CFLAGS@ +endif + +# common sources +cgminer_SOURCES := cgminer.c + +cgminer_SOURCES += elist.h miner.h compat.h bench_block.h \ + util.c util.h uthash.h logging.h \ + sha2.c sha2.h api.c + +cgminer_SOURCES += logging.c + +cgminer_SOURCES += klist.h klist.c + +cgminer_SOURCES += noncedup.c + +if NEED_FPGAUTILS +cgminer_SOURCES += fpgautils.c fpgautils.h +endif + +if WANT_USBUTILS +cgminer_SOURCES += usbutils.c usbutils.h +endif + +if WANT_LIBBITFURY +cgminer_SOURCES += libbitfury.c libbitfury.h mcp2210.c mcp2210.h +endif + +if WANT_CRC16 +cgminer_SOURCES += crc16.c crc.h +endif + +# Device drivers +if HAS_AVALON +cgminer_SOURCES += driver-avalon.c driver-avalon.h +endif + +if HAS_KNC +cgminer_SOURCES += driver-knc.c knc-asic.c knc-asic.h knc-transport.h knc-transport-spi.c +cgminer_LDADD += -lz +endif + +if HAS_BFLSC +cgminer_SOURCES += driver-bflsc.c driver-bflsc.h +endif + +if HAS_BITFORCE +cgminer_SOURCES += driver-bitforce.c +endif + +if HAS_HASHFAST +cgminer_SOURCES += driver-hashfast.c driver-hashfast.h hf_protocol.h hf_protocol_be.h +endif + +if HAS_HASHRATIO +cgminer_SOURCES += driver-hashratio.c driver-hashratio.h +endif + +if HAS_BITFURY +cgminer_SOURCES += driver-bitfury.c driver-bitfury.h +endif + +if HAS_BITMINE_A1 +cgminer_SOURCES += driver-SPI-bitmine-A1.c +cgminer_SOURCES += spi-context.c spi-context.h +cgminer_SOURCES += A1-common.h +cgminer_SOURCES += A1-board-selector.h +cgminer_SOURCES += A1-board-selector-CCD.c A1-board-selector-CCR.c +cgminer_SOURCES += A1-trimpot-mcp4x.h A1-trimpot-mcp4x.c +cgminer_SOURCES += i2c-context.c i2c-context.h +endif + +if HAS_DRILLBIT +cgminer_SOURCES += driver-drillbit.c driver-drillbit.h +endif + +if HAS_ICARUS +cgminer_SOURCES += driver-icarus.c +endif + +if HAS_KLONDIKE +cgminer_SOURCES += driver-klondike.c +endif + +if HAS_COINTERRA +cgminer_SOURCES += driver-cointerra.c driver-cointerra.h +endif + +if HAS_SP10 +cgminer_SOURCES += driver-spondoolies-sp10.c driver-spondoolies-sp10.h \ + driver-spondoolies-sp10-p.c driver-spondoolies-sp10-p.h +endif + + +if HAS_SP30 +cgminer_SOURCES += driver-spondoolies-sp30.c driver-spondoolies-sp30.h \ + driver-spondoolies-sp30-p.c driver-spondoolies-sp30-p.h +endif + +if HAS_BAB +cgminer_SOURCES += driver-bab.c +endif + +if HAS_AVALON2 +cgminer_SOURCES += driver-avalon2.c driver-avalon2.h +endif + +if HAS_AVALON4 +cgminer_SOURCES += driver-avalon4.c driver-avalon4.h +endif + +if HAS_MINION +cgminer_SOURCES += driver-minion.c +endif + +if HAS_BMSC +cgminer_SOURCES += driver-bmsc.c +endif + +if HAS_BITMAIN +cgminer_SOURCES += driver-bitmain.c driver-bitmain.h +endif + +if HAS_BITMAIN_C5 +cgminer_SOURCES += driver-btm-c5.c driver-btm-c5.h sha2_c5.c sha2_c5.h +endif + +if HAS_MODMINER +cgminer_SOURCES += driver-modminer.c +bitstreamsdir = $(bindir)/bitstreams +dist_bitstreams_DATA = $(top_srcdir)/bitstreams/README +endif + +if HAS_BLOCKERUPTER +cgminer_SOURCES += driver-blockerupter.c driver-blockerupter.h +endif diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..86e9f53 --- /dev/null +++ b/NEWS @@ -0,0 +1,7017 @@ +Version 4.9.0 - 16th December 2014 + +- Minor fix +- Fix MM41 voltage setting +- Fix the default settings of new module +- Count non matching stratum as a hw error on ava4 +- Fix ava4 build incompatibilites and missing write config parameters +- Use strcasecmp for device matching in usbutils in case of subtle manufacturer +changes +- Add manufacturer and product definitions for ava4 +- Cosmetic ava4 change +- Cosmetic ava4 message fixes +- Add sanity check for NULL data being passed to usb_perform_transfer +- All write errors should be treated as fatal for ava4 devices +- Change initial fan start speed, mins and max for avalon4 to ensure fan starts +spinning but can go lower RPM +- Disable zero length packets on ava4 before trying to init +- Add a cgpu device option to disable zero length packets and enable it for +avalon4 +- Display ava4 stats consistent with other devices +- Add ava4 to udev rules file +- Fix build warnings on ava4 +- Fix ava4 build +- Add Avalon4 support +- Filter duplicate stratum shares from being submitted upstream +- Do rudimentary detection of duplicate shares per device + + +Version 4.8.0 - 25th November 2014 + +- Allow forcing of building driver combinations with --enable-forcecombo +- Put spaces between name and id in avalon2 and icarus +- Relax detection of a failing ava2 to more than 1 minute and perform the test +after polling for results +- Cap maximum diff on ava2 in order to still get shares +- Put space between device name and id to prevent device names with numbers in +them confusing the display +- USB write errors are always fatal so they should be treated as such on ava2 +- Issue a usb reset for ava2 that is not returning valid shares and then drop it +if it persists for over a minute +- Process share results without a result value +- Damp out hashrate displayed for antminer USBs +- Add voltage and speed where relevant to antminer USBs +- Don't estimate time on any antminer usb during a timeout +- Return icarus nonce ok only when the nonce size matches the device or more +- Don't discard old workids until we cycle back to them on antusb and look for +more nonces in the buffer +- Adjust ant usb timing for queued work +- Use a cyclical list for the ant queued work +- Mask and limit workid for antusb and dont clear buffer +- Check the nonce on the worked item, not the submitted work +- Skip over unfinished work that we can't free in ant usb +- Use a workid and array if possible for the small ant usb work queue +- Create an array for antworks for antminer usb devices +- On U3 calculate hashrate purely on shares, not timeouts +- Add switches for AU3 +- Adjust icarus wait timeout according to device +- Differentiate U3 from U1/2 as a separate driver with different parameters and +adjust timing accordingly +- Skip ANUs detected in rock detect +- Try U3 after trying other icarus options +- Add rudimentary ANU voltage setting support for U3 +- Fix ignoring unprefixed v6 address in api allow list +- Fix minor typos in Spondoolies SP10 and SP30 drivers +- Implement a basic rock_flush function to discard the base work we are rolling +work from. +- Task_no for rockminer from the nonce bin should simply be masked +- Change rbox default correction times to 5 in a revised frequency order +- Change default frequency on T1 to 330 +- Reinstate last received check and resend in rockminer, being more lenient at 2 +seconds to allow for dither errors at 1 +- Roll work for the rbox when possible + + +Version 4.7.1 - 4th November 2014 + +- Selectively yield on dropping a lock only on single CPU platforms +- Make it impossible to configure in more than one device that is meant to be +standalone. Add more information to configure help, along with comments for new +drivers. +- Add warning against system libusb in configure help +- stratum_rthread sleep only 3s when all the pool have disconnected +- Filter responses that don't have a result +- Implement support for pool ping and json integers of zero in getversion and +ping +- Fix segfault when writing config with hashratio built in +- Save pools in priority order at time of writing config +- Set the correct flag for close on exec for sockets +- Suspend stratum on removing a pool +- Set CLOEXEC on sockets on linux +- Drivers that take a diff should specify a max diff or it is assumed they don't +support one so set max_diff to 1 if unset +- Send hfa generic frame only if voltage was specified on the command line for +that device +- Set hashfast voltage settings only when really needed +- Hashfast voltage support +- Increase max diff on sp30 to 1024 +- Reset ipv6 flag to false in every api-allow loop +- undeclared identifier 'IPV6_ADD_MEMBERSHIP' fix for apple +- two back temps spondoolies2 +- two back temps spondoolies +- correct suggest_difficulty json rpc call +- Add more usb3 hub identifiers for windows +- Set driver max diff to large value if unset +- Wake gws on get queued +- Implement blacklisting of attempting to match known products from ones without +identifiers +- Fix hfa driver building without libcurl +- Enable building libusb without udev +- Fix off by one calculation error in sp30 leading zeroes +- Send correct diff work to sp30 for hashmeter to be correct +- Do the sleep in spondoolies_queue_full_sp30 after dropping the lock +- Minor tidy in sp30 driver +- Fix sp30 warnings + +Version 4.7.0 - 14th October 2014 + +- Implement generic inet_pton for windows +- Fix warnings +- Fix bulk of remaining style in blockerupter.c +- Tidy style in blockerupter.h +- Tidy bulk of style in blockerupter.c +- Fix missing minimum diff setting for blockerupter +- Fix unused variable warnings +- remove unnecessary sleep; fix potenital div by 0 errs; use min_diff in driver +definition +- Fix coding style +- Make the sp30 hashrate meter based on valid share generation +- Change default max queue back to 1 in line with speed of most current asic +controllers +- Change diff limits to values suitable for sp30 +- Add pool number to response from addpool to the API +- Make the restart and quit API commands valid json responses +- Fix number of nos +- Add option to set clock ('--bet-clk X' actual clock is (X+1)*10 ) +- compatible with X24 board +- Fix error when using v6 without mask in api-allow +- Support ipv6 multicast +- Set min_diff to 1 +- Allow arbitrary clamping of lower device diffs for slow controllers by driver +- Don't set default fan to max on hashratio +- The 2nd read never gets anything on ava2 so remove it entirely and just return +an error if we are out of sync +- Implement support for mining.suggest_difficulty +- Fix client ip address output +- Free addrinfo garbage +- Remove the brackets when using v6 pool address +- Add ipv6 support for api listen +- Avalon Nano: Add support Avalon Nano usb miner +- fix bug in setdiff +- limit minimum diff to 64 +- Add BlockErupter Driver +- Avalon2: display currect max temperature on statline +- Remove unused variable + + +Version 4.6.1 - 20th September 2014 + +- Throttle bflsc28 devices when they hit the overheat limit +- Add whitelisting of firmware used in final bflsc28 products +- API.java - remove lowercase of all data sent +- Avalon2: Add 3 bytes nonce2 support +- Avalon2: MM needs n2size length <= 4 +- Use fan min as fan speed when run with --avalon2-fixed-speed +- Clear the pool submit fail bool after adding shares to the stratum hashtable +to minimise window the share is not in the table +- api-example unlimited socket works +- Add custom strcasestr and use custom gnu type functions in bflsc +- Fix windows build of bflsc driver +- Fix possible deref in bflsc28 + + +Version 4.6.0 - 7th September 2014 + +- We should not be checking for pool_unworkable in cnx_needed as it is keeping +stratum connections open on unused pools +- Properly handle lack of input when adding pool via menu +- Allow workers without passwords +- minion - increase max chip number +- Avalon2: add more comments on Avalon2 options +- Avalon2: add polling delay option, long coinbase support, LED status on API, +change overheat from 88 to 98 +- minion - add a ' before non-zero core error counts +- minion - hidden optional per core nonce stats +- bflsc28 - clock is hex +- bflsc28 - allow setting clock and volt from the API ascset command +- bflsc28 - add chip count to stats +- bflsc28 stats +- Simplehacks to better serve bflsc28 +- Only use one hashtable for bflsc28 work queued +- Copy back the buffer after we've stripped the inprocess field on bflsc +- Parse results for BMA based on uid and remove work from the queue when found +- Strip out the inprocess details from bflsc results if it exists +- Create a hashtable of work by uid as it's accepted by BMA +- Add some rudimentary values for BMA sleep times +- Fix various errors in queueing work for bflsc28 and limit job queueing to 10 +to fit within a usb frame +- Create preliminary work queueing for bflsc28 using jobs of up to 20 at a time +by rolling work where possible +- Convert all bflsc transfers to the full 512 bytes +- Don't mistake bflsc28 for fpga +- Do initial detection of bflsc28 devices + + +Version 4.5.0 - 29th July 2014 + +- Fix windows build for hashratio and ava2 +- Demote bad checksum message in cointerra driver but allow message to still be +parsed since it won't allow existing firmwares to work otherwise +- Reorder and document the configure options +- Merge https://github.com/KnCMiner/cgminer into knc +- Change default voltage on ava2 to 0.666V because Satan +- Enable combined building of avalon2 and hashratio +- Fix stratum embedded fpgas to not duplicate work with other devices +- Implement smarter PID type fan control for ava2 allowing more generous +temperatures and far lower fan speeds for optimal power and noise usage. Adjust +default frequency to 450 as per recommendation. +- Fix various warnings in ava2 +- Go back to polling design since async will not work for ava2 and fix various +read design errors +- Fix error in 2nd read functions for av2 and hro +- Correct init and read sequence for ava2, and convert from a polling mechanism +to a separate read thread +- Initial commit of ava2 conversion to direct USB +- Display frequency and voltage with ava2 on the statline +- Store fan percentage and display temp and fan percent for ava2 +- Set avalon2 frequency and voltage to appropriate defaults if none are +specified on the command line +- Demote some ava2 messages that don't need to be errors and remove unused works +array +- Fix broken fan logic for ava2 +- Fix hexdump on 64bit +- rockminer frequency is between 200 and 400 MHz +- fix jansson include path in cgminer-api compile instructions +- Remove requirement for ifndefs for avalon2 from the generic driver work +function +- Fix hashratio device name +- Make submit_nonce2_nonce return whether the share was valid or not +- Reinstate missing necessary init sequence for hashratio +- Handle disconnected hashratio devices +- Add hashratio frequency command line +- Fix stratum updates not being passed to hashratio devices and clean up +- Move to updated avalon2 type driver model for hashratio device +- Initial import and conversion of hashratio driver to direct USB +- Increase the internal buffer for API response, as "stats" command response +can grow greater than 8K +- Detach test pool thread only if we have a blocking startup + + +Version 4.4.2 - 17th July 2014 + +- Remove the use of the pthread_tryjoin_np which is currently unimplemented on +many platforms +- Fix processarg parameters loaded from a config file not being saveable +- We only use the jansson in our source tree so no need for special case +handling of older versions +- Upgrade jansson to 2.6 +- Only clear sockbuf if it's been allocated +- Fix missing osm-led-mode support in write config +- Deal with nanosecond overflow in both directions on both addition and +subtration of timespecs +- Rename sp10 driver internally from spondoolies to sp10 +- minion - add a 2nd (optional - disabled) reset test +- production stats added, reset queue added +- minion - correct led ghs2 choice +- minion - correct ghs2 display +- minion - reset the led counter when doing a chip RSTN full reset +- minion - don't reset the led counter for an SPI reset +- minion - led per chip and use all time average +- minion - report spi error counts and settings in stats +- minion - undeclared fix +- minion - chip power cycle option +- minion - record 0xff error history and reduce screen output +- minion - reset on result or fifo 0xff +- minion - clarify the 0 value of spireset +- minion - make SPI reset more configurable +- minion - make the SPI reset ms sleep a parameter and API settable +- sp10 sensors +- sp30 +- minion - led+more api setting +- Avoid blocking all pool testing if one pool fails to ever init +- There is no point storing the hints addrinfo in struct pool +- minion - 'reset' SPI when getting errors +- initialise more pool values in benchmark +- minion - auto adjust freq +- merge upstream frequency changes +- icarus - timing history in its own function +- rbox - add lotsa stats, tidy up a bit more +- Fix an off-by-one. +- icarus - detect stat should be LOG_DEBUG +- icarus - tidy up rbox code, remove statics, and add rocketbox +- minion - do an early reset to clear the chip status +- minion - use descriptive names for the list types +- Avalon2: automatic adjust fan speed, using crc16 on job_id compare, turn on +the led by API, detect twice when start, remember the last stratum message +increase the hashrate, add cutoff option +- fix AntS1 breakages from AntS2 changes +- minion - disable dup nonce check +- minion - add an ioseq number for each ioctl to simplify work ordering +- minion - redo old work expiry based on txrx order +- minion - more work stats, minimise queued work, free flushed queued work +- minion - allow resetting a chip via the API +- minion - correct 'WQue Count' in stats +- minion - delay after reset, reset history also, add dups to api stats +- noncedup - give access to the internal stats +- minion - increase reset to 75% +- minion - dup checking, disable reread by default and extra ioctl debugging +- minion - always check the chip queue before queuing new work + + +Version 4.4.1 - 21st June 2014 + +- Move icarus driver to being seen as an asic +- Clear usb reads on each pass through icarus detect to hopefully prevent false +positives for detecting rboxes +- Clean up pool failure and failover code for stratum + + +Version 4.4.0 - 16th June 2014 + +- Tidy unused rockminer variables +- Tidy rockminer defines +- Make rockminer driver compatible with other icarus drivers being present +- Import basic rbox driver +- minion - add optional (on) GPIO chip selection +- Clear the pool idle flag in the pool test thread +- CoreFmatch in cointerra should be a uint16 +- Display error message if we receive one on share rejects +- Allow zero length strings to be passed to valid_hex +- delete unused roundl definition + + +Version 4.3.5 - 10th June 2014 + +- Cointerra driver updates. +- Sleep before retrying in the test pool thread after a pool has died +- Use valid_ascii testing for job_id since it need not be hex only +- Only show slow/down message till pool is flagged idle +- Do some random sanity checking for stratum message parsing +- Keep looking for when a pool comes to life at startup and touch the logwin so +the message is not invisible +- Fix no libcurl build +- Added Drillbit Thumb to udev rules. +- Avoid dereference on getting API stats on partially initialised HFA instances +- A1: add support for updated product variants, small fixes +- Add one more usbutils fix +- Convert uses of usbutils memcpy to cg_memcpy +- Add a sanity checking memcpy function which checks for overflows +- minion - count force use reread +- minion - add a disabled ioctl() test +- minion - add more checking of SPI results for corruption +- minion - optional (disabled) ioctl() debug +- Increase S1 overheat to 75 degrees C +- Add ruby api-example to API-README +- minion - allow core selection at runtime +- API - lcd all-in-one brief summary + + +Version 4.3.4 - 25th May 2014 + +- Add support for 2 nonces per block in spond driver +- Increase timeout on reset in cta driver to 5 seconds +- Increase max diff on spondoolies driver slightly to be well below spi comms +limitations +- Use the active contents lock and safe list iteration within the linux usbfs +code +- Add Ruby Api Example +- Automatic detect the small miners +- Update default modules from 3 to 4 +- Fix the temp max. we should use currect max temp +- add avalon2-cutoff options +- Enable the cutofftemp to Avalon2. ignore longer coinbase and longer merkles +stratum +- Fix the diff value used on MM firmware +- Mark pool as idle if stratum restart is failed +- Add hacky workaround for double list removal race in libusb +- Make the work given in benchmark mode deterministic on a per-device basis +- Rework the benchmarking code to use a deterministic set of work items with a +known number of diff share nonces at regular spaced intervals +- minion - restrict nonce read result size to ioctl() limit +- minion - must check temp when overheated +- minion - idle chips that hit >100C until back to 80C +- minion - report the chip/reg when aborting due to an invalid ioctl() size +- minion - all freq in Mhz but only convert when used +- minion - remove unused ioctl debug +- minion - command queue is now larger +- minion - check rolled in stale work cleanup +- Work stats should be based on device_diff not work_difficulty since non-shares +haven't been filtered out yet +- Prevent a segfault when writing a config file containing 'rotate' option +- minion - comment out HW debug message +- minion - roll work to reduce CPU +- minion - report init_freq in stats +- api - howoldsec is only used for USB +- minion - allow setting the frequency +- minion - disable iostats by default since it slows down mining +- minion - define frequency value table +- minion - report temp/cores/freq and handle temp formatting +- minion - item is undefined +- Rationalise diffs stored in the work struct and document them to avoid further +confusion +- Add basic API stats for nfu drivers to see how many submits each chip returns +- Add output direction for the EN0 pin on nfu driver +- Support power management optimisations in newer nf* firmware +- Support variable numbers of chips with NFU and BXM drivers +- Identify number of chips in nanofury devices and change name accordingly +- Rename nf1 driver to nfu in anticipation of support for more chips +- Make hashfast reset counter rise on old instances when inheriting the value on +new ones + + +Version 4.3.3 - 3rd May 2014 + +- Fix typo +- Work should be freed when aged, fixing a massive memory leak for bxf devices +- miner.php fix single rig summary/config field formatting +- miner.php fix single rig total formatting + + +Version 4.3.2 - 2nd May 2014 + +- Fix accounting bug with nrolltime drivers + + +Version 4.3.1 - 2nd May 2014 + +- upgrade some int to int64_t to avoid overflows in reporting +- Make reconnection messages more explanatory +- Stratum client.reconnect require matching URL +- Fix memory leak in submit_noffset_nonce +- Clean up any work that may not have been used in the work scheduler +- Avoid unnecessary deref now that it's done within discard_work +- Clean work pointers after one way usage functions +- Avoid unnecessary total_work_inc in generating local work +- Cosmetic fixes +- Fix idle bug, when redirected client can't auth +- Rename spond temp rate to asics total rate +- Build fixes +- Set the unique id only for usb devices with serial strings longer than 4 chars +long +- Use usb serial strings as unique id if devices have them +- Discretely identify the onestring miners as OSM +- Add bxf debugging option and osm led modes +- A1: modularize board selector / add initial CCR support +- A1: cleanup tca9535 logging +- A1: fix and extend PLL parameters +- A1: clean up compile warnings +- A1: use real level in hexdump +- Add identification for onestring miner variants +- Avalon2: Parser the power good signal +- driver-avalon2: this functions used on detect, which don't have thr setup yet + + +Version 4.3.0 - 18th April 2014 + +- Put sleep in spond hash instead of queue full function +- Remove unused function for when compiled without curses +- Fix typo +- Add temperature rate, front, rear and device temperature to spond API output +- Limit bxf sleep in bxf_scan to 100ms minimum for strings of many chips +- -Werror=format-security error on driver-bitmain.c +- Fix parameters passed with getblockhash +- Check the block hash with the proper command when looking for orphan chains +- syslog requires a facility ... in more than one place +- Shuffle windows headers included +- Adjust the bxf sleep time according to the number of chips detected +- Fix off by one error in bxf chip count when adjusting device size +- Recalloc correct pointer +- Make instructions associated with winusb error even more explicit +- Add midsing headers to cgminer source in Makefile +- Trivial style changes to mg proto parser +- Trivial style and warning clean ups on spondoolies driver +- Merge spondoolies driver patch +- Call any BXF device with 3-6 chips reported HXF +- Avoid derefrence when calling statline before on hfa device during init +sequence +- Calloc the info structures even on failed hfa reset to prevent later possible +dereference +- Load all hfa devices based on identification alone and defer init sequence +till mining thread init sequence to allow all devices to be recognised rapidly +but each device initialisation not delay others +- Do not do thread shutdown unless thread init succeeded +- Remove unnecessary check for thread_prepare function +- Recognise variations on BXF based on chip value returned in responses +- Provide helper function for recallocing memory +- syslog requires a facility + + +Version 4.2.3 - 3rd April 2014 + +- Decay the per device hashrates when only the watchdog is calling the hashmeter +- Fix parsing of config files failing on custom parsing +- Allow an arbitrary number of chips in the BXF driver, showing results from +each chip in the API and identify the hexfury, naming it HXF +- Disable toggling display by default and offer a --widescreen option to have +all the information on an extra wide display. +- Use OPT_WITH_CBARG for all custom parsing functions to allow their values to +be written generically when writing the config file from the menu. +- Provide a ccan variant OPT_WITH_CBARG that assigns the arguments passed as a +string and then performs the callback function on the string. +- Define strings to store special option parsing parameters leaving no +OPT_WITH_ARG missing args +- Correct the writing of special case options to the config file +- Provide support for writing anu freq from menu write option +- Update to diver-avalon2.c +- Generalise a lot more of the command line options simplifying the write config +function and making it write far more values unaided +- Use the general opt_set_charp functions for setting api parameters +- Json escape any strings written to the config file +- Store standard charp options when writing config files +- Add support for all the integer range options when writing the config file +from the menu +- Remove the --device and --remove-disabled options which don't work in a +meaningful way any more +- Make the bxf bits configurable on the command line +- Provide a --btc-sig option to optionally add a custom signature to the solo +mining coinbsae +- Compact gbt solo extra data and store the length, allowing it to be variable, +leaving room for a signature +- miner.php - Kano summary Pool Acc/Rej should be only work submitted +- miner.php add best share and gen formatting for pool summary +- miner.php - remove BGEN/GEN eval() errors from the web log +- miner.php allow optional fields when gen is disabled +- miner.php dont format missing gen fields +- miner.php make Summary a custompage +- miner.php allow uers and system lists of customsummarypages and add more +examples +- Fix getwork share submission +- Cosmetic fix to udev rules +- Put WU on the hashrate status to compact lines further +- miner.php show api/rig errors at the top of a customsummarypage + + +Version 4.2.2 - 29th March 2014 + +- Minor correctness fix for unnecessary free +- Clean up various curl build issues +- allow url based config files +- Frequency only needs 3 digits for cointerra statline +- Use the serial number as unique_id for cta display +- Make it possible to enable/disable the status window from switching via the +display menu +- We should not update the tv hashmeter time unless we're updating the hashrates +- Add cointerra devices to udev rules. +- Use hashfast unique id instead of number since the unique id is displayed +- Remove displayed space +- Left align the displayed unique id +- Use the hashfast opname as its unique identifier +- Display BF1 serial number as its unique identifier +- Display a unique identifier instead of a number if the device has one +- Use an alternating status display to return to a compact width of 80 +characters, allowing more information to be displayed. +- No need for looking for khash hashrates in summary any more +- Fix two potential minor mem leaks +- Fix memory leaks in setup and generate work for gbt solo. +- Fix off by one malloc size error +- Fix memory leak in update_gbt_solo +- Put sanity check on decay_time to prevent updates with no time +- Add 3 rolling average hashrates to API output for summary and devs. +- Use the extra status screen real estate better, displaying rolling 1/5/15min +average hashrates as well. +- Revamp the ageing crufty hashmeter code to have proper exponential decaying +values and store rolling 1/5/15min hashrates. +- Increment total_work under control lock. +- Trivial variable reuse +- Add support for other usb3 hubs on windows + + +Version 4.2.1 - 24th March 2014 + +- Fix various ava2 build issues generically +- Minimise the amount of heap memory allocations/frees when submitting gbt +shares. +- Make varint in gbt submission a stack object. +- Fix big endian problems with gbt submissions. +- Fix 32bit overflow on relative diff shown. +- ants1 - stop results read hard looping +- ants1 - slow down mining if overheat occurs +- miner.php allow gen before (bgen) and after (gen) grouping +- Change default solo mining to failing when no btc address is specified. +- Use upgrade cglock variants in get_gbt_curl +- Provide a cg_uilock to unlock the intermediate variant of cglocks. +- Use the one curl instance for all gbt solo operations, protecting its use with +a bool set under gbt lock. +- Only start block detection with gbt solo if setup succeeded +- One less block detection message +- Toss out the curl handle after each solo poll +- Don't reuse any curl handles for solo mining and break out of the lp thread if +the pool is removed. +- Make sure to only start the lognpoll thread once on gbt solo. +- Don't keep RPC connections open for solo mining since bitcoind doesn't like +having many persistent connections. +- GBT solo pools should be considered localgen pools. +- miner.php - speed up formatting and allow calc on gen fields +- Always show the address we're solo mining to to avoid confusion for when no +address is set. + + +Version 4.2.0 - 18th March 2014 + +- Fix missing htobe16 on windows and meaningless >u32 string warning. +- Software ntime roll for all hashfast devices. +- Silence harmless warning. +- Drop a failed restart icarus device to allow it to be rehotplugged if +possible. +- Work with more than one transaction. +- Kill gbt solo pools that don't respond to the gbt request 5 times +sequentially. +- Fix ser_number for no remaining val byte. +- Create a work item and stage it when updating the gbt solo template to allow +new block detection and restart code to work. +- Test block hash as well as block height when solo mining to ensure we haven't +been mining on an orphan branch. +- Fix transaction processing for gbt solo. +- Encode height using integer varint format. +- Make new block detection message not show in gbt solo from test_work_current +- Add block detection via getblockcount polling in gbt solo and update gbt +template every 60 seconds. +- Iterate over transactions twice to malloc only once when copying all the +transaction data. +- Update solo coinbase regularly and submit as gbt work +- Only show merkle hashes for solo mining in debug mode. +- Set correct flag for solo work. +- Generate gbt solo work emulating stratum work construction. +- Set the diff as a double sdiff from gbt solo data. +- Move swork.diff out of the stratum work section to be shared as sdiff. +- Generate a header bin from gbt solo as per the cached stratum one. +- Store strings similar to stratum's when decoding gbt solo +- Avoid allocing and freeing stratum strings that should be fixed length. +- Run parser through detect_stratum after stratum+tcp:// is force added +- Remove unnecessary header length calculation for stratum header binary and +only binary convert the correct length of the header. +- Share more fields between stratum and gbt +- Share coinbase_len variable b/w stratum and gbt and setup more gbt solo +parameters. +- Generate a valid coinbase and set nonce2offset for gbt solo +- Move scriptsig header bin conversion to setup gbt solo +- Create our own custom scriptsig base. +- Add helper functions for creating script signature templates and beging +building template. +- Do gbt solo decoding under gbt lock. +- Add more gbt variable decoding from gbt solo information. +- Store all the transaction data in binary form when using GBT +- When setting up solo mining, check validity of bitcoin address against +bitcoind +- Make pooled GBT mining use merkle bin optimisations slated for solo mining. +- Abstract out the merkle bin calculation for gbt solo +- Implement efficient merkle tree base from solo GBT information. +- miner.php custom formatting and row counter '#' +- Drillbit: Fix for underestimating hash rate from Bitfury devices +- Send per-core hashrates at regular ~5min intervals back to cta devices. +- Calculate the cta per core hashrate at 5 minute intervals. +- Check the bits of the correct core in cta bit count. +- Display the bit count along with the bitmap for each cta core in the API stats +output. +- Store and display the per core hashrate on cta relative to each work restart. +- Decrease the time we wait for unsetting a core on the cta bitmap to correspond +with the lower max diff of 32. +- Set max diff on cointerra devices to 32 which is still only 11 shares per +second but allows for earlier confirmation of per core hashrates. +- Keep track of when the last restart and work updates were triggered and +provide helper functions for knowing the time since then. +- hashfast make api stats field names unique +- Fix gcc longjmp warning in api.c +- Add a per-core hashrate to the cta API stats. +- miner.php support edevs and estats +- API - put edevstatus where it was supposed to be +- Icarus - allow timing mode to work with ANU and not slow it down +- drillbit - remove warnings +- drillbit - minor code tidy up +- Drillbit: Change language around 'void' to warning about limiter disabled +- Drillbit: Fix accidental over-counting of HW errors +- Drillbit: --drillbit-auto parameter for tweakable custom tuning of ASIC speeds +- Drillbit: Output warning if board reports void warranty +- Drillbit: Add Avalon & drillbit-autotune notes to ASIC-README +- Drillbit: Limit work sent out to 8 units in a single pass, was DoSing a full +double scroll +- Drillbit: Move drillbit_empty_buffer calls to only when errors occur, were +limiting performance on Windows +- Fix Windows bug with libusb_reset_device returning SUCCESS for disconnected +device +- Drillbit: Fix some warnings +- Drillbit: Add --drillbit-autotune option for device to dynamically alter clock +speed +- Drillbit: Fix typo in previous commit +- Drillbit: Remove default config in cgminer, rely on defaults in firmware +- Drillbit: Combine split USB transfer for sending new work, reduce overhead +- Drillbit: Add support for protocol V4, with device-agnostic board +configuration data +- Drillbit driver: Add support for Avalon-based Drillbit miners +- API - add edevs and estats - to only show enabled devices +- Check device data exists on a hfa instance before trying to reinit it. +- Print off what quadrant regulator failed if known in hfa driver. +- Reset all the stats on autovoltage complete in cta driver. +- Use correct diff instead of diffbits in cta driver. +- Whitelist all firmwares <= 0.5 on hfa for software rolling of ntime. +- Avoid a memory leak by reusing the ntime field when rolling stratum work. +- Clear the pipe bitmap on cta only when no share has occurred for 2 hours +instead of 1. +- Cta share_hashes should be added, and we can base it on device wdiff instead +of pool work difficulty for more accurate hashrates. +- Since the device runtime is now reset, the Raw hashrate entry in the cta API +output is no longer meaningful. +- Look for autovoltage returning to zero on cta driver and reset stats at that +point since the hashrate is unreliable till then. +- ants1 - cgminerise applog calls +- Default to stratum+tcp:// on any urls that don't have a prefix instead of +http. +- Trivial cta style changes. +- ants1 - fix/enable temperature checking and remove unneeded temp_old +- ants1 - move local cgpu variables to info structure +- ants1 use a klist to store work and copied work +- Simplify dramatically the cross-process cgminer locking through use of flock +instead of sysv semaphores. + + +Version 4.1.0 - 8th March 2014 + +- Correct fix for dev start time being adjusted for stat zeroing. +- Make per device stats work for average after a stat zeroing. +- Add an hfa-options command line that allows the clockspeed to be chosen per +device by name comma separated, with a function that can be expanded with more +options in the future. +- Off by one drv_rolllimit check against jobs +- Free the work that may be lost, leaking memory, in a failed hfa_send_frame +- Roll the ntime for work within the hfa driver for firmware we know doesn't do +it internally as an optimisation. +- Export the roll_work function to be usable by driver code and make it +compatible with rolling stratum work. +- Make opt_queue be respected as a maximum value for staged items. +- Disable mistakenly enabled lock tracking. +- api version update for HEX32 +- api.c - HEX32 type needs quotes +- Disable the MAX_CLOCK_DIFF check for newer hashfast firmwares since it's not +required. +- Store the hardware and firmware revision in the info struct for easy use in +the hfa driver. +- Only decrease the hfa clock rate if the device has been running for less than +an hour before dying. +- Change lack of op name response message in hfa driver +- Check for lost devices at every write/read in hfa_detect_common +- Make bxm bits configurable. +- Move avalon2 options to ~alphabetic position in help. +- Do a shutdown routine on bxm close. +- Provide support for 2 chips in libbitfury sendhashdata and enable the 2nd chip +on BXM devices. +- Remove unnecessary opayload and newbuf members of bitfury info struct. +- Add an spi add fasync command. +- Cope with older hfa firmware not even responding to op_name. +- Forcibly kill everything silently with an exit code of 1 should we fail to +cleanly shut down and use a completion timeout for the __kill_work in +app_restart. +- Make __kill_work itself also be a completion timeout. +- Generalise more of libbitfury for more reuse in both nf1 and bxm drivers. +- Remove redundant init components of bxm driver. +- Set default osc6 bits on bxm to 50 +- Enable the transaction translator emulator for bxm devices and use a dummy spi +tx the size of a normal payload. +- Store usb11 and tt flags as booleans in cgusbdev allowing them to be +discretely enabled as well as detected by the device data. +- Add bxm scan function and check spi txrx returns only as much as sent. +- Add init sequence to bxm detect one. +- Add a bxm specific txrx function for spi transfers. +- Add bxm close to bitfury shutdown switch. +- Add reset/purge/cshigh/low sequence to bxm init +- Add bitmode init to bxm open sequence. +- Add initial bxm opening sequence for detect one. +- Add identifiers for bxm bitfury devices. +- Clean up parse_method +- More gracefully break out of parse_notify on a corrupted hex string error, +checking the return value of all hex2bin conversions and being consistent with +using stack memory. Fix an unlocking error in cases of failure. +- AntS1 - add detection information to usbutils +- Enable Bitmain Ant S1 code and make it conform to cgminer requirements +- Make the cointerra displayed hashrate based on valid share generation. +- Convert and update values shown in the cointerra api output. +- Export the api_add_int16 function. +- Use a custom mystrstr function in cointerra driver. +- Add api_add_int16 to API functions. +- Add support for Bitmain Multi Chain and Single Chain and optimize the +efficiency +- Add support for bitmain devices +- Perfect function of BitMain Multi Chain +- Add support for Bitmain Multi Chain and Single Chain and optimize the +efficiency +- Add support for bitmain devices + + +Version 4.0.1 - 28th February 2014 + +- Refresh the log window on pool failure message at startup. +- Rework the pool fail to connect at startup to not get stuck indefinitely +repeatedly probing pools with new threads and to exit immediately when any key +is pressed. +- Use an early_quit function for shutting down when we have not successfully +initialised that does not try to clean up. +- Add more information to a hfa bad sequence tail event. +- Increase the work queue at the top end if we've hit the bottom as well. +- Set the work generation thread high priority, not the miner threads. +- Bringing each hfa device online takes a lot of work generation so only ever do +one at a time. +- Increase the opt_queue if we can hit the maximum amount asked for but are +still bottoming out. +- Keep the old hfa device data intact with a clean thread shutdown to allow it +to be re-hotplugged with the old information. +- Cope with the API calling hfa on partially initialised devices having no info. +- Show only as many digits as are required to display the number of devices. +- Cold plug only one hashfast device to get started, and then hotplug many to +minimise startup delays and possible communication delays causing failed first +starts. +- Send a shutdown and do a usb_nodev if hfa_reset fails. +- Null a device driver should thread prepare fail. +- Add a function for making all driver functions noops. +- Don't try to reinit a device that's disabled. +- Disable a device that fails to prepare. +- Check for lack of thread in watchdog thread for a failed startup. +- Make all device_data dereferences in the hfa driver safe by not accessing it +in statline before when it's non-existent. +- Add an option to disable dynamic core shedding on hashfast devices. +- Do not remove the info struct on a failure to hfa prepare. +- Detect an hfa device purely on the basis of getting a valid header response to +an OP_NAME query, leaving init to hfa_prepare which will allow multiple devices +to start without holding each other up at startup. +- Store the presence and validity of opname in the hfa info. +- api - buffer size off by 1 for joined commands +- minion - clean up statline +- Only break out of usb_detect_one when a new device is found. +- Use usb_detect_one in the hfa driver. +- Provide a usb_detect_one wrapper which only plugs one device at a time, +breaking out otherwise. +- Issue a usb_nodev on a bad work sequence tail in hfa +- Read in hfa stream until we get a HF_PREAMBLE +- Add shed count to hfa API stats output. +- Display the base clockrate for hfa devices with a different name to per die +clockrates to be able to easily distinguish them. +- Use op_name if possible first with hfa devices to detect old instances and be +able to choose the starting clockspeed before sending an init sequence, +reverting to setting op name and serial number as fallbacks. +- Make hfa resets properly inherit across a shutdown. +- Don't break out of hfa_old_device early if there's no serial number. +- Fix harmless warning. +- Allow the drop in MHz per hfa failure to be specified on the command line. +- Icarus - ignore HW errors in hash rate ... and fix detection of them +- Enable the hfa shed supported feature by default. +- Add to udev rules hfa devices for firmware writing. +- Remove ENV from hashfast udev rules. +- Add a --hfa-name command that allows one to specify the unique opname for a +hashfast device. +- Ava2 decode the voltage, get the temp_max +- Set the clock rate with a work restart instead of an init when changing to old +clocks for hfa +- Set opname on hfa devices without a serial number to a hex value based on time +to not overflow the field. +- Add op name to hfa API stats output if it exists. +- Set the actual op_name in hfa devices if cgminer is choosing it itself due to +it being invalid. +- Re-init an hfa device to its old data before setting up info structures as +their sizes may change. +- Remove the usb device whenever we do a running shutdown on hfa and do a +shutdown as the imitated reinit to allow it to hotplug again. +- Reset opt hfa dfu boot after it's used. +- Comment out windows only transfer on hfa startup. +- Clean up structures unused in case of all failures in hfa detect common +- Clear all structures should we fail to hfa reset on adjusting clock on a +hotplug. +- Set master and copy cgpu hash clock rate for hfa when dropping it on a +restart. +- Set the master hfa clock speed to lower when shutting down a copy. +- Do a clear readbuf on any hfa reset in case the device has not yet cleanly +shut down. +- Increase hfa fanspeed slightly more when it's rising in the optimal range than +falling. +- Always decrease hfa clock speed on a running shutdown and don't try sending an +init frame since it will be dropped regardless. +- Match hfa devices to old ones based on OP_NAME values before serial numbers if +possible. +- Read off the OP_NAME if it exists and is supported on hfa devices, setting it +to the device serial number or a timestamp if it is invalid. +- Updated hf protocol +- Check for an amount along with no error in hfa clear readbuf +- Hfa clear readbuf can return a nonsense amount when there's a return error so +ignore the amount. +- Running resets always cause a shutdown on hfa meaning the device will +disappear with modern firmware so always kill off the threads to allow +re-hotplugging. +- Reset the hfa hash clock rate to the old one if we find an old instance, only +setting the device id in hfa_prepare +- Keep the device_id on the original zombie thread for HFA in case of further +resets. +- Break out of hfa inherit if there is no device data. +- Inherit the hfa zombie instance after the device id has been allocated. +- The list_for_each_cgpu macro will dereference when there are no mining threads +yet. +- Make hfa hotplug inherit some parameters from a previous instance if the +serial number exists and is matching, avoiding dropping the clock on all +devices. +- Per device last getwork won't work if the device stops asking for work. +- Use the share_work_tdiff function in the driver watchdogs. +- Provide a helper function for determining time between valid share and getwork +per device. +- Store last_getwork time on a per-device basis. +- Limit the decrease of hfa clock rate on reset to the default clockrate. +- Base the hfa failure time on the current expected hashrate instead of a static +15 seconds. +- We shouldn't be trying to read from the hfa_send_shutdown function itself. +- Reset the icarus failing flag only when a valid nonce is found. +- Transferred value is corrupt on a NODEV error in usbutils. +- Set each miner thread last valid work just before starting its hash loop in +case there are delays at startup. +- Only memcopy *transferred data in usbutils if we have received only success or +a non-fatal error. +- Increase to 25 nonce ranges on icarus fail detect. +- Set icarus device fail time to be dependent on device speed to avoid falsely +detecting failure on slower AMU devices. +- Updated hf protocol header. +- Updated BE hf protocol header. +- Take into account shed cores on hfa devices when determining how many jobs to +send. +- Fix compilation error with two avalon types. +- Fix missing A1 files from distribution. + + +Version 4.0.0 - 21st February 2014 + +- Check for error from setfloatval +- Halfdelay cannot be larger than 255. +- Allow any arbitrary frequency to be specified for ANU devices and try to find +the nearest frequency when initialising it, reporting if the frequency is not +exactly as requested. +- Only show system libusb warning when appropriate during configure. +- Merge branch 'avalon2' of https://github.com/xiangfu/cgminer into +xiangfu-avalon2 +- Hfa cooling remains satisfactory down to a minimum fanspeed of 5% +- Give a nodev error if we have already set nodev in hfa clear readbuf to avoid +further usb comms attempts. +- Fix missing include +- Move bitmine options to alphabetic positioning. +- bab - missed a few 'DEAD's in last commit +- bab - use 'bad' instead of 'dead' as per the screen B: +- bab - roll work if possible to reduce CPU +- Update the per die hash clock data on a running reset on hfa devices. +- Set the per die clock on hfa to the known starting base clock instead of our +requested clock rate. +- Hfa device failure can be detected within 15 seconds so we should try +restarting it sooner to avoid tripping the device's own watchdog. +- Check return result of hfa clear readbuf to minimise error messages on device +failure. +- Put MHz into cta statline description. +- Send a work restart with every shutdown message to hfa devices to clear any +work that might be stale on the next restart. +- Store the hfa hash_clock rate and display it in the statline. +- Store the maximum board temperature for hfa devices and take that into +consideration when calculating the highest temperature as well as the dies. +- A1: CoinCraft-Desk driver variant +- Initial import of Bitmine.ch A1 SPI driver +- klondike ensure stats type matches +- avalon, bab, drillbit, klondike use more screen space rather than truncating +info +- Add hashfast fanspeed% to statline display. +- Move driver statline padding to cgminer.c, expanding width of maximum +displayable statistics and window width to add more info. +- Prune old stratum shares that we've seen no response for over 2 minutes to +avoid memory leaks for pools that don't respond about some shares. +- Add warning if system libusb is being added. +- Only run ./configure with autogen.sh if extra parameters are passed to it. +- Updated cointerra features. +- Add le16toh defines for platforms that may be missing it. +- Remove modminer bitstreams from distribution and replace with a README saying +what file needs to be added if modminer build is still desired. +- Use the simplelog function from usb_list() +- Add a simplelog function that does not log date and time. +- Use a unique usb_list function displaying only pertinent information when +listing usb devices from the menu. +- Abstract out the _in_use function to take different linked lists. +- Break out of linked list loop in remove_in_use in case we've gone over the +whole list. +- Check for hfa invalid hash clockrate after other error messages. +- Detect non-responsive icarus devices and attempt a usb reset before killing +them after 2 minutes of no hashes. +- Detect non-responsive bitfury devices and try a usb reset on them before +killing their instances off after 2 minutes of no activity. +- Allow hotplug interval to be changed from the USB menu. +- Prevent recursive loop in __is_in_use linked list walking. +- Add the ability to whitelist previously blacklisted usb devices from the menu. +- Use a bool in struct cgpu to know when a usb device has been blacklisted, +avoiding blacklisting it more than once. +- bab - ensure disabled chips are counted in the screen dead chip counter +- bab - only disable the chip once ... +- bab - short work list skip disabled chips +- api.c avoid incorrect gcc warning +- cgminer -h crash fix +- Add blacklisting as an option to the USB menu. +- Add a mechanism to blacklist a usb device from its cgpu. +- Add an option to the USB menu to list all known devices. +- Add an option to send a USB reset via the USB menu. +- Add a usb_reset by cgpu function to usbutils. +- Add warning for attempting to unplug a usb device that is already removed. +- Add USB Unplug option to USB management device management menu. +- Add enable and disable USB device functions to the menu. +- Add a [U]SB menu item, initially with just statistics per device, adding +device number to the device status window display. +- Reuse the cgpu temp entry for avalon and bitfury devices, changing avalon to a +damped value. +- Store the cointerra maximum temperature in the cgpu struct as an exponentially +changing value based on the maximum temperature. +- Reuse the cgpu->temp entry for max temperature in hfa driver. +- bab - disable chips that return only bad results +- Add driver for cointerra devices. +- Add Avalon2 (2U size machine) support +- miner.php - define a default rigport (that can be changed) and don't require a +port number in the rigs array +- miner.php allow links for rig buttons in tables and allow using the 4th IP +octet if no rig name - default disabled for both +- format fix and bad variable usage fix for --benchfile +- Allow running cgminer in benchmark mode with a work file --benchfile +- ANU frequency is in MHz, not hex. +- Remove bitfury devices from the usb list on shutdown in case they have stopped +responding but have not had a fatal usb error. + + +Version 3.12.3 - 8th February 2014 + +- Put the hashfast temperature into the cgpu structure so that it shows up in +the devs API call. +- We shouldn't block on no work situations directly from the getwork scheduler +itself. +- Revert "Make the pthread cond wait in the getwork scheduler a timed wait in +case we miss a wakeup." + + +Version 3.12.2 - 8th February 2014 + +- Adjust antminer U1 timing according to command line frequency set, fixing the +need for icarus timing on the command line. +- Read pipe errors that don't clear are worth attempting to reset the usb. +- Revert "Do away with usb resets entirely since we retry on both pipe and io +errors now and they're of dubious value." +- Make the pthread cond wait in the getwork scheduler a timed wait in case we +miss a wakeup. + + +Version 3.12.1 - 7th February 2014 + +- Document new features for antminer U1 and hfa devices. +- Add support for ANU overclocking. +- Increase hfa fanspeed by more if we're rising in temp above the target than if +the temp is staying the same. +- Add debug output when get_work() is blocked for an extended period and add +grace time to the device's last valid work to prevent false positives for device +failure. +- Issue a shutdown prior to a reset command for hfa devices and lock access to +reads awaiting the response if the device is already running. +- Do not register as successful a hfa init sequence that reports the clockrate +as zero. +- Show device info in noffset nonce share above target message. +- Widen lines in top menu to fit extra large share values. +- Only show one decimal place if pool diff is not an integer. +- Show serial number as a hex value in hfa verbose startup. +- Slowly remove work even if it's not being used to keep the getwork counter +incrementing even if work is not used and as a test that pools are still +working. +- Increase the maximum diff between hfa dies to 100Mhz. +- Show which hfa die is bringing down all the others when decreasing all the +clock speeds. +- Increase the decrease when temp has increased more and we want to decrease it +on hfa. +- Give device info with share above target message. +- Allow throttling of hfa dies more frequently and increasing of speeds less +frequently. +- Wait after sending a hfa shutdown to allow the device to properly shut down +before possibly sending it more commands. +- Minimise the die clock differences in hfa to no more than 50Mhz. +- Check for when errno is set on windows as well as the windows variant for +errors. +- Revert "Update to libusb-1.0.18" +- Disable fan/die clock control in hfa if the firmware does not support it, with +notification. +- Add ability to enter ANU frequency as a multiple of 25 from 150-500. +- Decrease hfa clock by 10 if a reset is attempted due to the device remaining +idle. +- ifdef out icarus options unused without icarus built in. +- Reorder command line options alphabetically. +- Add no matching work to hfa API output. +- Change various logging message levels in the hfa driver. +- Only adjust clocks if there is no restart in hfa to avoid 2 restarts back to +back. +- Ensure we iterate over all dies adjusting temperate for hfa by starting +iterating after the last die modified. +- Clamp initial hfa fanspeed to min/max if passed as parameters. +- Allow hfa fanspeed to be set via command line. +- Further relax the target temperatures on hfa driver, targetting 88 degrees. +- Try one more time to get the hfa header on init since it can take 2 seconds +for all 3 boards on a sierra. +- Update authors for removal of gpu/scrypt. +- Wait for 5 temperature updates in hfa before adjusting fanspeed. +- Have some leeway before starting to throttle hfa dies. +- Use increments of 10 when increasing hfa clock since it may not have 5 MHz +granularity internally. +- Only perform a hfa fan speed update if we have new temps to work with. +- Correctly measure the hfa max temp and smooth out the changes in its value. +- Choose better defaults for min/max/default fan settings for hfa driver. +- bab - reduce def speed, fix speed staying in ranges and report bank/chips in +ioctl() errors +- bab - add info about number of boards/chips to each Dead Chain +- These may not be longs (eg: OSX)... fo a safe cast to ensure. +- bab - add dead boards and dead chains to stats +- Add fanspeed to hfa api output and set initial fanspeed to 10% +- Add hfa fanspeed control to try and maintain a target temperature. +- API-README correct new text format documentation +- API allow multiple commands/replies in one request +- Add op commands necessary to control hfa fanspeeds. +- Add OP_FAN to hf protocol header. +- Always show the stratum share lag time in debug mode. +- Add stratum share response lag time to verbose output if it's greater than 1 +second. +- Add stratum share submission lag time to verbose information if it's over 1 +second. +- Check for more interrupted conditions in util.c and handle them gracefully. +- Send a ping to hfa devices if nothing is sent for over 5 seconds. +- Add OP_PING to hfa commands +- Display the hfa serial number as a hexadecimal value. +- Add the ability to display a hexadecimal 32 bit unsigned integer to the API. +- Limit all hfa restarts for temperature control to no closer than 15 seconds +apart. +- Allow the hfa temp target to be disabled by setting it to zero. +- Handle interruptions to various select calls in util.c +- Add sanity check for silly overflows in hfa die temperature readings. +- Add per-die throttling control for hfa driver based on each die's temperature, +issuing a suitable reset to maintain the temperature below a configurable target +temperature. +- Update hf protocol +- Do not memcpy in usbutils unless data was transferred. +- Send a full allotment of jobs to the hfa device after a restart instead of +reading the status. +- Export the flush_queue function for use by drivers. +- Remove wrong goto +- Remove the unqueued work reference when we discard work from get queued as +well. +- Wake the global work scheduler when we remove a work item from the unqueued +work pointer. +- Discard work that is stale in the get_queued() function, returning NULL +instead. +- Add a call to a driver specific zero stats function when zero stats is called +to allow each driver to reset its own stats as well if desired. + + +Version 3.12.0 - 29th January 2014 + +- Add support for AntminerU1 devices with the icarus driver. +- Add antminer U1 to comment in udev rules. +- Do away with usb resets entirely since we retry on both pipe and io errors now +and they're of dubious value. +- Retry on usb IO errors instead of faking success. +- Check that we've cleared the pipe error after a clear request, not the err +value which is unchanged. +- Update to libusb-1.0.18 +- Change hfa overheat limit to 90 degrees. +- Relax timeout in hf get header to 500ms to match the usb timeout. +- Minion - check/clear interrupts for all chips +- Set info work to null after it is freed in nf1 after a restart to prevent +double free later. +- The second_run bool in libbitfury should be per device. Microoptimise its and +job_switched usage, removing the unused results array for NF1 devices. +- Fix displayed diff when solo mining at >2^32 diff. +- bab - stop stale work accumulating +- bab - set the default SPI speed back to 96000 + + +Version 3.11.0 - 25th January 2014 + +- Add hashfast documentation to ASIC README +- Support the variable HFA naming throughout the driver notices. +- Set the global hfa hash clock rate to equal the lowest if we are lowering it +for a device reset since it may be re-hotplugged after failing reset. +- Decrease the hfa clock rate if it is overclocked and we have had to try +resetting it. +- Put a sanity check on the measured temperature in the hfa driver for obviously +wrong values. +- Avoid calling applog from within hfa statline before to avoid a deadlock. +- Add throttling control to hfa driver, configurable at command line, nominally +set to 85 degrees. +- Reset hfa device if no valid hashes are seen for 1 minute from the last work. +- Store when the last getwork was retrieved and display it in the API summary. +- bab - also report dead chip count screen +- Count share based hashes in the hfa driver with the device diff to get more +frequent updates. +- Only count 2/3 of the accumulated hashes on each pass through the hfa scan +work loop to smooth out displayed hashrate. +- bab add total history HW% to API stats +- Test valid nonces in the hashfast driver allowing us to check against the +target when trying to submit them. +- No point casting a double to a uint64 +- Convert the hfa hashmeter to one based on successful share return and display +the raw and calculated hash totals in the API. +- bab - remove libbitfury dependency since it requires USB +- Add description to hfa hash clock command. +- Add hfa board temperatures to API output. +- Wait for up to 0.5 seconds in the hashfast scanwork loop if no jobs are +required. +- Label HFA devices as B or S when their configuration matches babyjet or +sierra. +- Fix libbitfury being compiled in always by mistake. +- bab - spelling +- Add bab-options +- bab - tune the chip speed based on error rates +- bab record/report spie and miso errors +- Win32 falsely comes up as big endian pulling in the wrong hf protocol header. +- Remove unused components in hashfast driver. +- Check in all usb communication places for hashfast driver that the device +still exists. +- Do not send a usb reset on a usb read pipe error. +- Don't replace usb pipe errors with the pipe reset return code. +- Updated hf protocol header. +- The search for extra nonce is not worth performing in the hashfast driver. +- Add core address to hfa parse nonce debugging. +- Retry sending a frame once if it has failed in hfa_send_frame +- Add extra hfa usb init errors. +- Quiet now unused variable warning in hfa detect. +- Remove unused variable. +- Add board temperature to hfa debug +- Make submit_tested_work return a bool about whether it meets the work target +or not. +- Provide a helper function for determining dev runtime and use it in the +hashmeters used. +- Look for hfa usb init header for 2 seconds, then resend the init twice more +before failing. +- Really only set up the hfa crc table once. +- Generically increase the queue if we are mining on a pool without local work +generation each time we run out of work. +- Change new block detection message since longpoll is rarely relevant today. +- Change the default clockspeed bits on nanofury devices to 50 and add a command +line option to allow it to be changed. +- Use unused line at the top of the log window which often gets stuck +unchanging. +- Clear pool work on a stratum reconnect message. +- bab record/report spie and miso errors +- bab - cleanup old work for dead chips also +- bab add avg fail tests to API stats +- bab report bank/board/chip for dead and v.slow chips +- bab process all nonce replies per chip together +- bab reduce work delays +- bab record the number of E0s discarded +- bab - modified result parsing +- bab restore removed unused flag +- configure - correct minion name +- bab only scan valid nonce offsets +- bab record continuous (and max) bad nonces +- bab display Banks/Boards/Chips in the device window +- Modify thread naming to make them easier to identify +- bab reduce the work send delay +- bab remove results polling +- bab report SPI wait in seconds +- bab report missing chips at start and API +- bab ensure there's enough space for the nonce reply +- bab correct stats 'Send Max' +- bab allow long enough wait on ioctl() per board +- bab more I/O stats +- api.c 2014 +- api allow any size stats data +- bab add processed links which excludes expired links skipped +- bab report chips per bank, hw% and ghs per chip +- bab lock access to new_nonces to ensure correct reporting +- bab report V2 banks/boards during initialisation +- bab expire chip work +- bab use only k_lists and make work handling more refined +- klist - allow adding to tail +- bab remove old unused #define +- bab correct for master git +- correct klist reallocs +- klist lists for bab +- api.c correct DEVICECODE and ordering +- Maxchips should be 384 (16 chips/board 24 boards/controller) +- bab more detailed stats and delay less when waiting for a buffer +- api add data type AVG float 3 decimal +- bab - add V2 detect with bug fix in detect +- api.c set the actual version number to 3.0 +- API V3.0 unlimited socket reply size +- README update --usb +- Check for loss of device in usb read before any other code on the usbdev +- Change stratum strings under stratum_lock in reconnect and free old strings. +- Add mcp2210 compilation to want_libbitfury configs. +- Fix HF driver typo. + + +Version 3.10.0 - 9th January 2014 + +- Set the mcp2210 transfer setting only when it changes. +- Buffer sizes in nanofury device data are unnecessarily large. +- Only perform spi reset on init, not with each transaction. +- Remove spi_detect_bitfury at nanofury startup and fix incorrect refresh time. +- Use a simple serialised work model for nanofury +- Use bitfury_checkresults to avoid hashing results twice in nanofury. +- Export bitfury_checkresults in libbitfury +- Pass extra parameters for later use in libbitfury_sendHashData +- Avoid double handling bswap of the nonce value in nanofury +- Avoid unnecessary rehashing in nanofury nonce checking. +- Remove the unused portions of atrvec in the nanofury driver +- Age work in nf1_scan to avoid risk of losing a work item and leaking memory. +- bitfury_work_to_payload is double handling the data unnecessarily +- Default bitrate on nanofury should be 200kHz +- localvec should be only 80 bytes not 80 words +- Wrong init value for nanofury +- Remove unused rehash values from nanofury driver. +- Only update info work in nanofury driver when it's empty. +- Fill the appropriate type of usb transfer when we know if it's an interrupt +transfer instead of a bulk one. +- Use the internal knowledge of the usb epinfo to determine whether we should be +doing an interrupt instead of a bulk transfer, and do not send a ZLP if so, and +limit read transfer to expected size automatically. +- Avoid bin2hex memleak when we start getting nanofury nonces +- Set atrvec only once and use a local array for each device's work. +- Cancel any spi transfers on nf1 close +- Add bitfury detection loop to nanofury startup +- Move spi init code to libbitfury +- Remove inappropriate extra config reg in nanofury setup. +- Status 0x30 should never happen with spi transfers. +- Fix spi transfer data size transmission mistakes. +- Minor correctness change in spi_add_data +- spi_txrx should always send and receive the same size message +- Random libbitfury changes. +- Set value of gpio pins to low on closing nanofury. +- Fix more init sequence for nanofury. +- Add basic initialisation for nf1 devices +- Add basic nf1_scan function. +- Basic import of libbitfury functions from nanofury branch +- Import functions from nanofury fork for libbitfury +- Meter out spi sends to only 2 bytes at a time, offsetting according to how +much data returns. +- Use the usb read limit function for mcp2210 reads. +- Provide a way for usb reads to just read the size asked for with a limit bool. +- Get pin value after an nf1 spi reset. +- Make sure what we send in the buffer doesn't change during spi reset for +nanofury +- Remove all standalone gpio setting change functions in mcp2210 and just use +the one global setting function. +- Set gpio values in the one function with all values for nanofury. +- Provide a helper function for setting all mcp2210 gpio settings. +- Add a helper function for getting all mcp2210 gpio settings. +- Set all pin designations and directions in one call for nanofury and don't +bother storing their values in the info struct. +- Provide helper functions for setting all pins and dirs on mcp2210 +- Set all nanofury pin designations in one call +- Provide a helper function for setting all pin designations on mcp2210 +- Store the spi settings in a struct for nanofury devices. +- Check the received status in mcp2210 spi transfers and repeat a zero byte send +if it's in progress. +- Set the bytes per spi transfer prior to each mcp2210 transfer. +- Separate out the send and receive functions for mcp2210 and check response +value in return. +- Check that mcp2210 spi settings have taken and check the value of the pin +during nanofury setup. +- Don't set GPIO pin designations after initial setting in nanofury since the +direction and values will be changed. +- Provide an mcp 2210 set gpio input helper function that sets a pin to gpio and +input. +- Move the set gpio output function to a generic mcp2210 version from nanofury +which also sets the pin to gpio. +- Implement a nanofury txrx with a larger buffer and cycling over data too large +to send. +- Implement magic spi reset sequence for nanofury. +- Add more spi magic to the nanofury init sequence. +- Add lots of magic spi initialisation to nanofury. +- Export reused components of bitfury management into a libbitfury and use for +bab and bitfury drivers. +- More init sequence for nanofury and implement a close function that sets all +pins to input. +- Reword offset header handling in hfa_get_header +- Sanity check in hfa_get_header +- Add more checks in hashfast driver for lost devices. +- Change spimode and send more data in nanofury setup. +- Add basic setup comms to nanofury. +- Implement an mcp2210 spi transfer function. +- Set the initial spi settings for nanofury driver. +- Provide a helper function for gettings mcp2210 spi settings. +- Implement an mcp2210 set spi transfer settings function. +- Cancel any SPI transfers in progress in nanofury after initial setup. +- Implement an mcp2210 spi cancel function. +- Return only binary values for mcp2210 GPIO values. +- Set GPIO LED and power to high in nanofury driver. +- Implement initial part of nanofury init sequence for GPIO pin settings and add +output debugging of set values. +- Add helper functions for getting and setting mcp2210 gpio pin designations. +- Don't return an error in usb read if we've managed to get the whole read +length we've asked for. +- Use correct endpoint order for nanofury devices and read with a short timeout +on return loop from send_recv. +- Add mcp2210 helper functions for getting and setting one GPIO pin val and +direction. +- Create a generic gpio pin struct and add helpers for mcp get pin val and dirs. +- Check the receive msg of a send/receive cycle on mcp2210 matches the send +message. +- Add a set of usb commands to the usbutils defines for mcp2210 comms, and use +the same command name for send and receive. +- Create a generic mcp2210 send_rcv function. +- Include mcp header for bitfury and fix extra params in macro. +- Add basic SPI comms defines for mcp2210 and build rules for bitfury. +- Minion set some core defaults similar to final requirements +- minion compile warnings +- move driver-minion.c to main directory +- Minion with ioctl() stats, settings to attempt to emulate 21TH/s +- minion driver with results interrupt working +- tested working driver-minion.c without interrupts +- Working driver-minion.c v0.1 +- driver-minion.c compilable untested +- minion driver - incomplete +- Add minion driver into cgminer +- Add basic device detection and updated udev rules for nanofury devices. +- Remove GPU from share logging example. +- Don't keep resetting BXF clockspeed to default. +- If no pools are active on startup wait 60s before trying to reconnect since we +likely have the wrong credentials rather than all the pools being out. +- Discard bad crc packets for hashfast driver instead of trying to process them. +- Update documentation for modified avalon options syntax and document relevant +55nm details. +- Modify the auto tuning sequence to work with the 50MHz changes required to +work with 55nm Avalon. +- 55nm avalon requires the delays between writes reinstated for stability. +- Use an equation instead of a lookup table to set the frequency for 55nm avalon +allowing arbitrary values to be used. +- Make the result return rate low detection on avalon less trigger happy. +- Always send the bxf device a clockspeed after parsing the temperature in case +the device has changed the clockspeed itself without notification. +- Fix BXF being inappropriately dependent on drillbit. + + +Version 3.9.0 - 23rd December 2013 + +- drillbit asic - enable in api.c +- Fix trivial warnings in knc driver. +- Reinstate work utility based hashmeter for knc. +- drillbit format %z not valid on windows +- drillbit more formatting changes +- usbutils remove old code added back +- Memset the spi tx buffer under lock in knc driver. +- drillbit fix temp display to fit in standard space +- Drillbit formatting +- drillbit - use one drvlog and display dname before add_cgpu +- Keep orginal naming for the bitfury driver +- knc: Bugfix - good shares wrongly reported as HW errors. Root cause of the +problem: several work items were assigned the same work_id in the active works +queue of the knc driver. Thus when good nonce report arrived from the FPGA, +wrong work item was picked up from the queue, and submit_nonce evaluated that +as an error. Fix: Limit the work_id counter update rate. Update it only to the +number of works actually consumed by the FPGA, not to the number of works +send. +- Store per-chip submit information for bxf device and show them in the API. +- Check for removed bxf devices before trying to update work or send messages. +- api.c no decref if not json +- Minimise risk of nonce2 overflow with small nonce2 lengths by always encoding +the work little endian, and increasing the maximum size of nonce2 to 8 bytes. +- Change default hashfast timeout to 500ms. +- Ensure we can look up the work item in the hashfast driver or print out an +error if we don't. +- Drillbit source formatting - reindent and retabify +- Add ASIC count, temperature status to drillbit API output (closes #1) +- Many warning fixes +- knc: Do not include variable "last minute" data into the "last hour" per-core +stats +- knc: Make per-core statistics available through API +- Implement command line control of the bxf target temperature. +- Add a simple PID-like controller to bi*fury devices to dynamically alter the +clock setting to maintain a nominal target temperature set to 82 degrees. +- Add data to BXF API output. +- Add support for newer protocol bi*fury commands job, clock and hwerror, +setting clock to default 54 value, turning parsing into a compact macro. +- Look for the thermal overload flag in the gwq status message in the hashfast +driver and send it a shutdown followed by an attempted reset. +- Log message fixups +- Fix for "Timing out unresponsive ASIC" for pools which send early reconnect +requests, and then take a short time to send work (ie BTCGuild) +- Shorten initial config line, win32/pdcurses doesn't like long lines during +early logging +- Pull back the very long timeouts set in fe478953cf50 +- Fix bug where work restart during results scan could lead to bad device state +- Align device status lines same regardless of number of temp status or >10 +ASICs +- Tag log lines from brand new devices as DRB-1 until they are initialised +- Tag log lines as 'DRB0' rather than 'DRB 0', same as other places in cgminer +- Print a summary of the device settings at level NOTICE during initialisation +- Allow chosing device settings based on 'short' product names shown in status +line +- Allow per-device settings to use "DRBnn" as an identifier instead +- Issue an ASIC restart during a work_restart, removes spurious timeout messages +from ASICs and probably some rejected shares +- Check all results against all work instead of just taking the first match +(avoids some rejected submissions to the pool, ASIC can produce multiple +candidate results.) +- Fix memory leak caused by unnecesarily copied work +- Fix bug with find_settings not returning default value +- Set timeouts on write, set very long timeouts +- Merge drillbit driver + + +Version 3.8.5 - 10th December 2013 + +- Increase the BFLSC overtemp to 75 for fanspeed to maximum. +- Set bflsc cutoff temperature to 85 degrees and throttle 3 degrees below the +cutoff temperature. +- Only set LIBUSB_TRANSFER_ADD_ZERO_PACKET for libusb versions we know include +support for. +- Provide a helper function that can reset cgsems to zero. +- Add to cgminer_CPPFLAGS instead of redefining them. +- Attempt a libusb reset device on usb devices that have stopped responding. +- Replace deprecated use of INCLUDES with _CPPFLAGS. +- Remove more unused GPU code. +- Attempt USB device resets on usb read/write errors that will normally cause +the device to drop out. +- Quieten down jansson component of build. +- Cache the bool value for usb1.1 in _usb_write +- Initialise usb locks within usbutils.c instead of exporting them. +- Imitate a transaction translator for all usb1.1 device writes to compensate +for variable quality hubs and operating system support. +- Rationalise variables passed to usb_bulk_transfer. +- Unlink files opened as semaphores on releasing them. +- Remove user configuration flag from pll bypass enabling in hashfast driver. +- Provide an hfa-dfu-boot option for resetting hashfast devices for +reprogramming. +- Fixed one byte stack overflow in mcast recvfrom. +- Having changed C_MAX means we don't calloc enough for usb stats, off by one. +- Don't free the info struct on hashfast shutdown since it's still accessed +after a device is removed. + + +Version 3.8.4 - 1st December 2013 + +- Deprecate the usb usecps function and just split up transfers equal to the +maxpacketsize on usb1.1 devices. +- Retry sending after successfully clearing a pipe error. +- Drop logging of timeout overrun message to verbose level. +- Use a much longer callback timeout for USB writes on windows only as a last +resort since cancellations work so poorly. +- Use vcc2 in bflsc voltage displayed. +- Increment per core errors on false nonces in bflsc and add per core statistics +to api stats, removing debugging. +- Store a per-core nonce and hw error count for bflsc. +- Fix json parsing in api.c +- Add debugging to hfa driver for how many jobs are being sent. +- Shut down the hfa read thread if the device disappears. +- Add debug output saying what frame command is being sent in hfa driver. +- Revert "Disable USB stats which were not meant to be enabled by default and +add extra memory for a memory error when stats are enabled." +- Reset work restart flag in hfa driver since we may check for it again in +restart_wait. +- Add more op usb init errors for hfa driver. +- Perform basic displaying of hfa notices received. +- Add hfa op usb notice macros. +- Update hf protocol header. +- Use sync usb transfers in lowmem mode. +- Go back to allowing timeout errors on USB writes to be passed back to the +driver without removing the device in case the driver wishes to manage them. +- Initialise more values for the hfa data structures. +- A USB control error must be < 0 +- Simplify USB NODEV error checking to success only for writes and control +transfers, and success and timeout for reads. +- libusb error IO should be fatal as well if it gets through usb read and write. +- Allow IO errors in usb reads/writes to be ignored up to retry max times. +- Use correct padding for bxf temperature display. +- Initialise devices before attempting to connect to pools to allow their thread +prepare function to be called before having to connect to pools. +- Add hidden hfa options to set hash clock, group ntime roll and pll bypass, +fixing frame sent on reset to include extra data. +- Relax the timeouts for the slower usb devices on linux. +- Add big endian hf protocol header to Makefile +- Check for correct big endian macro in hf_protocol +- Use an absolute timeout in hfa_get_header to cope with buffered usb reads +returning instantly confusing the 200ms counter. +- Update hfa_detect_one to use the new detect function API. + + +Version 3.8.3 - 23rd November 2013 + +- Set the bitfury device start times from when we first get valid work. +- Fix stack corruption of zeroing too much in bf1 driver. +- Make usb_detect return the cgpu associated with it to check if it succeeds to +decide on whether to increment the device count or not. +- Set tv work start time for bxf driver. +- Age the bxf work items over 90 seconds, not the bf1 work items. +- Zero the read buffer in _usb_read to avoid stale data and only use stack +memory instead of using the bulkbuf since it is only used in _usb_read. +- Leave room for temperatures above 100 degrees and pad consistently for bxf +statline. +- Drop json stratum auth failed message log level to verbose. +- Change the processed value not the bufsiz in response to an end of message +marker. +- Don't lose data beyond the end of message in a usb read. +- Silence irrelevant warning. +- Only check strlen on end if end exists. +- Simplify the end of message detection in _usb_read and allow it to return +without doing another read if the message is already in the buffer. +- Increase work ageing time to 90 seconds for bxf driver to account for firmware +changes. +- Use the age_queued_work function in the bitfury driver. +- Provide a function to discard queued work based on age. +- The json_val in api.c is a borrowed reference, not a new one so don't decref +it. +- Decrement json references in api.c to not leak memory. +- line 2913 added urlencode +- With reliable writes to the avalon there is no need for the sleep delays +between writes. +- There is no need to limit usb write transfers to maxpacketsize and it's +harmful for large transfers on slow devices such as wrt routers. +- Disable USB stats which were not meant to be enabled by default and add extra +memory for a memory error when stats are enabled. +- Set limit and count to integers to not overflow during failed hotplug attempts +and then not trying again. +- Update api example compilation instructions. + + +Version 3.8.2 - 16th November 2013 + +- Add more verbose documentation to the readme files for windows users. +- Add more information on libusb failure to init telling users to check README +file. +- Add information on unloading cdc drivers on osx to README +- Prevent a deadlock with use of restart_threads by spawning a thread to send +the driver flush work messages. +- Set priority of various threads if possible. +- Add bxf data to api output. +- Do not hold the mining thread lock in restart_threads when calling the driver +flush work commands. +- Send extra work regularly to the bxf device and parse the needwork command by +sending the amount of work it requests. +- Allow messages to have arbitrary offsets in the bxf parser in case we have +lingering buffered data. +- Send the maxroll command to the bxf driver and store the value to see if we +need to update it. +- Add sending of flush command to bxf on flush_work +- Add flush and version commands to bxf start up, flush buffer and try to parse +version response string. +- Abstract out bxf recv message. +- Add extra bxf commands to usbutils +- Abstract out bxf send message to allow us to easily add extra commands. +- Don't run device restart code if the device is not enabled. +- Expand size of bitfury statline +- Various driver fixes for bitfury devices, including a flag from when first +valid work appears. +- Look up work results in bxf driver from correct variable. +- Correct incorrect error code in bxf driver for usb writes and add debugging. +- Add bxf details to usbutils. +- Implement a statline showing temperature for bxf +- Add api data for bxf device, sharing the hashrate function with bf1. +- Count no matching work as a hw error on bxf +- Add BXF to udev rules. +- Work id should be hexadecimal in bxf messages. +- Add unrecognised string debugging to bxf driver. +- Implement the main scanloop for bxf, trying to prevent it from ntime rolling +work if the work protocol does not allow it. +- Parse bxf work submits fully, submitting the results. +- Provide a function for setting the work ntime. +- Implement a skeleton parse bxf submit function. +- Use the bxf read thread to set the device target and send its first work item. +- Implement a bxf send work function and set update and restart functions to +sending new work since that's the equivalent for that device. +- Add temperature parsing to bxf driver +- Create and destroy a basic bxf read thread. +- Remove the buffer from bitfury info since it is only used on one pass in the +bf1 device. +- Add a rudimentary bxf detect one function. +- Rename all bf1 specific functions in the bitfury driver, using a switch to +choose correct function. +- Rename bitfury_getinfo to bf1_getinfo since it's unique to bf1 devices. +- Separate out the bf1 reset from bitfury reset. +- Store the bitfury identity in the info struct. +- BaB - updated tested OS comment +- Uniquely identify the BF1 and BXF bitfury devices. +- Remove the default libusb WinUsb pipe policies that don't suit us. +- Only set the winusb pipe policy if it doesn't match our requirements instead +of every transfer. +- klondike - dont try to flush if not initialised +- api.c trylock() add missing locklock +- Use our new zero length packet support directly in windows. +- Enable support for zero length packet on windows and auto clear pipe stalls. +- util.c: Decreasing reference count on allocated JSON obects to prevent memory +leak +- api.c: Release apisock on error in api() +- api.c: Release io_data->ptr when releasing io_data in io_free() +- We can't connect to a GBT pool at all with fix protocol enabled. +- Initialise the stgd lock mutex earlier to prevent dereferences when pool +testing occurs before it. +- Klondike support I2C USB layout also - as KLI +- Return error codes in avalon_read() if they're not timeouts. +- Break out of the avalon idle loop if we get a send error. +- Set avalon ftdi latency to just less than the time it would take to fill the +ftdi buffer at 115200 baud +- Update example.conf +- Only limit packetsize on usb out writes. +- We must chop up every 64 bytes returned on an ftdi chip, not just the first 2 +bytes so revert to parsing the data internally in the avalon instead of using +usbutils' simple ftdi parser. +- Only retry 3 times in hfa_reset. +- Only add_cgpu in hashfast driver once we have a real driver set up. +- Clean up properly if hfa_detect_common fails in the hashfast driver. +- --shares should be scaled to diff1 not absolute number of shares + + +Version 3.8.1 - 11th November 2013 + +- Revert "Send a zero length packet at the end of every usb transfer on windows +in case libusb internally has batched them into one maxpacket sized." + + +Version 3.8.0 - 10th November 2013 + +- api update version to 2.0 and remove GPU form API-README +-Remove now unused scrypt files. +- api.c remove all GPU/gpu references and correct code as required +- Rudimentary removal of GPU OpenCL and Scrypt features from api.c +- Reorder configure alphabetically for devices to compile and fail if no support +is selected to be compiled in. +- BaB update/format some comments +- BlackArrowBitfury early GPIO V1 driver +- Fine tune the reading of results in bitfury driver to not lose any across work +restarts or corrupt due to store results not parsed during restart. +- Send a zero length packet at the end of every usb transfer on windows in case +libusb internally has batched them into one maxpacket sized. +- Framework for ntime rolling, keep looking for OP_USB_INIT replies when other +packets received +- Configure source for a new BaB driver +- sha2 allow external access to some macros and the K array +- Fixed a math issue when reporting fan speed on the status line. +- Use the main hashlist to store work done in the bitfury driver and remove work +from the list by time, thereby fixing the duplicates at startup. Count hardware +errors for when no match occurs. +- Add a get and queue helper work function. +- Remove GPU mining code. +- Use libusb's own zero length packet support unless we have to emulate it on +windows since only libusb knows for sure if it's needed. +- Unlock the avalon qlock while sending tasks to not hold the lock for an +extended period. +- Sleep in avalon send task on return to the function to allow other code to +work during the sleep period. +- Send zero length packets when terminating a usb write aligned to +maxpacketsize. +- Do the driver flush in avalon code lockless since it can lead to deadlocks. +- Reset the work_restart bool after the scanwork loop in case the driver flushes +work synchronously. +- Only check for the stratum clean message if we have had a valid message. +- Get rid of the stage thread since all work can be asynchronously added now via +hash_push anyway. +- Remove the now incorrect faq entry regarding scrypt difficulty. +- Check for fatal read errors and break out of the read loop in avalon. +- Send errors are basically fatal in avalon driver so break out of the send +tasks loop. +- Make the avalon driver return -1 for hash count when usb fails, allowing the +main loop code to send it the shutdown flag. +- Break out of the hash work loops when a failure is detected instead of +dropping into mt disable. +- Use usbutils' own ftdi parser for avalon and the ftdir's own latency for +managing timeouts since we can wait on reads with completely asynchronous +reads+writes. +- Use usbutils' own cps function for slowing rate of usb writes on avalon. +- Fix build for no libcurl +- Check length before submitting sync transfers + + +Version 3.7.2 - 5th November 2013 + +- Clean up completely on avalon shutdown. +- Use cgsem timed waits in avalon driver to not miss any queued wake ups to +account for async messages coming during a flush work. +- Statline before is too long on icarus that doesn't have monitoring. +- Different windows+usb combinations respond with varying levels of reliability +wrt timeouts so use a nominal extra 40ms before cancelling transfers that fail +to time out on their own. +- Do all hotplug_process under the write mining_thr_lock +- Fix for opt_worktime on big endian machines. +- Correct set_blockdiff for big endian machines. +- Make sure cgpu exists in the restart threads loop in cases of hotplug etc. +- Treat usb write timeout errors as unrecoverable. +- Transfer errors are filtered out in usbutils now so no need to look for them +in NODEV checks. +- Remove now unused entries from struct cg_usb_device +- Do not double up with checking for end of timeout measurements in usb +read/write. +- Do get_work in fill_queue without holding other locks. +- Initialise usb after all the locks and conditionals are initialised. +- Use only a trylock in flush queue to prevent deadlocks. +- Add a wr_trylock wrapper for pthread rw lock write trylock. +- Scale diff for scrypt when testing for block solves. +- Fix for non curses build. + + +Version 3.7.0 - 4th November 2013 + +- Use WRITEIOERR macro check for all usb writes. +- Always use a usb read buffer instead of having to explicitly enable it. +- Force unlocking of the console lock on restart to avoid corrupting the console +state when we finally quit. +- Never wait indefinitely for a pthread conditional in the hash_pop loop in case +the work scheduler misses the last wakeup. +- Make hash_pop signal the work scheduler each time it waits on the conditional +that it should look for more work. +- Discriminate between libusb transfer errors and regular libusb errors and make +sure to capture them all. +- Always read a full sized transfer for bulk reads. +- Deprecate preferred packet size functions in usbutils since they're unhelpful. +- Copy known transferred amount back to buffer for usb reads instead of +requested length. +- Treat timeout errors on usb writes as IO errors. +- Ignore iManufacturer from bitfury devices to support bluefury as well as +redfury. +- Add more debugging info for when usb details don't match. +- Look for timeout overruns in usb read/write. +- Use an int for usb_read/write to identify overruns. +- Use the callback timeout as a safety mechanism only on windows. +- Instead of using complicated sleeps to emulate characters per second on usb +writes, submit only as many characters as can be transferred per usb poll of +1ms, and use timeouts in bulk transfers, cancelling transfers only as a +failsafe. +- Remove discarded work from quota used. +- Display works completed in summary and API data. +- Store how many work items are worked on per pool. +- Make each pool store its on reference for what the most current block is and +fine tune management of block change in shared pool failover strategies using +the information. +- Rationalise use of current_hash to a single hex string the length of the +previous block and display only the first non zero hex chars of the block in the +status window. +- Update uthash to latest. +- show_hash doesn't know the size of the string so hard code the max size. +- Remove as many initial zeroes as exist on share display, abstracting out a +hash show function to use across different submission mechanisms. +- Add missing endian swap functions for 64bits. +- Sanity check for absurd target setting and divide by zero. +- Abstract out conversion of a 256 bit endian number to a double, correcting +errors and use it for determining any magnitude share diff. +- Avoid the extra generation of a byte flipped hash2 in struct work and directly +use the LE work hash. +- Add a sanity check to avoid divide by zero crashes in set_target +- Calculate diff from target accurately for all 256 bits. +- Set a true 256bit binary target based on any diff value in set_target() +- Provide a copy_work_noffset function for copying a work struct but changing +its ntime. +- Make calls to flush queue and flush work asynchronous wrt to the main work +loops. +- Share is also above target for submit noffset nonce. +- Use round for displaying current pool diff. +- Use round for stratum share diff display instead of floor. +- Use round instead of floor for displayed pool difficulty. +- Allow arbitrary diffs to be tested against nonces via a test_nonce_diff +function. +- Abstract out the rebuilding of hash2 in work. +- Share is above, not below target, when it doesn't meet it. +- Add the ability to add uint8 and uint16 entities to api data. +- Use a non blocking connect with a 1 second select timeout when initiating +stratum to allow us to iterate over all IPs returned by getaddrinfo in round +robin DNS pools. +- Minor style changes to output. +- Revert two different hash_sequence(_head)'s to one variable, use +HF_SEQUENCE_DISTANCE in both places +- Remove duplicate HF_SEQUENCE_DISTANCE() macro, and duplicate hash_sequence +from info structure +- Change SEQUENCE_DISTANCE() macro to HF_SEQUENCE_DISTANCE() +- Structure changes for OP_NONCE, add big endian header +- klondike - initialise stat_lock +- klondike - better to unlock locks than to lock them twice :) +- Add copyright notice to knc driver. +- Trivial style changes to knc driver. +- Improve performance of work generation by optimizing hex2bin and bin2hex +- klondike - change options to clock and temptarget only +- klondike - fix another uninit dev warning +- klondike - downgrade 'late update' but add an idle detect - and correct error +levels +- klondike - fix isc uninit warning +- Use a mutex to protect data in the knc structure, to prevent loading more work +during a flush, and unlock and return to main between calls to get_queued_work. +- Use the existing device_data for knc state data. +- Only count successful nonces as hashrate in the knc driver. +- Fix trivial warnings in knc driver. +- Add KNC to api +- klondike - drop the device for hotplug if it's unresponsive +- usbutils - usb_nodev() allow a driver to drop a device +- klondike - single 'shutdown' and ensure it happens +- klondike remove SCNu8 - unsupported on windows +- Correctly calculate sleep_estimate in usbutils that may have been preventing +usecps from working. +- Use a sanity check on timeout on windows. +- Better HW error count; disable permanently those cores which fail often +- KnC driver: knc-spi-fpga ASIC driver +- Fixup jansson & libusb include paths when using separate build directory +- 'llround' is more suitable here than 'roundl' +- Silence warning if MAX/MIN is already defined +- Remove prebuild ccan/opt dependencies +- Reinstate block solve testing. +- Dramatically simplify the calculation of blockdiff. +- Simplify the set_target function, allowing it to work properly for fractional +diffs. +- Merge hashfast driver +- Merge KnC driver + + +Version 3.6.6 - 26th October 2013 + +- Remove inappropriate extra locking in _usb_transfer_read + + +Version 3.6.5 - 26th October 2013 + +- klondike - fix uninitialised dev bug +- Adjust the binary ntime data in submit_noffset_nonce even when there is no hex +ntime string for eg. gbt. +- Put an entry into the work struct telling drivers how much they can roll the +ntime themselves. +- Only set libusb cancellable status if the transfer succeeds. +- Remove the applog on miner threads dying to prevent deadlocks on exit. +- Do one extra guaranteed libusb event handling before testing if there are any +pending async usb transfers. +- Use a linked list for all usb transfers instead of just cancellable ones. +- Provide a mechanism for informing drivers of updated work templates for +stratum and gbt mining. +- Add cancellable transfers correctly to the ct_list +- Check for presence of thr in icarus get nonce for startup nonce testing to +work. +- Use cancellable usb transfers in the icarus driver to avoid having to loop and +poll when waiting for a response and to speed up work restart response time. +- Add a usb_read_ii_timeout_cancellable wrapper +- Add usb transfer cancellation on shutdown and documentation regarding where +cancellable transfers are suitable. +- Use cancellable transfers on bitfury device. +- Cancel cancellable usb transfers on work restart messages. +- Don't bother having a separate cancellable transfer struct for usb transfers, +simply include the list in the usb_transfer struct. +- Add wrappers for usb_read_cancellable and usb_read_timeout_cancellable +- Specifically set the cancellable state for it to not be uninitialised in the +usb transfer struct. +- Alter the usb cancellable list only under cgusb_fd_lock write lock. +- Pass the cancellable option to _usb_read options to decide on whether to add +usb transfers to the list of cancellable transfers. +- Create a linked list of potentially cancellable usb transfers. +- Don't attempt to disable curses or print a summary during an app restart to +prevent deadlocks. +- Keep the libusb event handle polling thread active until there are no async +usb transfers in progress. +- Keep a global counter of how many async usb transfers are in place. +- Perform libusb_submit_transfer under the write variant of cgusb_fd_lock +- klondike - error condition handling +- Avoid entering static libusb directory if --with-system-libusb is enabled. +- Minor opencl build corrections. +- Enable dynamic linking against system libusb --with-system-libusb +- Modify Makefile to only include opencl related code when configured in. +- Convert opencl to need to be explicitly enabled during build with +--enable-opencl +- Implement a cglock_destroy function. +- Implement a rwlock_destroy function. +- Implement a mutex_destroy function. +- Add usb command name to critical libusb error reporting. +- Use windows' own higher resolution time and handlers allowing us to have +higher precision absolute timeouts. +- Fix lldiv error in windows cgminer_t calculation. +- miner.php correct sort gen field names largest to smallest +- api ... the code related to device elapsed +- api add device elapsed since hotplug devices Elapsed is less than cgminer +Elapsed +- Drop usb buffering message to debug logging level. +- Do the ntime binary modification to the work struct when submitting an ntime +offset nonce within submit_noffset_nonce +- Code cleanup and improved documentation +- Improvements to support for BitBurner boards +- Convert libusb transfer errors to regular libusb error messages to allow for +accurate message reporting. + + +Version 3.6.4 - 18th October 2013 + +- Fixing the memory leak for remaining semaphores means we can go back to using +async transfers on other OSes with our own timeout management again. +- Use the forcelog function on shutdown to cope with indeterminate console lock +states due to killing of threads. +- Add a forcelog variant of applog which invalidates any console lock to force +output. +- Send pthread_cancel to failed completion_timeout that has timed out. +- Simplify queued hashtable by storing unqueued work separately in a single +pointer. +- bflsc use getinfo chip parallelization if it is present +- bflsc - fix brackets so [Chips] isn't always null +- Remove unused variables. +- Use cgcompletion timeouts for the unreliable shutdown functions on kill_work. +- Fix cgcompletion return code and free on successful completion. +- Provide a cg_completion_timeout helper function for unreliable functions that +takes arbitrary functions and parameters and reliably returns. +- Perform sync transfers on shutdown to allow final transfers to complete. +- Destroy cgsems used after transfers to not leave open files on osx. +- klondike rewrite work control +- allow __work_complete() access +- miner.h allow devices to tv_stamp work + + +Version 3.6.3 - 17th October 2013 + +- API add 'MHS %ds' to 'summary' +- Optional lock tracking and stats via the API +- Speed up polling repeat again in usb poll thread and handle async after the +message to disable polling is complete. +- Revert to using timeouts on !linux since libusb leaks memory without them. +- Revert to libusb instead of libusbx + + +Version 3.6.2 - 17th October 2013 + +- Remove unused components of jansson +- Remove unused parts of libusb +- Work around older libtoolize that fails without top ltmain.sh not being +present during autogen +- Fix open coded use of autoreconf in autogen +- Update jansson to only build parts we require and suited to our build +environment. +- Initial import of jansson-2.5 +- Prevent further USB transfers from occurring once the shutdown signal has been +sent to prevent transfers getting stuck and libusb failing to shut down. +- Make the USB polling thread poll every second to potentially aid longer +timeout transfers. +- Set device_diff on work in get_work to not be missed with drivers that use +get_work directly. +- Convert icarus driver to hash_driver_work model. +- bflsc - also allow ' 0' in DEVICES IN CHAIN +- bflsc - allow a 0 in DEVICES IN CHAIN +- Add needed EXTRA_DIST for libusbx. +- Update libusbx configure.ac changes. +- Revert libusb Makefile changes from going to libusbx. +- Fix trivial libusbx warnings. +- Convert libusb-1.0.16-rc10 to libusbx-1.0.17 + + +Version 3.6.1 - 14th October 2013 + +- Emulate the libusb_control_transfer sync setup in our async variant. +- usbutils - make all libusb_error_name messages the same + + +Version 3.6.0 - 14th October 2013 + +- increasing max miners for avalon driver +- using separate identifier for bitburner fury boards +- changes to bitburner driver for bitburner fury boards +- hexstr is too small in test_work_current +- Windows uses errno for WSAETIMEDOUT +- Convert the usb callback function to using cgsem_t timed waits to avoid race +conditions with conditionals/mutexes. +- Give correct return code in cgsem_mswait +- Check for correct timeout error in cgsem_mswait +- Fix util.h exports for cgsem_mswait +- Implement a generic cgsem_mswait similar to sem_timedwait +- Use the one LIBUSB_ERROR_TIMEOUT for cancelled transactions since this error +is explicitly tested for in various drivers. +- Do not use locking on usb callback function pthread signalling to prevent +deadlock with libusb's own event lock. +- Use a write lock when performing any USB control transfers to prevent +concurrent transfers. +- Free a libusb transfer after we have finished using it to avoid a dereference +in usb_control_transfer +- Do not perform bfi int patching for opencl1.2 or later. +- Although async transfers are meant to use heap memory, we never return before +the transfer function has completed so stack memory will suffice for control +transfers, fixing a memory leak in the process. +- klondike - correct/reverse min/max stats +- api incorrect message name +- klondike - use a link list queue rather than a circular buffer - and add +timing stats +- Use a timeout with usb handle events set to a nominal 200ms and wait for the +polling thread to shut down before deinitialising libusb. +- Use stack memory for hex used in stratum share submissions. +- Use stack memory in test_work_current, avoiding a malloc/free cycle each time. +- Provide a lower level __bin2hex function that does not allocate memory itself. +- Convert the bitfury driver to use the hash_driver_work version of hash_work. +- Add a hash_driver_work function to allow for drivers that wish to do their own +work queueing and management. +- Convert all usb control transfers to asynchronous communication with our own +timeout management as well. +- Klondike - increase circular read buffer size +- Klondike - extra zero value and range checking in temp conversion +- klondike - display MHz also +- Make pthread conditional timeouts handle all bulk usb transfer timeouts +performing libusb_cancel_transfer, disabling timeouts within libusb itself. +- Avoid calling get_statline_before on exit to avoid trying to use it on drivers +in an indeterminate state. +- Avoid calling get_statline on exit. +- Add a small amount to the usb timeout before cancelling to allow for a regular +usb polling interval to pass. +- Do not attempt to clear a usb halt before sending the cancel message since all +transfers should normally be cancelled before attempting to clear a halt +condition, and only change the return message to a timeout if it's consistent +with a cancellation. +- Retry up to USB_RETRY_MAX times to clear a halt condition before failing. +- Show the error number as well as the description in erroring bulk transfers. +- Drop logging level for failed to connect to stratum to verbose mode only since +we hit it regularly. +- We are always dependent on libusb handling events so use the blocking +libusb_handle_events in the polling thread and use a bool to know if we should +continue polling. +- Use fractional hashrate return values in bitfury_scanhash to minimise the +number of times we return 0 based on hashrate so far to further damp out +displayed hashrate. +- Check for presence of driver name in DRIVER_COUNT_FOUND to prevent strcmp on a +null pointer when a driver is not built in. +- CMR allow sending flash and clock commands +- Kill off threads that have failed using hash_sole_work instead of just +disabling them. +- Make the bf1 getinfo size a macro +- Failing to add_cgpu in bitfury should be a terminal failure. +- Check return values when attempting to open a BF1 device and set the msg size +as a macro. +- Display errors on failed usb read and write and consider sequential IO errors +a permanent failure. +- Use libusb's own error name function instead of hand coding the error names. +- Limit ms_tdiff to 1 hour as a sanity check. +- Enable the usb buffer in avalon driver. +- Check for async transfer variants of error messages. +- Remove unused variables. +- Try switching pools if for some reason we end up with only idle pools and have +ended up current_pool set to an idle one. +- Check a pool is stable for >5 mins before switching back to it. +- Minimise the time between dropping the read devlock and grabbing the write +devlock to avoid tons of logging spam in the interim. +- Check for libusb transfer stall error to be consistent with async IO errors +returned for a halt condition. +- Check for continuous IO errors on USB and consider the device inactive if more +than retry max. +- Make the devlock a cglock in usbutils and only grab the write lock for +fundamental changes allowing us to send and receive transfers concurrently +without lock contention. +- Prevent overflows in us_tdiff and ms_tdiff. +- Change second initialise message on bitfury verbose mode. +- Submitting an ntime offset nonce needs to be done on a copy of the work +instead of the original so abstract out shared components as much as possible, +minimising strdups in copy_work and make submit_work_async work take copied +work, cleaning up code in the process. +- Provide a way for drivers to submit work that it has internally rolled the +ntime value by returning the amount it has ntime rolled to be added. +- Typo in configure.ac +- Remove unmaintained broken ztex driver. +- Icarus - use a data structure for I/O rather than magic numbers +- delete old tracked ccan/opt/*.o files +- klondike correct cvtKlnToC() temperature calculation +- klondike - correct 1st reply debug based on define +- klondike - debug dump structured replies +- klondike - avoid division by zero if maxcount is unexpectedly zero +- klondike store and report errorcount and noise +- klondike - fix chipstats api stats buffer overrun with 16 chips +- klondike add new nonecount only once +- klondike - report mh/s based on nonces found + put old estimate into API stats +- klondike use a memcpy +- klondike fix bracket tabs indenting +- api.c missing Klondike from ASIC list +- Klondike update code to current git +- Add 2nd CMR to 01-cgminer.rules +- Add Klondike to 01-cgminer.rules +- Klondike to main directory +- Klondike consistent code spacing +- Klondike update driver code to current git +- update firmware for 16 chips, add dist files +- beta final 0.3.0 release +- updated firmware, IOC method +- prevent nonces when not state W +- added driver config option support +- fixes for 300 MHz, fix K1 parts list +- update driver, docs +- update firmware & utils +- updated cgminer driver for 3.3.1 +- update firmware and driver, create new cgminer fork +- update klondike driver +- add cgminer driver file as-is +- Add API output displaying USB cancellations. +- Store statistics on how often we have to cancel async bulk transfers and add a +debug message whenever we do. +- Treat any unexpected timeouts waiting for async transfers as though there may +be a usb halt condition and attempt to clear the halt before cancelling the +tranfer. +- Remove zero packet flag on usb as it's unsupported outside linux and +unnecessary. +- Fake the libusb transfer timed out message if we force cancel it with our own +async functions. +- Use asynchronous transfers for all bulk transfers, allowing us to use our own +timers and cancelling transfers that take too long. +- Add libusb error warning message when significant error occurs. +- Icarus CMR2 detect FPGA setup +- Disable bitfury device thread on it disappearing. + + +Version 3.5.0 - 29th September 2013 + +- Add magic init sequence required on BF1 devices to get them mining on windows. +- usbinfo.devlock is only ever write locked so convert it to a mutex +- Icarus remove unneeded opt_debug tests due to applog being a macro +- Icarus - CMR shouldn't wait the full timeout due to handle sharing +- We should only yield once in cg_wunlock +- Provide a function to downgrade a cglock from a write lock to an intermediate +variant. +- Deuglify use of _PARSE_COMMANDS macro expansions. +- Deuglify use of usb parse commands macro in usbutils. +- Use the driver add commands macros in api.c to avoid individually listing +them. +- Separate out asic fpga and opencl drivers in the driver parse commands macro +for use individually as needed. +- Use macro expansion in usb_find_devices to avoid explicitly listing them all. +- Use macro expansion to iterate over all the drivers without explicitly writing +them out in usbutils.c +- Iterate over the bitfury offsets in order of decreasing likelihood. +- Reattach the kernel driver on linux on usb_uninit. +- Attach the kernel driver on failure to usb init on linux. +- libusb kernel driver operations are only available on linux. +- There is no need to get the external prototypes for drivers in cgminer.c any +more. +- Remove unnecessary gpu_threads initialisation. +- Put avalon last in the sequence of adding drivers to prevent it trying to +claim similar chip devices on startup. +- Use macro expansion to iterate over all device drivers without needing to +explicitly code in support in all places. Pass a hotplug bool to the detect() +function to prevent opencl trying to hogplug GPUs. +- Forward declare all device drivers in miner.h avoiding the need to export them +everywhere else. +- Add a noop function for driver detect when it's missing. +- Reuse the DRIVER_ macros to avoid having yet another definition for DRV_ +- Use macro expansion to generate extern device_drv prototypes. +- Create a macro list of drivers to enable easier addition of further drivers. +- There is no point setting the BF1 preferred packet size to the maximum since +it will do so automatically. +- icarus ensure all cmr interfaces are initialised properly +- usbutils - fix USBDEBUG warnings +- Remove unnecessary steps in communicating with BF1 and just use USB interface +1. +- usbutils - usb_bulk_transfer fix the buf/data fix +- usb_bulk_transfer - use the allocated buffer +- Set preferred packet sizes per interface on BF1. +- usbutils allow PrefPacketSize per endpoint +- Remove magic control sequences on open/close on BF1 and just flush the read +buffers. +- Check return codes in getinfo and reset and fail as needed in BF1. +- Check return code for bitfury_open and release resources properly on failed +initialisation. +- Abstract out flushing of interrupt reads in BF1 devices. +- Perform interrupt read after close message on BF1 as per serial close. +- Perform interrupt read flush as per serial open on BF1 devices. +- Add information for 2nd USB interface on BF1 devices and choose interface 1 +for bulk transfers. +- usbutils - bulk transfer copy test fix +- usbutils - add USBDEBUG for usb_bulk_transfer +- Add more read_ii variants to usbutils. +- Name remainder of BFU usb commands used. +- Use submit_tested_work in bitfury driver to avoid unnecessarily re-testing the +work for validity. +- Abstract out work submission once it's been tested, to be used by drivers that +do their own internal validity testing. +- Store the hash2 array in struct work for further reuse. +- usbutils - which_intinfo not requried +- Use the test_nonce function within submit_nonce and store the uint32 +corresponding to hash2 37 for further use. +- usbutils - interfaces must all be on one handle - ep implies the interface +- avalon stats use exact type +- Only set share diff if we've confirmed it's a share first. +- Update ASIC-README for bitfury devices. +- Use an array of offsets when checking nonces in bitfury_checkresults +- Limit the duration we wait for reads in BF1 based on time already elapsed to +account for other delays such as work restart messages or out of work. +- Minimise size of serial string we copy in BF1 stats to avoid overflow. +- Implement basic API stats for BF1 and increase array of results to check for +the rare straggling result. +- Space debug output for bf1 to separate from numerals. +- Abstract out the bitfury open close and reset functions and use them on +reinit. +- Rename BF1 devices BF1 +- Check for work restart, breaking out early after usb reads in BF1. +- Do not lose the first sets of results from BF1. +- There is no point checking for results from the next round of work on BF1. +- Last result returned by BF1 is an end of results marker so ignore it. +- restart_wait should return 0 if thr_restart is true. +- Remove unused code by bitfury driver since current driver uses serialised +scanhash. +- Meter out return of estimated hashes in BF1 to smooth out visible hashrate. +- Optimise inner scanhash loop for bf1. +- Add yet another backup work for triple buffering of work in bf1 to account for +extra late results returned and don't check nonce offsets which appear to never +return. +- Name the work request and result usb commands for BF1 +- Define a mandatory upper limit to waiting for reset and data on BF1 based on +full nonce duration. +- Decrease usb buffering to verbose logging. +- Add in first draft for a serialised work model sending/receiving data for BF1 +devices. +- Add complete close sequence to bf1 as it happens on serial. +- Provide a bitfury identify function for bf1. +- Reliably extract BF1 information at startup and reset the device. +- Add commands for getting BF1 bitfury info +- Add magic BF1 bitfury open and close control sequences. +- Add BF1 detection code to bitfury driver. +- Create basic placeholders for bitfury driver code. +- Add bf1 device information to usbutils to enable device detection. +- Add basic defines for building for bitfury devices. +- Add redfury device to udev rules. +- avalon: display the FPGA controller version on API +- pool_active uninitialised_var rolltime +- Use macro expansion to only need to define usb enums and commands in one +place. +- usbutils saving incorrect overflow buffer +- ignore libusb.la and *.lo on linux +- icarus support CMR with no extensions +- usbtils - interfaces dont work yet in libusb windows so disable for that only +- Provide a --disable-libcurl config option to build support for stratum mining +only. +- Fix the api-example.c compile under Linux +- usbutils - only release the device once - for the first intinfo +- usbutils set_interface is no longer valid +- ubsutils interfaces much each have their own handle +- usbutils kernel_detach should use the interface number +- usbutils - allow the driver to change which_intinfo +- Reset quotas on load balance for all pools at the same time to avoid running +out during selection and unintentionally dropping to fallback. +- Break out of select pool from a common point for appropriate debug messages +and to avoid further tests. +- usbutils correct/reverse CMR product numbers +- usbutils specifically track handles and interfaces +- change drivers to use usb_interface() - required for multi interface change +- usbutils - allow a device to use multiple interfaces (and better var names) +- Cast -1 to (char) to cope with different default char types on ARM. + + +Version 3.4.3 - 13th September 2013 + +- Put corefoundation and iokit separate in ldflags for darwin. +- Add rules for libusb Makefile.am building on osx +- Add flags for building libusb statically on osx. +- Find the greatest common denominator in quotas and use the smallest number of +consecutive work items per pool in quota load balance mode to smooth hashrate +across pools with large quotas. Give excess quota to priority pool 0 instead of +pool 0. +- Avoid dynamically adding stack memory for nonce2 in the stratum send thread +and check the pool's nonce2_len will not cause an overflow. +- Add subdir-objects to automake options. +- Use inet_addr instead of inet_network to fix windows build. +- Remove unused pbase variable. +- Add support for socks4/4a proxies with stratum, and drop back to socks4 +support via the global --socks-proxy command to not break previous +configurations. +- Fix warning on mingw build. +- Only show long-poll message in pool summary if it's not using stratum. +- Increase the time for the waiting for work message to be given to be greater +than that required for a pool swap in the scheduler which is set to 5s. +- Change message in status when using a balanced pool strategy to notify if +there's a stratum pool as well. +- Use the --failover-only flag to have special meaning in combination with +load-balance mode to distribute any unused quota back to pool 0 to maintain +ratios amongst other pools. +- Display quota and allow it to be modified via the pool menu. +- Add API commands and modify output to support pool quota displaying and +changing. +- Change message in status when using a balanced pool strategy to notify if +there's a stratum pool as well. +- Add quota support to configuration files. +- Rotate pools on all failures to set a pool in select_pool. +- Use quotas for load-balance pool strategy. +- Provide a mechanism for setting a pool quota to be used by load-balance. +- Use the --socks-proxy option with stratum, changing it to defaulting to socks5 +and give appropriate message should it fail to connect. +- Cope with trailing slashes in stratum urls. +- Add more debugging messages when negotiating with proxies for stratum. +- Test specifically for socks5h in socks support for stratum. +- Add support for socks5 proxy with stratum +- Provide support for negotiating a stratum connection via http proxies. +- Connect to the proxy URL and port if specified for stratum sockets instead of +the pool directly. +- Extract any proxy url and port to be used by sockaddr if possible using +extract_sockaddr. +- Make extract_sockaddr set variables passed to it rather than pool struct +members. +- miner.php sort the mcast rigs so they are always in the same relative order +- miner.php allow sending the muticast message multiple times +- miner.php mcast ignore duplicate replies + + +Version 3.4.2 - 3rd September 2013 + +- take_queued_work_bymidstate should use a write lock. +- miner.php coding warning +- miner.php disable 'gen' by default +- miner.php allow formula generation of new fields +- miner.php add doctype +- miner.php remove incorrect echo +- miner.php optional error if not enough mcast rigs are found + + +Version 3.4.1 - 31st August 2013 + +- API mcast add a description option with miner.php +- Always use a maxpacketsize buffer in usb_bulk_transfer +- bflsc ensure getinfo cannot overflow it's storage buffer +- Don't decref json values in stratum parsing due to memory corruption. +- Use 64 bytes for all libusb control transfers. +- Skip dissecting opt->names in parse_config if it doesn't exist. +- Use an internal buffer in _usb_transfer_read in case the read is larger than +the buffer passed to it. +- ICA optional limit timing with short=N or long=N +- Revert to old custom tolines function since strtok_r is not portable. +- bflsc remove unused commented out code +- logging - code mistake +- logging - applogsiz() for large messages +- Provide base structures for getaddrinfo. +- Include string.h in bflsc driver. +- Get rid of linear removal of spaces in bflsc text parsing and use strstr +throughout instead. +- Use reentrant strtok in tolines() function in bflsc to avoid racing on +contextless calls. +- Show how small a too small result in bflsc is. +- Duplicate the buffer in process_results in bflsc since strtok modifies it +making debugging output limited to one line. +- Only process nonces in bflsc if the breakdown function succeeds. +- Ignore zero count messages in bflsc instead of trying to parse them. +- Return ok in tolines when it doesn't match inprocess message for bflsc. +- Remove inprocess line instead of deleting all following responses in bflsc. +- Change ok testing logic in breakdown() in bflsc and return if not ok at any +stage. +- Check the return value of tolines in bflsc driver. +- Use strtok to parse lines in bflsc driver. +- Add libusb-1.0 m4 directory and gitignore file. +- Properly convert from ranlib to lt_init in configure.ac +- Make autoconf always build for libusb. +- More autoconf fixes. +- Unconditionally build jansson statically from the cgminer source tree. +- Only test for all usb devices once in configure.ac +- Fix various libusb warnings and possible bugs on linux build. +- Add make clean and maintainer-clean to autogen +- Remove examples from libusb Makefile and generated autoconf files. +- Fix libusb subdirectory builds. +- Remove cached files from libusb autoconf on running autogen.sh +- Remove unused HAVE_LISBUSB macro and use USE_USBUTILS everywhere. +- Use direct auto* files to avoid failure of autoreconf +- Remove unused and maintainer cleaned files +- Show RT_LIBS in ./configure output. +- First import of libusb-1.0 +- bflsc xlinkstr use snprintf +- Fix win32 build. +- Use take_queued_work_bymidstate in the bflsc driver to avoid the rare chance +repeated results come back from the same work item. +- Provide a funcion that looks up queued work by midstate and then removes it +from the device hash database. +- Fix no -rt library on darwin. +- Update included jansson to v2.4 +- Fix OSX build. +- Provide an osx fix for cgtimers and a fallback to timevals for all other +platforms !linux !win32 !osx. +- Move two more timer functions out of define macros to enable them to be used +by future osx code. +- cgtimer_sub is now the same since cgtimer_t should be the same on all +platforms. +- miner.php fix missing global +- Only count submitted nonces as diff1shares if they're valid. +- Substantially raise the maximum avalon frequency for water-cooled, over-volted +designs. +- Compile MCast.java with an old java +- API Multicast sample MCast.java+MCast.class +- BTB show C/MHz/mV for device +- api.c remove unused reply string +- api.c fix mcast debug message bug +- miner.php implement API Multicast handling to automatically find your local +net miners +- API mcast only reply to remote IP's that are allowed access +- Initial API Multicast response v0.1 to find cgminer APIs +- Use timespecs on windows as cgtimer_t to capitalise on the higher resolution +clock changes. +- Abstract out the conversion of system time to an lldiv_t in decimicroseconds. +- Use our own gettimeofday implementation on windows for it to be consistent +across ming builds and higher resolution. + + +Version 3.4.0 - 21st August 2013 + +- Use stack data for HW error% in avalon stats. +- Add avalon HW error% to stats and only show BTB variables if avalon is a BTB. +- Check for cnx_needed on each loop through wait_lp_current. +- Return positive for cnx_needed when no_work is true. +- Stratum is used more often so test for it first. +- Reorder support names alphabetically. +- Only display the no pool work message once if there are multiple waiters in +hash_pop +- Provide a message and set a bool when no work is available from any pools and +when it resumes again. +- We don't want to continue into the hash_pop function if the getq is frozen. +- Only report threads in and out in queued work devices across a get work since +the rest happens asynchronously and the get work is what the device might be +waiting on. +- Thread reportin and out can be static non inline. +- usbutils cps sleep_estimate is not an underestimate +- usbutils add cps stats estimates +- Provide cgtimer_sub helper functions. +- Provide cgtimer_to_ms helper functions. +- Rename cgsleep_prepare_r as cgtimer_time to get time in cgtimer_t format and +call cgsleep_prepare_r as a macro for cgtimer_time +- Use the reentrant cgsleep functions for usecps in usbutils. +- TimeBeginPeriod and TimeEndPeriod do not add significant overhead when run the +entire time for cgminer so avoid trying to maintain balanced numbers of them for +specific time calls to simplify code. +- Replace all references to the old n*sleep functions with the equivalent +cgsleep_*s replacements. +- timeGetTime uses huge resources on windows so revert to using timevals for its +implementation of cgtimer_t +- Quotient/remainder error in ms division. +- Only grab a queued work item if we successfully grab the lock to submit work +in bflsc_send_work +- BTB get version from Firmware +- Carve out the unused portions of sha2 implementation. +- Import Aaron D. Gifford's fast sha256 implementation. +- Increase the que_low watermarks on BFLSC for they are too low to keep the +device busy on scanwork loops. +- Provide cgtimer_to_timeval helper functions. +- Provide a timeval_to_cgtime helper function to reuse values. +- Check for thr->work_restart in restart_wait. +- We should be using que_low to decrease scan sleep time in bflsc. +- Prepare sleep time on bflsc if no dev needs work yet to avoid busy waiting. +- Simplify cgsleep code for windows by using a typedef for cgtimer_t that +resolves to clock resolution, using that internally. +- On windows use the higher accuracy timegettime function to really get 1ms +clock and timer accuracy. +- Use the cgsleep reentrant function to sleep for bflsc between read results to +account for time taken to perform reads. +- Use 100ms delay between checking for results on all bflsc devices as the +buffering of results mean checking more frequently just wastes CPU and causes +more lock contention for only marginally better latencies. +- Fix missed endtimeperiod in overrun timer on windows. +- Make cgsleep_us_r take an int64_t for us. +- Make the cgsleep functions build on windows. +- Use the cgsleep reentrant function in avalon_send_task. +- Use the reentrant cgsleep functions within the avalon_send_tasks function. +- Set high resolution timing on windows within the cgsleep functions. +- Use the reentrant cgsleep function to time sleeps on reading from avalon. +- Provide reentrant versions of cgsleep functions to allow start time to be set +separately from the beginning of the actual sleep, allowing scheduling delays to +be counted in the sleep. +- Make the nmsleep and nusleep functions use the new cgsleep functions +internally till functions are migrated to the new cgsleep API. +- Add a ms_to_timespec helper function, and create a cgsleep_ms function that +uses absolute timers with clock_nanosleep to avoid overruns. +- Add rt lib linkage to enable use of clock_nanosleep functions with older +glibc. +- Add necessary time header include to avalon driver. +- Do a sleep of the full duration it would take to do all the work using +clock_nanosleep in avalon_send_tasks to avoid sleep overruns before polling to +see if it's ready. +- Add a timeraddspec helper function. +- Provide a us_to_timespec helper function. +- Use the us_to_timeval helper function in the avalon driver. +- Provide a us_to_timeval helper function. +- Use timeval_to_spec helper in avalon driver. +- Add helper functions to convert timespec to timeval and vice versa. +- simplifying buffer full check +- forking bitburner write thread function +- making sure original Avalon is unaffected by BitBurner changes +- changes to queueing strategy for BitBurner boards +- Do not poll in avalon_get_results without sleeping if we have finished parsing +a full result. +- Add c to ambient temperature display for avalon driver. +- BTB allow up to 1400mV as per firmware limits +- avalon for timeout allow d='calculate it' and fix uninitialised +- Use cloned work when finding avalon results since another thread can discard +the work item while it's in use. +- Provide a variant of find_work_bymidstate that returns a clone of the found +work. + + +Version 3.3.4 - 14th August 2013 + +- API/miner.php add some % fields +- Nonce2 stratum submission is not working with nonce2 lengths >4, revert the +buggy __bin2hex function and use bin2hex. +- The write thread in avalon is only ever actually woken up by timeout so remove +the write semaphore and use a simple sleep poll. +- Fix warning. +- Interrupting reads on the avalon to start writes loses data so remove the +cgsem_post in the read code. +- Add room for the null byte at the end of the nonce2 string on stratum share +submission and zero the allocated ram. + + +Version 3.3.3 - 13th August 2013 + +- Only perform the bin2hex on nonce2 data if it's required for stratum +submission, thereby removing the last conversion of that type from stratum work +generation. +- Create a work data template when receiving stratum notification, allowing a +simple memcpy of the merkle root avoiding more hex2bin conversions on each work +generation. +- Export the workpadding char in miner.h +- Avoid a potential overflow should a pool specify a large nonce2 length with +stratum. +- Avoid one more hex2bin in gen stratum work. +- Rename work gbt_coinbase to coinbase to be in line with pool variable name. +- Perform merkle bin hex2bin on stratum notify to avoid doing it on each work +generation. +- Reuse just the one pool coinbase variable in stratum, avoiding more string +functions and storage in gen_stratum_work on each work generation. +- Rename pool gbt_coinbase variable to coinbase to combine it with the stratum +coinbase data. +- Use a nonce2 offset variable for both gbt and stratum to consolidate +requirements on work generation. +- Merge pull request #474 from kanoi/master +- util.c update quit call for new functions +- use correct define for OSX in util.c +- miner.h inline semaphores increase information on failure +- util.c expand quit to show file/func/line +- Merge remote-tracking branch 'conman/master' +- Cache as much of the gbt coinbase as possible to avoid doing unnecessary +hex2bin conversion on every work generation with gbt. +- We should be using a cg_wlock initially in generating stratum and gbt work +before downgrading the lock. +- Add the ability to downgrade a write variant of the cglocks. +- Fix --scrypt being required before scrypt intensities on command line or not +working at all via config files. +- Cache the hex2bin of pool nonce1 in stratum, avoiding hex2bin on each work +generation. +- Cache the binary generation of coinbase1 and 2 on stratum, avoiding a hex2bin +of coinbase1 and 2 on each work generation. +- cgsem - increase information on failure +- avalon init write_sem before use + + +- 3.3.2 - 9th August 2013 + +- Fix uninit variable warnings. +- usbutils - force check every combination +- Fix warning. +- Recreate curses windows on windows when a device is hotplugged to allow window +resizing without crashing. +- Update copyright notice. +- Limit intensity range according to whether scrypt is in use or not. +- Do not allow benchmark mode to be used with scrypt. +- Add a --bflsc-overheat command which allows you to set the throttling +temperature for BFLSC devices or disable it. +- Move bflsc defines to a header file. +- avalon allow frequency to be set via the API +- BTB voltage management via the API - and set default on startup +- Avalon BTB allow partial work to be transferred +- avalon_cts use correct buffer +- miner.php format Best Share +- remove unnecessary memcpy +- using more concise description +- using usb_ident +- forgot a return +- changes to Avalon driver for BitBurner boards +- Revert "Sleep after sending icarus work to emulate working at 115200 baud." +- api correct timeout stat display +- usb timeouts - min/max also +- log USB timeouts in API stats +- usbutils report failed timeouts +- usbutils ensure stats macros are using the macro arguments +- Check for negative wait time in socket_full. +- Fix extra argument passed to statline before. +- Adjust socket wait timeout in recv_line according to how long we've already +waited to avoid a 60 second wait dropping to 1 second due to a blocked socket. +- usbutils use a heap buffer for bulk read rather than stack +- usbutils only one bulk transfer call per stat +- set device_drv function noops when first add_cgpu +- usbutils - in init only change the config if needed +- bflsc nonce per work item stats +- bflsc increase flush count to handle parallel work +- force type checking on curses +- logging - size check sprintf +- usbutils - size check all sprintf +- cgminer - size check all sprintf +- size check get_datestamp/get_timestamp and remove unused cgpu->init +- make all statline overflow safe +- WU only needs +2 width +- Check for a timeout in avalon_scanhash and post to the write sem if we receive +one. +- Decay result count in avalon more slowly to not falsely detect idle periods as +low result return rates. +- Count the number of miners idled in avalon to account more accurately for when +its result return rate is too low. +- Fix potential dereference when starting avalon with all new work. +- Convert the decay_time function into one that truly creates an exponentially +decaying average over opt_log_interval. +- Only throttle avalon clockspeed in avalon_auto in non optimal temperature +settings if the fanspeed has reached maximum. +- Reinstate more aggressive <2% HW error target for avalon-auto +- Set avalon fan min and fan max to PWM values instead of percentage. +- Provide an --avalon-freq command line to give a valid range of frequencies for +avalon in auto mode. +- Set the avalon idle frequency to lowest if avalon auto is enabled and we have +an overheat condition. +- Decrease avalon frequency in auto mode if we are unable to maintain the +temperature in the optimal range. +- Don't count invalid nonces as hashrate for bflsc. +- Use a more conservative upper limit of 1% for hardware errors with avalon auto +frequency. +- Allow the avalon fanspeed range to be passed as parameter on the command line, +default to 20-100% +- Just display A: and R: for difficulty accepted and rejected to preserve screen +real estate and decrease decimal places for WU. +- correct device DR: and remove global U: +- Update all screen A/R to instead use DA/DR and device U to WU +- miner.php add ASC fields +- GPU fan rpm display 9999 when it overflows +- bflsc get volts stats needs its own GETVOLTS +- Support all avalon frequencies on the command line. +- Move to slightly more relaxed timeouts for avalon. +- MMQ turn on cps delays +- bflsc x-link header different to documentation +- Reset the other auto counters in avalon when idling a device. +- usbutils/icarus include more locking to usbdev access +- Icarus turn on cps delays by default +- usbutils cps correct time measurement + + +Version 3.3.1 - 25th June 2013 + +- Add an avalon-auto option which enables dynamic overclocking based on hardware +error rate for maximum effective hashrate. +- Add an --avalon-cutoff feature which puts the avalon idle should it reach this +temperature, defaulting to 60, re-enabling it when it gets to target +temperature. +- Change default avalon target temperature to 50 degrees. +- usbutils - incorrect test for * in bus:dev +- Redo +1 fix in bflsc. + + +Version 3.3.0 - 24th June 2013 + +- Add an --avalon-temp option to allow a user specified target temperature. +- Demote no matching work message to verbose logging only on avalon. +- Make the fan control on the avalon a simple PID controller with a target +temperature of 45. +- Demote bflsc hw error messages to verbose logging only. +- bflsc - handle xlink timeouts by having generic IO functions +- Demote the invalid nonce warning to log info. +- Ignore iManufacturer for BFLSC devices since the device name will still match +and some unbinned chips are missing it. +- sc_count shouldn't be +1 in bflsc. +- Use the info timeout for read_nl in getidentify bflsc. +- Add a usb_read_nl_timeout macro. +- bflsc try getinfo twice +- set MSG_ASCUSBNODEV always defined +- Hard code the preferred packet size for AMU, BLT and ICA. +- API V1.26 update ASIC support +- Icarus enable the read buffer for the detect nonce +- Support new overclocking speeds for avalon: 325, 350 and 375 +- undo icarus show errno, put it as debug in ubsutils +- icarus add errno to rerr and werr +- Sleep after sending icarus work to emulate working at 115200 baud. +- Use the nusleep function for sleeping after sending work in avalon. +- Show an integer only for diff if it is one. +- Set the avalon preferred packet size to 512. +- Reinstate the maxPacketSize determined by the end descriptor but allow the +driver to override it. +- Only update hashmeter if we have done hashes or haven't updated longer than +the log interval, fixing a us/ms error. +- Use only one cgsem in avalon signalling when the write thread should commit +work by reading the status bytes off during an avalon_read, minimising the +number of usb calls and resetting from only one place. +- Change avalon no valid work message to no matching work to match API +terminology. +- Use low latency usb transfers on the avalon, sleeping up to half a buffer's +worth only if no data is returning to increase hashrate, abolish lost work and +decrease CPU. +- Minimise the sleep times in avalon read to avoid result loss. +- Use a half nonce range before cycling through avalon's scanwork to ensure it +gets a chance to fill work if time is tight for the write thread to signal a +wakeup. +- Temporarily limit usb transfer sizes to 512 till we provide a way for each +driver to choose the upper limit. +- Increase watchdog sick time to longer than it takes for a pool to be detected +dead. +- Limit USB transfers to the max size reported by the descriptors. +- Increase the BFLSC timeout to allow the maximum number of results to be +returned for BAS in time. +- Decrease BAL and BAS latency to be just larger than one result read. +- disable curses device resize that crashes on windows +- BFLSC latest firmware has its own thermal cutoff set to 90, so use the same +value in case we have an old firmware that isn't throttling by itself. +- Drop watermark low limits for bflsc. +- Set the fanspeed on bflsc to max if we don't know the temperature. +- Use a low watermark for queueing mandatory work on bflsc instead of zero. +- Only mandatorily grab the bflsc mutex on submitting work when the queue is +empty. +- Adjust bflsc v2 watermarks. +- Only increase sleep time on bflsc if the queue isn't emptying at all over the +sleep duration. +- Fix warning. +- bflsc yet more API stats +- bflsc add some more API stats +- bflsc correct firmware matching +- bflsc correct comment +- Fixed Commands with No params +- bflsc driver support for v2 firmware +- Odd Issues +- Fixed Python Example +- Added Python Api Example +- Added Python Api Example +- Multiplier fail for microseconds vs milliseconds when updating hashmeter in +hash_queued_work. +- Only make threads report in/out across the actual driver code and update their +status on reporting out as well as in. +- usbutils initialise close key/sem +- usbutils cleanup linux semaphores on release +- Difficulty should be unconditionally byteswapped, not swapped to big endian. +- We should be setting cancelstate, not canceltype when disabling it for usb +locking. +- Pthread cancel state should be set to disable on usb DEVLOCK. +- Fanauto on bflsc is Z9X according to the source code, not 5 as per the draft +protocol document. + + +Version 3.2.2 - 16th June 2013 + +- Record and report USB pipe errors via API stats +- Suspend stratum connections when we know they've failed and don't try to recv +data from them once the socket no longer exists. +- Pipe error is quite common on usb3 so drop logging to verbose level only. +- ocl.c fix applog warnings on windows +- applog/quit fix GPU errors created +- usbutils - DEVLOCK other usbdev access +- applog usb device list can be > LOGBUFSIZ +- fix windows log warnings +- logging remove extra added +- remove varargs from logging/quit/in general as much as possible +- Don't yield when downgrading a cg ilock. +- Don't yield on grabbing the read lock variant of cglocks. +- Off by one error in device count for display. +- Don't display devices beyond the most_devices count in the curses status. +- Only display as many device rows as the maximum live existed at any time. +- usb lock out use cg locks +- usb lock out transfers during open/close +- Add error message to libusb pipe error +- Differentiate libusb control transfer pipe errors from transfer errors since +they're not fatal. +- Create a usb_bulk_transfer wrapper for libusb_bulk_transfer to cope with pipe +errors. +- Only show efficiency in pool information for pools that don't support local +work generation. +- Create a pool_localgen bool function for testing when a pool can generate work +locally. +- ignore file that is generated on Macs +- compile unix code on Mac OS X fixes not finding the config file in $HOME +- Use mining start time for device MH/U calculations +- Decrease the sleep duration before reading in avalon to not let the read +buffer overflow. +- Failure to read and write on pseudo semaphores on apple happens routinely on +shut down so should not be a quit error, just a warning. +- Unlock usb dev lock in the same place in usbutils. +- Sleep if the avalon buffer is empty and we've requested a read to allow the +write thread to take precedence. +- Yield after releasing a lock in case we are on a device with limited CPU +resources. +- Add the cgpu_info structure before avalon reset. +- Tidy up DEVLOCK/UNLOCK to have consistent use of the pstate variable without +needing brace level match. +- Icarus driver elaspsed timeout shouldn't be just USB I/O +- usbutils avoid leaving devlock locked when thread cancelled +- MMQ fix nodev failure caused by changes +- ubsutils lock all access to nodev and cgusb +- USB make device_path handled by usbutils +- tidy up free in device detect functions +- USB control creation and free of cgpu +- Add FAQ regarding Work Utility. +- Throttling the BFLSC at 80 seems to prevent generating garbled responses of +higher temps. +- Return after failed bin2hex conversion in bflsc. +- Demote failed hex2bin result to LOG_INFO and check return result in +driver-bflsc to avoid doing find_work_by_midstate. +- Set BFLSC fan speed coarsely to keep it under 60 or auto as per specs saying +it tries to stay below 60. +- Limit usbutils LATENCY_STD to 32ms to keep transfers under 512 bytes. +- Move macro definition to bflsc driver +- Use a longer timeout for retrieving bflsc details. +- Add a usb_read_ok_timeout wrapper to cope with slow init'ing devices. +- cgsem_post after creating the thread info +- Fix build. +- Use cgsem structures instead of the flaky pings in the work queue to start +mining threads and remove the unused thr_info_freeze function. + + +Version 3.2.1 - 7th June 2013 + +- Shorten the avalon statline to fit in the curses interface and show the lowest +speed fan cooling the asic devices. +- Set usbdev in usbutils after checking for nodev to avoid trying to access a +dereferenced value. +- AMU usbstatus correct name from enable UART +- Icarus AMU enable the UART +- Only libusb close if libusb release succeeds. +- Failed reads and writes on cgsem_post and cgsem_wait should be extremely rare. +- Implement cgminer specific cgsem semaphores to imitate unnamed semaphore +behaviour on osx which does not support them. +- Set cgusb->buffer to NULL when doing usb_buffer_disable. +- Temporarily fix apple not having semtimedop by ignoring the timeout value. +- BFLSC enable buffered USB reading +- Icarus use buffered USB reading +- bflsc & icarus use usb_ftdi_set_latency +- usb_ftdi_set_latency LOG_ERRs if called incorrectly +- add usb_ftdi_set_latency +- usbutils optional read buffering +- Set the avalon read transfer latency to avoid sleeping when no data is +returned after very short latency settings. +- correct bflsc BFLSC_BUFSIZ max calculation +- Fix build for !curses +- restore max code - since timeout is unsigned +- compile warning - remove unused max +- usb set FTDI latency higher to minimise status bytes +- Check for zero timeout on _usb_write. +- Check for zero timeout in usb read. +- Define a minimum polling time based on frequency of mandatory updates of ftdi +responses at 40ms. +- Sleep right up to the timeout instead of the first half if we find ourselves +polling in _usb_read +- Enforce half timeout sized sleeps in usb_read if we find the device is not +respecting libusb timeouts to avoid polling frequently. +- Add more ASIC documentation. +- Update README +- Remove start device limitation on log window size to allow it to get larger +with hotplugged devices. +- Switch logsize after hotplugging a device. +- Change switch_compact function name to switch_logsize to be used for other +changes. +- Only adjust cursor positions with curses locked. +- devs display - fix GPU duplicate bug +- Do not hotplug enable a device if devices have been specified and the hotplug +device falls outside this range. +- Change the --device parameter parsing and configuration to use ranges and +comma separated values. +- basic copyright statement in API.java +- devs display - show ZOMBIEs after all others +- Modify scrypt kernel message. +- Check for pool_disabled in wait_lp_current +- usbutils semun use proper def for linux which fixes OSX also +- Check for pool enabled in cnx_needed. +- Icarus add delays during intialisation +- Update documentation. +- Update copyrights of modified files. + + +Version 3.2.0 - 31st May 2013 + +- Add FAQ about windows USB keyboards and hotplug interactions. +- Fix mingw build warnings in icarus driver. +- Make usb_ftdi_cts use the _usb_transfer_read function. +- Update ASIC-README with avalon info regarding default behaviour. +- Break out of idling loop in avalon_idle if the buffer is full. +- Provide some defaults for avalon if none are specified and do not try to claim +the device if it fails to reset with them and no options are specified. +- usbutils automatically track IO errors +- usbutils allow a short wait for resources to be released +- correct semaphore timeout comment +- Set the fanspeed to the nominal chosen for GPUs. +- Inverted sem_init logic. +- Document avalon options in ASIC-README +- Do avalon driver detection last as it will try to claim any similar device and +they are not reliably detected. +- Clamp initial GPU fanspeed to within user specified range. +- Use a counting semaphore to signal the usb resource thread that it has work to +do. +- Avalon fan factor is already multiplied into the info values. +- Get rid of zeros which corrupt display. +- Logic fail on minimum fanspeed reporting. +- Provide a workaround for fan0 sensor not being used on avalon and pad fan RPM +with zeros. +- Add ambient temp and lowest fan RPM information to avalon statline. +- Display max temperature and fanspeed data for avalon. +- Set devices to disabled after they exit the hashing loops to prevent the +watchdog thread from trying to act on them. +- Add avalon driver to hotplug. +- Shut down the avalon mining thread if the device disappears. +- Check for no usb device in usb_ftdi_cts +- Check for valid usbdev in _usb_read in case the device has been unplugged. +- Scanhash functions perform driver shutdown so don't repeat it. +- Change the opencl shutdown sequence. +- Send the shutdown message to threads and do the thread shutdown functions +before more forcefully sending pthread_cancel to threads. +- Use the cgpu_info shutdown to determine when to stop the avalon read and write +threads. +- Use semaphores to signal a reset to pause the read thread while the write +thread does the actual reset, making all writes come from the same place. +- Remove now unneeded fgpautils.h include from avalon. +- usb_transfer_read should also not play with the endianness. +- Use the USB wrappers for avalon, telling usbutils that we want the raw data. +- Use separate ep for avalon tasks vs avalon reset and do not loop in write +indefinitely. +- Remove unneeded function and checks in avalon write code. +- CMR handle baud options +- work_restart is reset within the queued hash work loop. +- Fix avalon shutdown sequence. +- Execute driver shutdown sequence during kill_work. +- Use nusleep in avalon_get_results in place of nmsleep. +- Provide an nusleep equivalent function to nmsleep. +- usb/ica add more (incomplete) CMR settings +- Give a buffer of perceived results in avalon during idle periods to allow for +results once it becomes active again. +- libusb_control_transfer are meant to be endian specific, but host endianness +so no conversion is needed. +- Reuse old MTX Handle +- usbutils check all memory allocation +- usb separate thread for resource locking and modified windows locking code +- Icarus report data direction with comms errors +- Set the read and write threads for avalon to not cancel within libusb +functions and wait for the threads to pthread_join on shutdown. +- Offset needs to be incremented after avalon reads. +- Make the avalon_read function parse the ftdi responses appopriately. +- Use the avalon read timeout to completion if no data has been read. +- wait_avalon_ready should only be used before writes. +- Ask for the correct amount to read in avalon get results. +- Spawn the avalon read thread first with info->reset set to discard any data +till work is adequately queued. +- Use direct usb read commands to avoid ftdi data being automatically cut off in +avalon reads. +- Do a simple usb_read_once for the avalon result from a reset command. +- Make sure avalon is ready to receive more usb commands before sending them. +- Implement avalon_ready and avalon_wait_ready functions for when usb is ready +to receive commands. +- avalon_read should not loop but just return whatever it has succeeded in +reading. +- Set avalon_info to device data void struct. +- Specify avalon in avalon_reset. +- First pass rewriting serialdev into direct usb dev for avalon driver. +- Define a cts equivalent for direct usb and use it for avalon driver full. +- Compile usbutils into avalon driver. +- Check results come in at least at 2/3 the rate they should be on avalon and if +not, reset it. +- Give a warning but don't reset if the avalon buffer is full early. +- Discard any reads obtained from the avalon get results thread during a reset. +- Differentiate initial reset in avalon from subsequent ones. +- Perform a mandatory reset if the avalon buffer signals it's full before it has +queued its normal quota of work. +- Wait till buffer is cleared after sending idle tasks to avalon before +returning from avalon_idle. +- Lock qlock mutex during reset from read thread in avalon to prevent more work +being sent till the reset is over. +- Reset avalon if we continue to be unable to send all the work items. +- Add avalon reset response to debugging output. +- Do a wait_avalon_ready before sending a reset code. +- Iterate over spare bytes in the avalon result returned from a reset request +trying to find the beginning of the reset. +- Idle avalon after reset. +- Check for nothing but consecutive bad results on avalon and reset the FPGA if +it happens. +- Make submit_nonce return a bool for whether it's a valid share or not. +- Unset the work restart flag sooner in avalon_flush_work to avoid re-entering +the flush work function and just reset the queued counter instead of rotating +the array to avoid runs of no valid work. +- Implement an avalon_flush_work function for work restarts. +- Shut down avalon read and write threads and idle the miners on closing it. +- Tighter control over work submissions in avalon allows us to use a smaller +array. +- Rotate avalon array to reset the queued count before releasing the lock so +work will always be available on next pass. +- Move avalon read thread start till after conditional wait, store idle status +in avalon_info and use it to determine whether an error is appropriate or not. +- Wait till the avalon_send_tasks thread has filled the avalon with idle work +before starting the avalon_get_results thread. +- Use AVA_GETS_OK macro in avalon_read. +- Do all writes on avalon with a select() timeout to prevent indefinite blocking +and loop if less than desired is written. +- Check explicitly that ava_buffer_full equals the macro. +- Send initial reset as an avalon task to remove avalon_write function. +- avalon_clear_readbuf is no longer required. +- Check for 2 stray bytes on avalon reset. +- Create a separate thread for handling all work and idle submission to the +avalon which messages the scanhash function it has completed to update +statistics. +- usbutils ensure it compiles without stats +- usbutils include transfer mode in usbstats +- Give the avalon get results thread name the device number as well. +- Make sure we're not adjusting temps on every successful work retrieval on +avalon. +- Count missing work items from behind a successful work read in avalon as well. +- Change message for work not found in avalon parser. +- usbutils handle bulk_transfer partial writes +- Simplify debugging and only discard from avalon read buffer if at least one +full result has been discarded. +- Only display discarded bytes in avalon if they're not used as nonces. +- Only loop once through avalon_parse_results, but do so after timeouts as well. +- Only debug and move ram if spare bytes exist in avalon buffer. +- Remove off by one error. +- Inverted logic. +- Add more debugging to avalon reads. +- Convert unsigned size_ts to ints for parsing avalon messages. +- Cope with not finding nonces in avalon parsing gracefully by not overflowing +buffers. +- Adjust avalon temp values on one lot of valid nonces from the parser. +- Created a threaded message parser for avalon reads. +- Avalon_wait_write is not effective during resets so do it after going idle. +- Send only a single byte reset. +- Repeat going idle after avalon reset, and wait for write ready before sending +each reset request instead of some arbitrary sleep time. +- Timeouts on avalon_read and avalon_write should be 100ms. +- Don't close avalon after detecting it until we're cleaning up, instead using +reset for comms failures. +- Check for avalon_wait_write before sending reset command. +- Sleep in avalon_write_ready. +- Make avalon_wait_write a bool function and check its return value. +- Show how many idle tasks are sent to avalon if it aborts on buffer full. +- Reset avalon->device_fd after it is closed. +- Create an avalon_wait_write function that is used before sending avalon idle +command. +- Avoid repeating avalon_idle in do_avalon_close and extra sleep. +- Pass fd to avalon_idle. +- Do avalon_reset after info structure is set up. +- Rework avalon reset sequence to include idling of chips and waiting for them +to go idle followed by 2nd reset and then checking result. +- Do a non-blocking read of anything in the avalon buffer after opening the +device. +- Assign the avalon info data to the device_data in cgpu_info. +- thread shutdown is different on windows +- usbutils make all windows timeouts 999ms +- usb add another Cairnsmore1 USB chip +- icarus do the full detect test twice if required +- CMR usb config guess +- usb add transfer_read and commented out in icarus +- usbutils allow unrounded control transfers +- icarus ICA initialisation +- icarus report err on read failure +- icarus correct device_id and use device_data for icarus_info +- miner.h remove unused device_file and add device_data +- miner.h icarus no long uses fd +- icarus AMU config transfers +- Create a logwin_update function which mandatorily updates the logwin and use +it when input is expected to prevent display refresh delays. +- usbutils force an unknown IDENT for zero +- icarus set default options/timing based on device +- Must unlock curses as well in logwin_update. +- Create a logwin_update function which mandatorily updates the logwin and use +it when input is expected to prevent display refresh delays. +- icarus report usb write error information +- Add name to icarus copyright notice. +- Check for *pth dereference on pthread_join +- usbutils name latency correctly +- Check for restart before buffering more reads in Icarus. +- Icarus should timeout if it's greater than the timeout duration even if it's +receiving data. +- We should check for amount buffered in icarus get_nonce against amount already +received. +- Make mining threads report out during work submission. +- submit_work_async is no longer used directly by driver code. +- Fix first read timeout on icarus get nonce. +- Retry icarus_initialise if the first read attempt fails. +- Properly pthread_join miner threads on shutdown. +- Properly pthread_join miner threads on shutdown. +- Use a persistent single separate thread for stratum share submission that uses +workqueues since all stratum sends are serialised. +- All stratum calls to recv_line are serialised from the one place so there is +no need to use locking around recv(). +- Only allow the mining thread to be cancelled when it is not within driver +code, making for cleaner shutdown and allowing us to pthread_join the miner +threads on kill_work(). +- Only allow the mining thread to be cancelled when it is not within driver +code, making for cleaner shutdown and allowing us to pthread_join the miner +threads on kill_work(). +- Set pool->probed to true after an attempt to resolve the url via stratum code. +- icarus test nodev everywhere +- usbutils/icarus separate FTDI transfer values and more debug +- add icarus to hotplug +- usbutils add rest of icarus +- simple serial-USB python test script +- icarus->USB v0.1 incomplete - missing initialise() +- README spelling +- Update documentation for icarus switch to USB +- Add USB rules for supported USB devices +- switch icarus configuration to usb +- usbutils new command for icarus +- usb add a numeric sub-indentity for each name +- usbutils - make FTDI handling automatic +- fix duplicate name +- usbutils set Black Arrow Lancelot's as BAL and match the lot->llt name +- usbutils identify Icarus devices +- libusb_control_transfer 16 bit words are endian specific. +- usb_applog separate amt display +- Show pool difficulty more verbosely if it changes via stratum. +- Attribute whatever stats we can get on untracked stratum shares based on +current pool diff. +- Provide a --lowmem option which does not cache shares on failed submission to +prevent low memory hardware (eg Avalon) from crashing. +- Update util.c + + +Version 3.1.1 - May 11th, 2013 + +- Use a discrete device target for scrypt that dynamically changes to ensure we +still report a work utility even if no shares are submitted such as in solo +mining. +- Make set_work_target a function to set a specified char as target for use +elsewhere. +- Further consolidate the hash regeneration between sha and scrypt doing it only +once and always checking the share diff for both before submission. +- Regenerate the hash before checking the share diff in hashtest(). +- Minor typo. +- Use a scantime of 30 seconds for scrypt if none is specified. +- Support more shares to be returned for scrypt mining. +- Update the write config to properly record device entries and remove disabled +option. +- Show a different warning and loglevel for failure to resolve a URL on first or +subsequent testing of stratum pool URLs. +- Fix the problem of seting up termio of ttyUSB0 for icarus. the CSIZE is the +mask of CS2/4/8 From: navyxliu +- Set all stratum sockets to nonblocking to avoid trying to use MSG_DONTWAIT on +windows. +- Fix warnings on win32 build. +- Only use MSG_NOSIGNAL for !win32 since it doesn't exist on windows. +- Use MSG_NOSIGNAL on stratum send() +- Set TCP_NODELAY for !linux for raw sockets. +- Use TCP_NODELAY with raw sockets if !opt_delaynet +- Make raw sockets compile on windows +- Recheck select succeeds on EWOULDBLOCK for stratum. +- usbutils/mmq fixed size usb_read default to wait for all data +- usbutils optional (disabled by default) dev debug +- Add an ftdi usb read macro without newline +- Avalon usb interface should be 0. +- Add more debug for failure to USB init. +- Recv() should all be non-blocking for raw sockets in stratum. +- Change verbosity and error for getaddrinfo warnings in setup stratum socket. +- Free servinfo after p is checked in setup stratum socket. +- Use raw sockets without curl for stratum communications. +- Sacrifice curl handle memory on stratum disconnects on all versions of libcurl +to avoid curl corruption. +- Don't use TCP_NODELAY if opt_delaynet is enabled with stratum. +- Fix warnings in avalon driver. +- Make FULLNONCE an ULL to fix a warning on 32 bit. +- ztx correct applog typing +- ocl correct applog typing +- util correct applog typing +- api correct applog typing +- cgminer correct applog typing +- scrypt correct applog typing +- bfl correct applog typing +- ica correct applog typing +- mmq correct applog typing +- adl fix trailing % +- usbutils correct applog typing +- applog - force type checking +- Simplify the many lines passed as API data in the avalon driver now that the +API does not need persistent storage for the name. +- Duplicate the name string always in api_add_data_full to not need persistent +storage for names passed to it. +- Add extra matching work count data in API for Avalon with 4 modules. + + +Version 3.1.0 - April 28th, 2013 + +- va_copy is meant to be matched by a va_end in log_generic. +- usbutils remove_in_use break +- usbutils remove_in_use missing prev +- usbutils missing add_in_use +- Clean up summary slightly better on exit. +- Make the scan sleep time after scanwork in bflsc dynamic to keep queues +between watermark levels. +- Remove unused temp counts in bflsc. +- Calculate a rolling 5 min average set of temperatures for bflsc. +- Damp the display of voltage for BFLSC devices. +- Damp the temperature display measurement for bflsc since it fluctuates so +wildly. +- bflsc add volt stats +- Handle failed tolines command in bflsc driver. +- Can use a read lock instead of a write lock in bflsc scanwork. +- Since we are filling a queue on the bflsc devices, there is no need to run +through scanwork frequently provided we use the restart_wait function to abort +early during a block change. +- Remove flushed work in bfl scanwork from the hash table. +- Set correct device in process_nonces in bflsc driver. +- bflsc add work reply INPROCESS: missing from the spec +- bflsc put in some error messages not yet written +- bflsc get completed hashes as late as possible +- Fix potential memory leak with unused work items in bflsc_queue_full +- Reverse bools in bflsc_queue_full +- Avoid recursive loop calling correct function instead. +- bflsc fix details identification +- Differentiate BFLSC device from regular bitforce and give warning if no +support is compiled in. +- util.c str_text make a fully text readable version of str +- BFLSC fix FPGA identity overlap +- Locking error in bflsc_send_work +- Use htobe32 function for converting nonce in bflsc. +- Replace deprecated bzero with memset in bflsc driver. +- Fix compilation of bflsc driver without opencl. +- Check for realloc failures in bflsc driver. +- Check for failure to calloc in bflsc driver. +- Trivial style change +- Use copy_time function in bflsc driver. +- Use cgtime in bflsc driver and update copyright notice. +- Use a separate function for bfl initialise that doesn't require locking. +- Fix BFLSC building. +- bflsc v0.1 + + +Version 3.0.1 - April 25th, 2013 + +- Bypass attempting to read and save binary files on OSX to avoid crashes on >1 +GPU. +- Receive failures in recv_line should unconditionally fail. +- Use sock_blocks in api.c +- Use sock_blocks function for stratum send and receive. +- Create an OS specific sock_blocks function. + + +Version 3.0.0 - April 22nd, 2013 + +- Further fix distdir for hexdump.c +- Fix build and distdir. +- Remove all CPU mining code. +- compile on win32 +- Update SCRYPT README with improved hashrates for 7970. +- Use copy_time helper throughout cgminer.c +- Provide wrappers for commonly used timer routines with API stats. +- Avoid one cgtime call in sole_hash_work. +- Fulltest is true if value is <= target. +- Use system host to endian functions for clarity in fulltest. +- Provide endian_flipX functions to avoid special casing big endian in cgminer.c +- Provide a flip128 helper to simplify big endian flipping. +- Use flip helpers to simplify code for calculation of midstate. +- Use flip32 function instead of open coding it in gen_stratum_work. +- Move util.c exports to util.h +- Fix warning on building avalon on win32 +- Use cgtime in driver-avalon.c +- Use cgtime in driver-icarus.c +- Use cgtime in driver-bitforce.c +- Use cgtime in logging.c +- Use cgtime in usbutils.c +- Use cgtime in driver-opencl.c +- Use cgtime wrapper in driver-modminer.c +- Use cgtime in driver-ztex.c +- Use cgtime in compat.h +- Use cgtime instead of gettimeofday in fpgautils.c +- Replace gettimeofday usage in cgminer.c with cgtime +- Create a cgminer specific gettimeofday wrapper that is always called with tz +set to NULL and increases the resolution on windows. +- Add high resolution to nmsleep wrapper on windows. +- Set default ocl work size for scrypt to 256. +- define le32toh if needed +- fliter out the wrong result from adjust fan code +- compile avalon driver on win32 and win64 +- Restart threads on the rare chance we found the block ourselves. +- Add more FAQs about crossfire. +- Set last device valid work on adding device. +- Increment last device valid work count in submit_nonce to cover scrypt. +- Set opt_scrypt drv max diff for correctness. +- Make scrypt submission use the submit_nonce code, with nonces matching +endianness. +- Do testing for HW errors on submit nonce for both scrypt and sha. +- Increment hardware error count from the one site. +- Rename scrypt regenhash function for consistency. +- Add new best share info to verbose logging. +- Add notice for when network diff is changed. +- Convert error getting device IDs in ocl code to info log level only since +multiple platforms may be installed and the error is harmless there. +- Unnecessary extra array in ocl code. +- Further driver FAQs. +- Add MAC FAQ. +- Add more FAQ details. +- Check for work restart after disable in the hash queued work loop since it may +be a long time before we re-enable a device. +- Unconditionally test for many wrong results on avalon and reset to avoid +passing a corrupt avalon result to temperature code. +- build out of source dir +- Set device_diff for queued work or there will be no diff1 share count. +- Only reset an avalon device with no results when there are no results +consecutively. +- More FAQs. +- More FAQs. +- Cleanup when stratum curl fails to initialise. +- Avoid applog in recalloc_sock. +- Avoid applog under stratum_lock in recv_line. +- Avoid applog under stratum_lock in __stratum_send. +- Put spacing around locking in util.c for clarity. +- Avoid applog under cg_wlock. +- Put spacing around locking code for clarity. +- Avoid applog under pool_lock. +- Avoid more recursive locks. +- Avoid applog while ch_lock is held. +- Avoid recursive locks in fill_queue. +- Variable is already initialised in global scope. +- More GPU faqs. +- More README faqs. +- Yet more README faqs. +- Add more faqs to README. +- Wrap result wrong tests in avalon scanhash in unlikely() and only consider a +hash count of zero wrong if a restart wasn't issued. +- avalon: if result_wrong >= get_work_count jump out the read loop +- Fix warning on 32bit. +- Fix warning on 32bit. +- Avoid curl_easy_cleanup on old curl versions in setup_stratum_curl as well. +- fix the fan control on max temp2/3 +- for some reason network down. one simple cgminer command: "cgminer -o +127.0.0.1:8888 -O fa:ke --avalon-options 115200:32:10:50:256" can idle the +avalon for safe power and protect chip +- if hash_count == 0; reinit avalon, fix the 0MHS bug use the max value of temp1 +and temp2 for fan control +- Reinstate the matching_work_count per subdevice on avalon based on the work +subid. +- Avalon driver is missing the drv_id. +- Rationalise and simplify the share diff and block solve detection to a common +site. +- Rationalise and simplify the share diff and block solve detection to a common +site. +- Make the avalon array size a macro. +- Use replacement of work items in the avalon buffer as needed instead of +flushing them. +- Reinstate wrong work count to reset avalon regardless and display number of +wrong results. +- Revert "The result_wrong measurement for avalon is continually leading to +false positives so remove it." +- select() on serial usb in avalon does not work properly with zero timeout. +- The result_wrong measurement for avalon is continually leading to false +positives so remove it. +- Revert "Use only 2 queued work arrays in avalon." +- Use no timeout on further reads in avalon_gets +- Do sequential reads in avalon_get_reset to cope with partial reads. +- Show read discrepancy in avalon_get_reset. +- Reuse avalon_get_work_count variable. +- Check for AVA_GETS_RESTART when deciding if avalon has messed up. +- Make the detection of all wrong results on avalon much more conservative to +avoid false positives on work restarts. +- Show error codes on select and read fail in avalon. +- If we get a restart message in avalon_gets still check if there's a receive +message to parse first without a timeout before returning AVA_GETS_RESTART. +- Use only 2 queued work arrays in avalon. +- avalon_gets is always called from the one call site so inline it. +- The read_count is unused by the avalon get result code and no longer required +for avalon reset so simplify code removing it. +- Use a separate avalon_get_reset function for resetting avalon instead of using +avalon_get_result. +- The current hash count returned by avalon scanhash is just an obfuscated +utility counter so make it explicit. +- Check for a restart before a timeout in message parsing code in avalon. +- We should check for a restart message before checking for a timeout in avalon +scanhash. +- Store the subid for the work item in avalon. +- usbutils more stats for bflsc +- Fix record_temp_fan function in avalon driver. Patch by Xiangfu + +- Remove inappropriate memset of struct avalon result which was corrupting fan +values. +- Fix warning with no curses built in. +- Bump version to 2.11.4 +- Add API support for Avalon. +- Only do_avalon_close once on multiple errors. +- Reset the result_wrong count on block change in avalon scanhash to prevent +false positives for all nonces failed. +- Small timeouts on select() instead of instant timeout increase reliability of +socket reads and writes. +- Only get extra work in fill_queue if we don't have any unqueued work in the +list. +- Small timeouts on select() instead of instant timeout increase reliability of +socket reads and writes. +- Rotate the avalon work array and free work on AVA_SEND_BUFFER_EMPTY as well. +- Only get extra work in fill_queue if we don't have any unqueued work in the +list. +- Don't get any work if our queue is already full in avalon_fill. +- Differentiate socket closed from socket error in recv_line. +- Differentiate socket closed from socket error in recv_line. +- Free avalon->works in the event we call avalon_prepare on failure to +initialise. +- Fix warnings. +- Create an array of 4 lots of work for avalon and cycle through them. +- Remove unused per unit matching work count for avalon. +- Rename the confusing avalon_info pointer. +- Simplify avalon scanhash code using the new find_queued_work_bymidstate +function. Partially works only. +- Members of cgpu_info for avalon are not meant to be in the union. +- Use correct struct device_drv for avalon_drv. +- cgminer.c -S help to only say Icarus +- Check enough work is queued before queueing more in avalon_fill. +- Actually put the work in the avalon queue. +- Rneame avalon_api to avalon_drv. +- First draft of port of avalon driver to new cgminer queued infrastructure. +- Add Makefile entry for driver-avalon. +- Add configure support for avalon. + + +Version 2.11.4 - April 5th, 2013 + +- Remove bfl-sc option from configure for 2.11 branch. +- Only update hashrate calculation with the log interval. +- Update the total_tv_end only when we show the log to prevent failure to update +logs. +- Minor README updates. +- Add example 7970 tuning for scrypt in readme. +- Update driver recommendations. +- Add extensive GPU FAQs for the flood of new Scrypt miners. +- Remove help option for cpumining in build environment. +- Remove scripts that make it too easy to compile CPU mining support. +- Win32 and win64 build updates +- Remove references to CPU mining from README. +- Show share hash as little endian as needed. +- usbutils extra message requirements +- Make hashmeter frequency for hash_queued_work match sole_work. +- Update links and recommended SDKs. +- Update scrypt readme re drivers and sdk. +- usbutils.c usb_cmdname() usb_cmds -> string name +- BFL FPGA Windows timeout set to 999ms +- AUTHORS - spam update time (one year since the last) +- Update README for x970 memdiff values. +- Update README to match changes to display. +- Remove increasingly irrelevant discarded work from status lines. +- Remove increasingly irrelevant queued and efficiency values from status and +move WU to status line. +- Allow cgminer to start if usb hotplug is enabled but no devices yet exist. +- Do not scan other gpu platforms if one is specified. +- Update README for sync objects on windows. +- Update README about intensity. +- Add information for setting gpu max alloc and sync parameters for windows with +scrypt. +- If the hashmeter is less than the log interval and being updated by the +watchdog, don't update the hashrate. + + +Version 2.11.3 - March 17, 2013 + +- Update docs and reorder README to show executive summary near top. +- Update the hashmeter at most 5 times per second. +- Usbutils use its own internal read buffer +- Calculate work utility for devices that support target diffs of greater than +1, and update scrypt code to use it. +- usbutils allow read termination match to be a string +- Set default GPU threads to 1 for scrypt. +- Connect backup stratum pools if the primary pool cannot deliver work. +- Use a new algorithm for choosing a thread concurrency when none or no shader +value is specified for scrypt. +- Do not round up the bufsize to the maximum allocable with scrypt. +- Remove the rounding-up of the scrypt padbuffer which was not effectual and +counter-productive on devices with lots of ram, limiting thread concurrencies +and intensities. +- bufsize is an unsigned integer, make it so for debug. +- Update the hashmeter once per second but only display the extra logs every +opt_log_inteval. +- add a dummy ztex to usbutils so cgminer -n lists ztex also +- nDevs required for -n with usb +- USB device list - convert some common error numbers to messages +- USB -n 'known' text only without ---usb-list-all +- USB modify -n and --usb-dump to only show known devices or use new +--usb-list-all option to see all +- Make pool adding while running asynchronous, using the pool test thread +functionality. +- Only curl easy cleanup a stratum curl if it exists. +- Sacrifice the ram of curl handles in stratum disconnects when we have built +with old libcurl to avoid crashes. +- cgminer -n to include a USB device list +- usbutils allow call of usb_all() from other code +- Convert gbt_lock to a cg_lock. +- Add intermediate variants of cglocks that can be up or downgraded to read or +write locks and use them for stratum work generation. +- Move the stratum and GBT data to be protected under a new cg_lock data_lock. +- Convert the ch_lock to cg_lock. +- Convert the control_lock to a cg_lock. +- Remove unused qd_lock. +- Implement cg_lock write biased rwlocks. +- do usb_initialise() after the started message so we see it +- --usb-dump display brief dump if value = 0 +- USB add --usb options to limit USB device selection v0.1 + + +Version 2.11.2 - March 9, 2013 + +- Whitelist AMD APP SDK 2.8 for diablo kernel. +- Cope with the highest opencl platform not having usable devices. +- Fix memory leak with share submission on GPU work structures as discovered by +twobitcoins. +- usb_cleanup() without locking. +- Use curl_easy_cleanup to close any open stratum sockets. +- Show pool number in switch message +- Don't start testing any pools with the watchpool thread if any of the test +threads are still active. +- Set sockd to false should curl setup fail on stratum. +- Close any open sockets when reusing a curl handle and reopen the socket +whenever we're retrying stratum. +- Set pool died on failed testing to allow idle flag and time to be set. +- Remove unused pthread_t typedefs from struct pool. +- Perform pool_resus on all pools that are found alive with the test pool +threads. +- Use pool_unworkable in select_balanced as well. +- Differentiate pool_unusable from pool_unworkable. +- Keep a connection open on higher priority stratum pools to fail back to them. +- Rename threads according to what pool they're associated with as well. +- Set the wrong bool in pool_active +- Start the stratum thread only if we successfully init and authorise it, +otherwise unset the init flag. +- Make the initialisation of the stratum thread more robust allowing the +watchpool thread safe access to it after the stratum thread is started. +- API no longer ignore send() status +- API make the main socket non-static + + +Version 2.11.1 - March 7, 2013 + +- Shorten the time before keepalive probes are sent out and how frequently +they're sent with stratum curls. +- Only set stratum auth once to prevent multiple threads being started. +- Display select return value on select fail in stratum thread. +- Clear the socket of anything in the receive buffer if we're going to retry +connecting. +- Allow pools to be resuscitated on first startup by the watchpool thread. +- Check all pools simultaneously at startup switching to the first alive one to +speed up startup. +- Clear just the socket buffer when we don't care what is left in a stratum +socket. +- Clear the stratum socket whenever we are closing it since the buffer is going +to be reused. +- Do not continue work from a stratum pool where the connection has been +interrupted. +- Reset stratum_notify flag on suspend_stratum as well. +- Close any sockets opened if we fail to initiate stratum but have opened the +socket. +- Close any existing stratum socket if we are attempting to restart stratum so +the pool knows the connection has gone. +- Show mechanism of stratum interruption if select times out. +- Make stratum connection interrupted message higher priority to be visible at +normal logging levels. +- Implement client.show_message support for stratum. +- API add 'Network Difficulty' to 'coin' +- Setup BFLSC support +- API use control_lock when switching pools +- Make sure to retry only once with noresume support for stratum. +- Instead of keeping track of when the last work item was generated to keep +stratum connections open, keep them open if any shares have been submitted +awaiting a response. +- usbutils.c copy full size to 'Last Command' +- configure - set USE_USBUTILS when usbutils is required and use it in the code +- Clear last pool work on switching pools if the current pool supports local +work generation or we are in failover only mode. +- make rw locks: mining_thr_lock and devices_lock +- Release MMQ device only once (not 4 times) +- api.c fix MSG overlap +- Hotplug - allow setting interval via --hotplug or API +- curses - fix - put a dev_width inside #ifdef +- usb_cleanup() use correct locking mechanism +- Implement and use usb_cleanup() on shutdown or restart +- miner.php report 'Last Valid Work' as time before request +- API - return Last Valid Work +- api -> drv +- ZTX bug set missing drv_id + + +Version 2.11.0 - March 2, 2013 + +- Update kernel file names signifying changes. +- Update a pool's last work time when the work is popped as well as staged. +- API always report failed send() replies +- Update diff stale: total and pools when stratum throws away shares +- Keep stratum connections open for 2 minutes after the last work item was +staged to allow stray shares to be submitted on pool switching. +- Try to extract the sessionid associated with mining.notify on 3rd level array +and submit it along with the userid to support mining resume, failing gracefully +and restarting if the pool rejects it. +- Speed up watchdog interval and therefore display updates to 2 seconds. +- Update copyright dates. +- Cope with misread sessionid on stratum for now. +- Use constants from the array of __constants throughout the diablo kernel. +- Create a __constant array for use within diablo kernel. +- Fix --benchmark generating valid work for cgminer. +- Use the sessionid as passed on stratum connect to attempt to resume a +connection once and then clear it if it fails, to use a new connection. +- Move to storing the nonce1 in the work struct instead of the sessionid for the +now defunct first draft mining.resume protocol. +- Use global constant arrays for all other constants used in scrypt kernel. +- Use global __constants for sha functions in scrypt kernel. +- Use constants for endian swap macros. +- Revise scrypt kernel copyright notice. +- Separate out additions in scrypt kernel. +- Reuse some Vals[] variables that can be assigned to constants earlier in the +poclbm kernel, making for fewer ops. +- Put all constants used in poclbm kernel into __const memory array to speed up +concurrent reads on the wavefront. +- BFL stop 1st init command if no device +- Add a get_queued function for devices to use to retrieve work items from the +queued hashtable. +- Bugfix: Duplicate stratum sessionid when copying work, to avoid double-free +- Bugfix: Missing pool_no parameter to applog for no-stratum-sessionid debug +message +- Add the choice of hash loop to the device driver, defaulting to hash_sole_work +if none is specified. +- Add comments. +- Add a driver specific flush_work for queued devices that may have work items +already queued to abort working on them on the device and discard them. +- Flush queued work on a restart from the hash database and discard the work +structs. +- Create a central point for removal of work items completed by queued device +drivers. +- Create a fill_queue function that creates hashtables of as many work items as +is required by the device driver till it flags the queue full. +- Create the hash queued work variant for use with devices that are fast enough +to require a queue. +- Update copyright year. +- Fix tv_lastupdate being made into tv_end and update the hashmeter on cycle, +not opt_log_interval. +- Fix tv_lastupdate being made into tv_end and update the hashmeter on cycle, +not opt_log_interval. +- Only continue submitting shares with mining.resume support on stratum when the +session id matches. +- Provide support for mining.resume with stratum, currently re-authorising after +successful resumption pending finalising of the protocol process. +- Provide basic framework for restarting stratum depending on whether resume +support exists or not. +- Abstract out the setting up of the stratum curl socket. +- Free sessionid in clean_work and remove redundant setting of strings to NULL +since the whole work struct is zeroed. +- Only clear stratum shares mandatorily on stratum dropouts when the pool does +not support resume. +- Try resubmitting stratum shares every 5 seconds for up to 2 minutes if the +pool session id exists and matches on failure to submit. +- Do as much outside of mutex locking of sshare_lock as possible. +- Remove last reference to struct work used outside the sshare_lock in +submit_work_thread +- Unlock the sshare_lock in submit_work_thread when all references to work and +sshare are complete. +- Add timestamps to stratum_share structs as they're generated and copy the +stratum sessionid if it exists to stratum work generated. +- Store session id for stratum if the pool supports it for future mining.resume +support. +- API.java allow partial reads +- debug_cb buffer type warning +- MMQ rewrite the last of the old scanhash loop and drastically reduce CPU +- hash_sole_work can be static +- Make the numbuf larger to accept larger scrypt parameters. +- Keep the unique id of each work item across copy_work to prevent multiple work +items having the same id. +- Abstract out the main hashing loop to allow us to use a separate loop for +devices that are fast enough to require queued work. +- Provide a noop thread_enable function for drivers that don't support it. +- Provide a noop thread_shutdown function for drivers that don't support it. +- Provide a noop hw_error function for drivers that don't support it. +- Provide a noop prepare_work for drivers that don't support it. +- Provide a noop thread_init for drivers that don't support it. +- Provide a noop can_limit_work for devices that don't support it. +- Provide a noop thread_prepare function for drivers that don't use +thread_prepare. +- Use blank_get_statline_before for GPU devices that don't support adl +monitoring. +- Provide a noop get_stats function for drivers that don't support it. +- Provide a blank get_statline for drivers that don't support it. +- Provide a blank get_statline_before function for drivers that don't have one. +- Fill drivers missing reinit_device with a noop version. +- add 'count' to cumstomsummarypage 'calc' +- hotplug use get_thread() where appropriate +- convert sleep(const) to nmsleep() +- remove empty #ifdef +- call a separate get_devices() with locking, as required +- usbutils - avoid free cgusb twice +- usbutils hotplug v0.1 +- Report USB nodev as ZOMBIE on the screen +- Change file modes. + + +Version 2.10.5 - February 7, 2013 + +- Fix logic fail on partial writes with stratum send that was leading to corrupt +message submissions. +- Do not consider every call to stratum_resumed a pool recovery unless it was +actually idle. +- Do not enable the pool disable on reject feature unless explicitly enabled +with --disable-rejecting. +- Stratum disconnect shares - count total against stale +- Use sanity checking to prevent a possible overflow with invalid data being +given by the pool for difficulty as reported by luke-Jr. +- Check for calloc failure for completeness in gen_stratum_work. +- Cache the coinbase length to speed up stratum work generation. +- Cache the header length when generating stratum work to avoid calculating it +on every work generation, and to only need one alloc+sprintf, speeding up work +generation. +- Use heap ram for coinbase in gen_stratum_work, zeroing it before use. +- Provide a wrapper for aligning lengths of size_t to 4 byte boundaries. +- Fix memory leak on stratum share submission. +- Zero the best share string memory when zeroing stats. + + +Version 2.10.4 - December 29, 2012 + +- Change the pool stratum socket buffer to be dynamically allocated to +accomodate any size coinbase and keep receiving data in recv line for up to 60s +if no end of line has been received. +- Differentiate socket full from sock full. +- Allow stratum to startup without notify but check it is valid before creating +stratum work. +- Do not try to generate stratum work unless the notify command has succeeded. +- Reset total diff1 shares when zeroing stats as well to show correct work +utility. + + +Version 2.10.3 - December 26, 2012 + +- Do not give the share submission failure message on planned stratum +disconnects. +- Parse anything in the stratum socket if it's full without waiting. Empty the +socket even if a connection is not needed in case there are share returns. +- Provide a mechanism to zero all the statistics from the menu. +- Display the current pool diff in the status line. +- Display block diff in status line. +- Generalise the code for solving a block to enable block solve detection with +scrypt mining. +- Generate the output hash for scrypt as well and use the one function to set +share_diff. +- Use the flip80 function in regeneratehash and the correct sized hash array. +- Use one size for scratchbuf as a macro in scrypt.c +- Stage work outside of the stgd lock to prevent attempted recursive locking in +clone_available. +- share_diff needs to be performed on a BE version of the output hash to work, +leading to false best_share values as spotted by luke-Jr. +- Remove the unused sha224 functions. +- Use the flip functions in hashtest. +- Simplify the setting of the nonce data field in work on submitting nonces. +- Scrypt code does not enter the hashtest function. +- Go back to cloning available work under staged lock. +- Updated links to AMD APP SDK +- Updated link to ADL SDK +- scrypt_diff uses a uint64_t as well. +- Correct target for stratum support with scrypt mining. +- libztex: fixed a typo +- libztex: check returnvalue of libusb_claim_interface() and release the +interface in case of early exit + + +Version 2.10.2 - December 19, 2012 + +- Stop all work from the current pool if it's a stratum pool once it is +disconnected since it will be invalid upon reconnecting. +- Discard all staged work from stratum pools as well as the shares upon +disconnection since all the work becomes invalid. +- Use correct cbreak after 15 second delay when no pool is found alive. +- MMQ missing firmware -> ERR not DEBUG +- Allow stratum to work with scrypt. +- MMQ ensure delta clock can never exceed limits +- MMQ lowercase new string constants +- MMQ add api pgaset for clock +- API V1.23 - new pgaset command, to be used soon +- Protect the best_share/best_diff values under control lock. +- MMQ style police +- MMQ count work check timeout failures +- MMQ allow partial work replies and count them +- Check a stratum pool hasn't gone dead while being a backup pool and missed +having its idle flag cleared. +- MMQ overheat: remove clockdown (doesn't help) + ensure no lost shares +- API-README grammar +- API-README explain custom page extensions in miner.php +- miner.php add a sample group pool report +- miner.php allow where,group,having on cumstom pages + + +Version 2.10.1 - December 14, 2012 + +- Check for EWOULDBLOCK when supported in send and recv as well. +- Use the raw send() command instead of curl_easy_send since curl raw socket +usage introduces random bugs on windows. +- Use raw recv() command in place of curl_easy_recv since the curl +implementation introduces random bugs on windows builds when the recv fails. +- miner.php when displaying a single rig, add prev/next rig buttons if they +exist, next to refresh +- miner.php allow custom page joins for STATS +- API show if pool has GBT (so people know not to use that pool) +- miner.php - include windows easyphp link +- driver-ztex: use the correct size for the swap array +- API stats - display pool byte transfer stats +- Pool store data transfer stats +- README ModMiner dependency +- Benchmark incorrect work size +- ChangeLog refer to NEWS +- MMQ handle over temp differently and hash longer +- driver-ztex: search the complete noncerange based on the actual speed +- README - update ModMiner details +- API-README update +- api use a dynamic io buffer, truncated before it reaches the current ~64k +limit + + +Version 2.10.0 - December 10, 2012 + +- Include prctl header for thread renaming to work. +- Set tv_idle time if a pool is not active when input from the menu. +- usb display message when device is in use/another cgminer +- libztex: avoid the use of libusb_error_name() +- minor unlikely zero pointer test +- BeaverCreek doesn't like BFI INT patching. +- Only stratum pools that are idle need to be kicked via cnx_needed. +- mmq - abbreviate the temperature numbers +- Do not do any setup if opt_api_listen is disabled in api.c. +- usbutils.c uninitialised usbstat for non-primary mmqs +- Only set the lagging flag for select_pool() on failed getwork if we're not in +opt_fail_only mode. +- libztex: in case the selectFpga() failed set the selected fpga to unknown +- Modified windows-build.txt to update git instructions. +- libztex: use a function for the twice called firmware reset code +- libztex: removed an unused struct member (ztex->valid) +- driver-ztex: support for broken fpga on a multifpga board +- Set the pool lagging flag on startup to avoid it being shown initially, and +only unset it once the maximum number of staged work items has been reached. +- Avoid recursive locking of the stgd lock. +- Return value of keep_sockalive is no longer used. +- Remove dependency on mstcpip.h for windows build by making curl version >= +7.25.0 mandatory on windows builds, and use curl functions for keepalive +whenever possible instead. +- Make main() the getwork scheduler once everything is set up, so that all app +exits use the kill_work and quit paths. +- ztex: more style and whitespace fixes +- libztex: silenced another warning +- Set successful connect to true on auth stratum to allow summary on exit from +single stratum pool. +- Only consider work stale for stratum of different job_id if it's not a share. +- Increment version preempting changed version signifying different codebase to +2.9 +- Hash_pop should signal further waiters on its own pthread conditional in case +there are multiple waiters. +- Check the job_id has not changed on stratum work when deciding if the work is +stale as might occur across disconnections. +- Perform pool_resus on getwork pool that generates work in getwork_thread. +- Set pool lagging message for getwork pool that falls to zero staged in getwork +thread. +- Stage extra work when the primary pool is a getwork pool without rolltime. +- Do not try to clean up twice if kill message is given. +- Only recalculate total_staged in getwork thread if required. +- Include the correct config header in libztex and include it before other +includes. +- Implement a completely new getwork scheduler. Stage all work from the one +thread, making it possible to serialise all requests minimising the number of +getworks requested or local work generated. Use a pthread conditional to wake up +the thread whenever work is removed to generate enough work to stay above the +watermark set by opt_queue. Remove all remnants of the old queueing mechanism, +deleting the now defunct queued count. +- libztex: fixed some warnings and removed some whitespaces +- libztex: silenced some warnings +- Remove all references to the now unused workio_cmd structure. +- Remove the old workio command queue thread, replacing it with a kill +conditional to exit the program. +- Remove getwork command from workio_cmd queues and do them directly from +queue_request. +- Begin tearing down the old workio command queues by removing submit commands +from there and submit them asynchronously via their own threads. +- Update windows build instructions. +- Set pool probed to true on successful authorisation with stratum to avoid it +being pinged later with pool_getswork. +- driver-ztex: libztex_setFreq() must be called before ztex_releaseFpga() +- driver-ztex: changed two pairs of malloc()/memset() to calloc() +- libztex: Read bitstream file in 2kb blocks with simpler and faster code +- Added the binary versions of ztex_ufm1_15d4.ihx and ztex_ufm1_15y1.ihx +- Trivial space removal. +- libztex: Add firmware download support for ZTEX 1.15d and 1.15x +- libztex: Factor out local version of libusb_get_string_descriptor_ascii() +- Shut up some boring old cpu warnings. +- Style changes. +- Allow pool active to be called on stratum or disabled pools in the watchpool +thread if the pool has not been probed. +- libztex: Make log messages say bitstream when refering to bitstreams +- libztex: Don't return error when a bitstream was already configured +- libztex: Read bitstream file in 64kb blocks with simpler and faster code +- libztex: Verify that the mining firmware is not a dummy firmware +- libztex: Match mining firmware ZTEX descriptor against the dummy firmware +- Combine shared padding into one char. +- libztex: Start download sequence only after reading in the new firmware +- libztex: Download mining firmware to all devices with dummy firmware +- lock (most of) the threaded statistics updates +- README stats don't add up +- usbutils.c remove compiler warning +- Make need connection return true if a pool is idle. +- API add Best Share to summary +- Check on creating new GBT work if the structures are up to date and update +them as required rather than regularly. +- Update windows build instructions. +- Enable backup stratum connections for getwork when the primary pool doesn't +have longpoll aka solo mining. +- Check for correct absence of opt_fail_only in cnx_needed. +- Remove unused variable. +- The specification for stratum has been elaborated to say that a changed diff +applies only to new work so do not retarget when submitting shares. +- Use a variable length string array in submit_upstream_work to cope with +massive GBT submissions. +- API lock access to some summary statistics (and copy them) +- Suspend stratum connections to backup pools when there is no requirement to +potentially grab work from them. +- Fix missing export for RenameThread. +- enumerate the mining threadnames +- MMQ avoid possible number overrun crashes +- mmq usb v0.4 + api usb stats +- setting the name of the threads for linux,freebsd,openbsd and osx code is +borrowed from bitcoins util.c, so it is already tested +- Don't show broken WU value with scrypt mining. +- Style police. +- Remove unused getwork times in getswork. +- Fix readme wordwrap. + + +Version 2.9.6 - December 2, 2012 + +- Make gen_stratum_work more robust by using a dynamically allocated array for +the header in case bogus data is sent by the pool to avoid overflowing a static +array. +- scrypt_diff now returns a uint64_t +- Support monitoring and reporting much higher diffs for scrypt mining, +truncating irrelevant zeroes from displayed hash. +- Pass ostate values around in scrypt to be able to extract full hashes if +needed later on. +- Since we will be using calloc_str to put a string into it, convert the +function to calloc_strcat which does it automatically. +- Revert "Handle crash exceptions by trying to restart cgminer unless the +--no-restart option is used." +- Count longpoll and GBT decodes as queued work since the count otherwise +remains static. +- Use the string helper functions to create gbt blocks of any length. +- Provide helper functions calloc_str and realloc_strcat to create and extend +arbitrary length arrays based on string length. + + +Version 2.9.5 - November 25, 2012 + +- fixes target calc for mips openwrt +- openwrt needs roundl +- Get rid of unused last_work in opencl thread data. +- Do away with the flaky free_work api in the driver code which would often lose +the work data in opencl and simply flush it before exiting the opencl scanhash. +- Use base_work for comparison just for cleanness in __copy_work +- Remove all static work structs, using the make and free functions. +- Add pool no. to stale share detected message. +- Add info about which pool share became stale while resubmitting. +-b Copy the work on opencl_free_work +- Add an extra slot in the max backlog for ztex to minimise dupes. +- Do not use or count the getworks submitted which are simply testing that pools +are still up. This was increasing share leakage and making stats not reflect +real work. +- Track all dynamically allocated memory within the work struct by copying work +structs in a common place, creating freshly allocated heap ram for all arrays +within the copied struct. Clear all work structs from the same place to ensure +memory does not leak from arrays within the struct. Convert the gbt coinbase and +stratum strings within the work struct to heap ram. This will allow arbitrary +lengths without an upper limit for the strings, preventing the overflows that +happen with GBT. +- libztex: Work around ZTEX USB firmware bug exposed by the FreeBSD libusb +- opencl: Use new dev_error function for REASON_DEV_NOSTART + + +Version 2.9.4 - November 18, 2012 + +- Provide rudimentary support for the balancing failover strategies with stratum +and GBT by switching pools silently on getwork requests. +- Convert remaining modminer and bfl uses of usleep to nmsleep. +- Convert libztex to nmsleep where possible. +- Convert unreliable usleep calls to nmsleep calls in ztex driver. +- Support workid for block submission on GBT pools that use it. +- Provide rudimentary support for literal ipv6 addresses when parsing stratum +URLs. +- Work around libcurl cflags not working on hacked up mingw installations on +windows. +- Only increase gpu engine speed by a larger step if the temperature is below +hysteresis instead of increasing it to max speed. +- Convert pool not responding and pool alive message on backup pools to verbose +level only since they mean a single failed getwork. +- Update work block on the longpoll work item before calling restart threads to +ensure all work but the longpoll work item gets discarded when we call +discard_stale from restart_threads. +- Do not attempt to remove the stratum share hash after unsuccessful submission +since it may already be removed by clear_stratum_shares. +- Check against a double for current pool diff. +- Support for fractional diffs and the classic just-below-1 share all FFs diff +target. + + +Version 2.9.3 - November 11, 2012 + +- Make header larger on gen stratum work to accomodate \0 at the end. + + +Version 2.9.2 - November 11, 2012 + +- Use stratum block change from backup pools as an alternative to longpoll for +pools that don't support LP. +- Check share target diff for best_share to be calculated when solo mining. +- Round some more static string arrays to 4 byte boundaries. +- There is no need for the static arrays to be larger than required, so long as +they're 4 byte aligned to appease ARM. +- Store the full stratum url information in rpc_url for correct configuration +file saving. +- Put in a hack to prevent dud work from sneaking into test_work_current being +seen as a new block. +- Reset the work->longpoll flag where it will affect stratum work items as well. +- Check for both coinbase/append and submit/coinbase support before using GBT +protocol. +- First pass through testing for GBT should not set probed to true since we are +about to probe again. +- Hash1 is only used by the deprecated cpu mining code and never changes so +remove it from the work struct and bypass needing to process the value for all +other mining. +- Get a work item once per minute for all getwork and GBT pools to test they're +still alive and to maintain a current GBT template. +- Get a fresh block template with GBT pools on switching to them. + + +Version 2.9.1 - November 6, 2012 + +- Reset work flags to prevent GBT shares from being submitted as stratum ones +after switching. + + +Version 2.9.0 - November 6, 2012 + +- Add endian swap defines for where missing. +- Only retarget stratum shares to new pool diff if diff has dropped. +- Remove resetting of probed variable when detecting GBT. +- Count lost stratum share submits and increase message priority to warning. +- Only retrieve a new block template for GBT pools that are the current pool. +- Show which pool untracked share messages have come from. +- Add management for dead GBT pools. +- Count lost shares with stratum as submit stale lost. +- Discard record of stratum shares sent and report lost shares on disconnection +since they will never be reported back. +- Swab, don't just swap the bytes in the GBT target. +- Change status window message for GBT connected pools versus LP. +- Generate a gbt work item from longpoll when required to set new block and +message appropriately. +- Use existing pool submit_old bool from gbt data. +- Retrieve a new block template if more than 30 seconds has elapsed since the +last one to keep the data current and test the pool is still alive. +- Update GBT longpollid every time we request a new longpoll. +- Manage appropriate response codes for share submission with GBT. +- Allow the longpoll thread to start with GBT and only set the longpollid once. +- Correct last few components of GBT block generation courtesy of Luke-jr. +- Use correct length for offsetting extra nonce and remaining data. +- Flip all 80 bytes in the flip function which was wrongly named flip256 for its +purpose. +- Calculate midstate for gbt work and remove now unused variable. +- Use a standard function for flipping bytes. +- Insert the extra nonce and remaining data in the correct position in the +coinbase. +- Remove txn size debugging and enlarge gbt block string to prevent overflow. +- Remove varint display debugging. +- Build varint correctly for share submission and sleep 5 seconds before +retrying submit. +- Make gbt_coinbase large enough for submissions, swap bytes correctly to make a +header from GBT and encode the number of transactions in share submission. +- Store the fixed size entries as static variables in GBT in binary form, +byteswapping as is required. +- 32 bit hex encoded variables should be in LE with GBT. +- Target and prevblockhash need to be reversed from GBT variables. +- Construct block for submission when using GBT. +- Use same string for debug as for submission and make string larger to cope +with future GBT messages. +- Skip trying to decipher LP url if we have GBT support. +- Store all the transaction hashes in pool->txn_hashes instead of separating +txn0 and correct generation of merkle root, fixing memory overwrites. +- Hook into various places to generate GBT work where appropriate. +- Create extra work fields when generating GBT work. +- Generate header from correct hashing generation of the merkle root for GBT. +- Generate the merkle root for gbt work generation. +- Create a store of the transactions with GBT in the minimum size form required +to generate work items with a varied coinbase. +- Create a function that generates a GBT coinbase from the existing pool +variables. +- Extract and store the various variables GBT uses when decoding gbt work. +- Check for invalid json result in work_decode. +- Decode work in separate functions for getwork vs gbt. +- Check for the coinbase/append mutable in GBT support to decide whether to use +it or not. +- Add a gbt mutex within the pool struct for protecting the gbt values. +- Convert work decode function to prepare for decoding block templates. +- Check for GBT support on first probing the pool and convert to using the GBT +request as the rpc request for that pool. +- Make the rpc request used with getwork a pool variable to allow it to be +converted to/from gbt requests. +- Changes to build prototypes to support building on FreeBSD 9.1-RC2 amd64 +- Free old stratum_work data before replacing it +- There is no need for addrinfo any more. +- server and client sockaddr_in are no longer used in struct pool. +- Merge pull request #322 from luke-jr/bugfix_stratum_tmpwork +- Set sshare id and swork_id within the sshare mutex to avoid multiple share +submits with the same id. +- Initialize temporary stratum work + + +Version 2.8.7 - October 29, 2012 + +- Fail on select() failing in stratum thread without needing to attempt +recv_line. +- Add share to stratum database before sending it again in case we get a +response from the pool before it's added. + + +Version 2.8.6 - October 29, 2012 + +- Shorten the initiate stratum connect timeout to 30 seconds. +- Shorten the stratum timeout on read to 90 seconds to detect unresponsive pool. +- Display best share difficulty on exit. +- Make stratum socket fail more robust on windows by disabling the send buffer. +- Reuse the same curl handle forcing a new connection instead of risking +derefencing. +- Add information about submission failure to stratum send. +- Only add stratum share to database if we succeeded in submitting it, with a +debug output saying it succeeded. +- Use keepalive with stratum sockets to improve its ability to detect broken +connections. +- Show only the URL in the status bar to avoid long prefixes making for extra +long lines. +- Display compact status in menu and update README to reflect current menu +entries. +- Add a compact display mode that does not list per device statistics in the +status window. +- Add blank spaces after best share displayed. +- Round a few static string arrays up to 4 byte boundaries for ARM. +- Display best share diff for scrypt as well. +- Show the best diff share as "best share" and add info to the README. +- Display the best diff share submitted so far. +- Redundant check. +- The work struct pointer in struct pc_data in findnonce is never freed yet +there is no need to allocate it separately so make struct work a static part of +the struct pc_data. s + + +Version 2.8.5 - October 23, 2012 + +- Handle crash exceptions by trying to restart cgminer unless the --no-restart +option is used. +- Switch queued count when choosing a different pool from a failed stratum pool +in getwork thread. +- Put a mandatory 5s wait between reattempting a getwork on failure to avoid +hammering requests. +- The ATI stream / AMD APP SDK environment variables appear to only interfere +with win32 builds so bypass them. +- Make sure to check pool stratum curl exists under lock before attempting any +recv to not risk dereferencing upon attempting to reinitiate stratum. +- Avoid redefining macros and align to 4 byte boundaries. +- API - add Stratum information to pools +- update FPGA-README for MMQ + + +Version 2.8.4 - October 18, 2012 + +- Time for dynamic is in microseconds, not ms. +- x86_64 builds of mingw32 are not supported directly and should just configure +as generic mingw32 builds since they're NOT 64 bit. +- Cope with both ATI stream and AMD APP SDK roots being set when building. +- Use 3 significant digits when suffix string is used and values are >1000. +- MMQ new initialisation (that works) and clocking control +- Get rid of unused warning for !scrypt. +- Use select on stratum send to make sure the socket is writeable. +- Cope with dval being zero in suffix_string and display a single decimal place +when significant digits is not specified but the value is greater than 1000. +- Pad out the suffix string function with zeroes on the right. +- Failure to calloc in bin2hex is a fatal failure always so just check for that +failure within the function and abort, simplifying the rest of the code. +- Provide locking around the change of the stratum curl structures to avoid +possible races. +- Bump opencl kernel version numbers. +- Remove atomic ops from opencl kernels given rarity of more than once nonce on +the same wavefront and the potential increased ramspeed requirements to use the +atomics. +- Clear the pool idle flag in stratum when it comes back to life. +- Display correct share hash and share difficulty with scrypt mining. +- Use explicit host to BE functions in scrypt code instead of hard coding +byteswap everywhere. +- Show work target diff for scrypt mining. +- Ease the checking on allocation of padbuffer8 in the hope it works partially +anyway on an apparently failed call. +- Watch for buffer overflows on receiving data into the socket buffer. +- Round target difficulties down to be in keeping with the rounding of detected +share difficulties. +- Dramatically simplify the dynamic intensity calculation by oversampling many +runs through the opencl kernel till we're likely well within the timer +resolution on windows. +- String alignment to 4 byte boundaries and optimisations for bin<->hex +conversions. +- In opencl_free_work, make sure to still flush results in dynamic mode. +- Align static arrays to 4 byte boundaries to appease ARM builds for stratum. + + +Version 2.8.3 - October 12, 2012 + +- Left align values that are suffix_string generated. +- Share_diff should not be converting the work data to hex. +- Off by one error. +- Prevent overflows of the port char array in extract_sockaddr. +- Disable stratum detection with scrypt. +- Use the suffix string function when displaying device hashrates. +- Be consistent with the get_statline function. +- Use the suffix string function for displaying hashrate with 4 significant +digits. +- Display the actual share diff next to the pool required diff, using a suffix +creation function to prevent values of >1000 being shown in their entirety. +- Fix 4 * 0 being 0 that would break dynamic intensity mode. +- Fix wrong byteswap macro being used on mingw32 which was breaking target +generation on stratum. + + +Version 2.8.2 - October 11, 2012 + +- Reinstate the history on dynamic intensity mode to damp fluctuations in +intensity but use an upper limit on how much the value can increase at any time +to cope with rare overflows. +- Create a fix-protocol option which prevents cgminer from switching to stratum +if it's detected. +- Simplify target generation code. +- Add support for client.get_version for stratum. +- Use a 64 bit unsigned integer on the diff target to generate the hex target. +- Update reconnect message to show whole address including port. +- Look for null values and parse correct separate array entries for url and port +with client reconnect commands for stratum. +- The command for stratum is client.reconnect, not mining.reconnect. +- Only copy the stratum url to the rpc url if an rpc url does not exist. +- Implement rudimentary mining.reconnect support for stratum. +- Ignore the value of stratum_active on calling initiate_stratum and assume +we're always trying to reinitiate it, and set the active flag to false in that +function. +- stratum auth can be unset if we fail to authorise on subsequent calls to +auth_stratum which undoes the requirement of setting it in one place so set it +in pool_active. + + +Version 2.8.1 - October 8, 2012 + +- Use the stratum url as the rpc url advertised if we switch to it. +- Count an invalid nonce count as a hardware error on opencl. +- Count each stratum work item as local work. +- Cope with one stratum pool being the only active pool when it dies by sleeping +for 5 seconds before retrying to get work from it instead of getting work +indefinitely. +- Detect stratum outage based on either select timing out or receiving an empty +buffer and properly re-establish connection by disabling the stratum_active +flag, coping with empty buffers in parse_stratum. + + +Version 2.8.0 - October 7, 2012 + +- Major upgrade - support for the stratum mining protocol. +- Fix various modminer warnings on mingw. +- Fix sign warning on windows build for bitforce. +- Cast socketfail to integer since SOCKET is an unsigned int on windows. +- Use strtod not strtol for bitforce temp backup. +- Cope with broken drivers returning nonsense values for bitforce temperatures. +- Minor warning fixes. +- Use the stratum thread to detect when a stratum pool has died based on no +message for 2 minutes. +- Only set the stratum auth flag once and once the stratum thread is started, +use that to set/unset the stratum active flag. +- Only hand off to stratum from getwork if we succeed in initiating the +protocol. +- Target should only be 32 bytes copied. +- Use a static array for work submission data instead of stack memory. +- Clear the buffer data before sprinting to it. +- Clear work stratum strings before setting them and add them to debug output. +- Drop stratum connect failed message to verbose level only since it's a regular +probing message. +- TCP Keepalive in curl is only in very recent versions and not required with +regular messages on stratum anyway. +- Move stratum sockets to curl infrastructure with locking around send+recv to +begin support for proxies and ssl. +- Make detect stratum fail if a proxy has been set up. +- Stratum does not currently have any proxy support so do not try to switch to +stratum if a proxy has been specified. +- Windows doesn't work with MSG_PEEK on recv so move to a continuously updating +buffer for incoming messages. +- Alloca is unreliable on windows so use static arrays in util.c stratum code. +- Begin support for mingw stratum build. +- Add space to reject reason. +- Parse the reject reason where possible from stratum share submission. +- Pass json error value to share result function to be able to parse reject +reason in stratum. +- Don't try to parse unneeded parameters in response to mining.subscribe. +- Remove the sshare hash entry if we failed to send it. +- Change notify message to info level to avoid spamming repeatedly when a pool +is down. +- Check the stratum pool difference has not changed compared to the work diff +when testing whether a share meets the target or not and retarget if necessary. +- Bit error in target calculation for stratum. +- Set work_block in gen_stratum_work for when work is reused to avoid thinking +it's all stale. +- Offset the current block detection to the prev block hash. +- We should be testing for id_val, not id in parse stratum response. +- Make target on stratum scale to any size by clearing sequential bits according +to diff. +- Correct target calculation in gen_stratum_work. +- If a share result has an error code but still has an id, it is likely a +reject, not an error. +- Initiate stratum the first time in pool_active only, allowing us to switch to +it on getting a failed getwork and detecting the presence of stratum on the url +at that time. +- Use 5 second timeout on sock full for now as a temporary workaround. +- If no stratum url is set by the end of the detect stratum routine, copy the +sockaddr url. +- Make all buffers slightly larger to prevent overflow. +- Make the stratum recv buffer larger than the recvsize. +- Userpass needs to be copied to user and pass earlier to allow stratum +authorisation to work with it. +- Store a sockaddr url of the stripped url used in determining sockaddr to not +confuse it with the stratum url and fix build warnings. +- Decrease the queued count with stratum work once it's staged as well. +- Allow the stratum retry to initiate and auth stratum in pool_alive to make +sure the stratum thread is started. +- Avoid duplicating pool->rpc_url and setting pool->stratum_url twice to itself. +- Detect if a getwork based pool has the X-Stratum header on startup, and if so, +switch to the stratum based pool. +- Comment update. +- Minor message change. +- Create a work item from a "clean" request from stratum allowing the new block +to be detected and the appropriate block change message to be given. +- Use statically allocated stratum strings in struct work to cope with the +inability to safely deallocate dynamically allocated ram. +- Use the current pool when deciding whether to reuse work from a stratum source +rather than the work's previous pool. +- Copy the stratum url to the rpc url to avoid none being set. +- Provide locking around stratum send operations to avoid races. +- Submit shares from stratum through the abstracted submit share function +detecting what message they belong to and showing the data from the associated +work, and then deleting it from the hash. +- Use a more robust mechanism to obtain a \n terminated string over a socket. +- Abstract out share submit as a function to be useable by stratum. +- Rename parse_stratum to parse_method as it is only for stratum messages that +contain methods. +- Display stratum as mechanism in status line when current pool is running it. +- Count each stratum notify as a getwork equivalent. +- Correct nonce submitted with share. +- Extranonce2 should be added before coinbase2. +- We should be hashing the binary coinbase, not the hex one. +- Fix endianness of nonce submitted for stratum. +- Check that stratum is already active in initiate_stratum to avoid +de-authorising ourselves by subscribing again. +- Begin implementing a hash database of submissions and attempt sending results. +- Copy parameters from stratum work required for share submission. +- Set lagging flag on first adding a pool to prevent pool slow warning at +startup. +- Fix work->target being a 32 byte binary in gen_stratum_work. +- Store and display stripped url in its own variable. +- Create machinery to divert work requests to stratum. +- Generate the work target in gen_stratum_work, setting default diff to 1 in +case it is not yet set. +- Generate work data, midstate and hash1 in gen_stratum_work. +- Generate header created from stratum structures in gen_stratum_work. +- Generate merkle root hash in gen_stratum_work. +- Generate the coinbase for generation of stratum based work. +- The number of transactions is variable so make merkle a variable length +dynamically allocated array and track how many there are for stratum. +- Rename nonce2 to n2size reflecting that it's a size variable and not the +actual nonce. +- Provide rudimentary support for stratum clean work command in the stratum +thread. +- Cope with pools being removed in the stratum thread. +- Use the pool sock value directly in the stratum thread in case it changes +after reconnecting. +- Create a stratum thread per pool that has stratum that monitors the socket and +serves received data. +- Check return value of stratum_parse. +- Complete authorisation in stratum. +- Implement stratum parsing of notify parameters and storing them in the pool +stratum work structure. +- Create helper functions for duplicating json strings to avoid keeping json +references in use. +- Append \n in the sock_send function instead of adding it when constructing +json in stratum. +- Don't keep any json references around with stratum structures. +- Create parse_stratum function that hands off stratum parameters to other +functions to manage pool stratum work struct variables. Implement mining +difficulty setting. +- Create helper functions for checking when a socket is ready to read on and +receive a single line at a time. Begin stratum authorisation process. +- Provide a helper function for reading a single \n terminated string from a +socket. +- Create a stratum work structure to store current work variables. +- Test specifically for stratum being active in pool_active. +- Detect stratum in common place when adding urls, and use a bool to tell us +when it's active. +- Fix warnings. +- Extract and store various parameters on stratum init confirming successful +mining notify. +- Use existing socket macros and close the socket on failure in init stratum. +- Initiate stratum and grab first json result. +- Get detailed addressinfo from the parsed URL for future raw socket usage when +possible. IPV4 only for now. +- Prepare for getaddrinfo call. +- Add data structures to pool struct for socket communications. +- Put all socket definitions in util.h to allow reusing by added socket +functions to be used in util.c. + + +Version 2.7.7 - October 7, 2012 + +- Fix unused warnings on ming build. +- Fix sign warning in ocl.c +- fds need to be zeroed before set in modminer. +- Put scrypt warning on separate line to avoid 0 being shown on windows as +bufsize. +- Display correct pool number when block is found. +- Prevent corrupt values returned from the opencl code from trying to read +beyond the end of the buffer by masking the value to a max of 15. +- Icarus USB write failure is also a comms error +- api.c DEBUG message has no paramter +- Icarus catch more USB errors and close/reopen the port +- API-README update cgminer verison number +- hashmeter fix stats kh/s on 32bit windows + + +Version 2.7.6 - September 24, 2012 + +- Reorder libztex header include order to fix missing struct definition. +- Display share difficulty on log with a shortened hash display on submission. +- API stats add some pool getwork difficulty stats +- Ignore any pings pushed to the worker threads if the thread is still paused to +prevent it being enabled and disabled repeatedly. +- README - FAQ - usermod group - shouldn't remove other groups +- Test for sequential getwork failures on a pool that might actually be up but +failing to deliver work as we may end up hammering it repeatedly by mistake. +- reduce windows compile warnings +- util.c - bug - proxy - no data end condition +- As we average gpu time over 5 work intervals for dynamic GPU intensity, there +is no need to maintain a rolling average and it avoids the potential long term +corruption of a single overflow value. +- Test for the now-automatically exported variable AMDAPPSDKROOT when looking +for the presence of the OpenCL headers. +- API don't change 'Diff1 Shares' - backward compatability FTW +- miner.php highlighting correctly handling difficulty +- API - Add last share difficulty for devices and pool +- Store and report Accepted,Rejected,Stale difficulty in the summary and API +- WorkTime - display prevblock for scrypt +- api.c remove compile warnings +- Calculate work difficulty for each getwork and display with WorkTime debug +- remove MMQ unused variable warning +- FPGA - allow long or short device names in detect code + style police +- WorkTime - multiple nonce per work and identify the work source +- Optional WorkTime details with each Accepted/Rejected work item +- Icarus - ignore hardware errors in timing mode +- miner.php oops - mistype +- miner.php by default don't display IP/Port numbers in error messages +- api.c all STATUS messages automatically escaped +- api.c add missing escape for comma in MSG_PGAUNW +- API add display of and setting queue,scantime,expiry +- HW: dont submit bad shares +- save individual pool proxy settings to config +- --default-config - allow command line to define the default configuration file +for loading and saving +- API-README update for pools proxy info +- README URL proxy must use quote so show in the example +- bug: remove proxy: from the front of the proxy used +- CURL support for individual proxy per pool and all proxy types +- README spelling/etc +- README - FPGA device FAQ +- HW: error counter auto for all devices - ztex code not fixed +- API pgaidentify - unsupported message should be a warning +- API/BFL identify a device - currently only BFL to flash the led +- BFL add throttle count to internal stats + API +- BFL: missing device id in log message +- miner.php correct to new Diff1 Work field names +- API add device diff1 work +- API-README update +- api.c Correct diff1 field name +- count device diff1 shares +- API-README more debug parameter information +- API allow full debug settings control + + +Version 2.7.5 - August 31, 2012 + +- Adjust opencl intensity when adjusting thread count to prevent it getting +pegged at a value below the minimum threads possible. +- miner.h max_hashes -> int64_t +- Keep the local block number in the blocks structs stored and sort them by +number to guarantee we delete the oldest when ageing the block struct entries. +- Use correct sdk version detection for SDK 2.7 +- Revert "Pick worksize 256 with Cypress if none is specified." +- Test for lagging once more in queue_request to enable work to leak to backup +pools. +- There is no need to try to switch pools in select_pool since the current pool +is actually not affected by the choice of pool to get work from. +- Only clear the pool lagging flag if we're staging work faster than we're using +it. +- needed flag is currently always false in queue_request. Remove it for now. +- thr is always NULL going into queue_request now. + + +Version 2.7.4 - August 23, 2012 + +- Perform select_pool even when not lagging to allow it to switch back if needed +to the primary. +- Simplify macros in output kernels avoiding apparent loops and local variables. +- Carry the needed bool over the work command queue. +- Move the decision to queue further work upstream before threads are spawned +based on fine grained per-pool stats and increment the queued count immediately. +- Track queued and staged per pool once again for future use. +- OpenCL 1.0 does not have native atomic_add and extremely slow support with +atom_add so detect opencl1.0 and use a non-atomic workaround. +- Pools: add RollTime info to API 'stats' and 'Stats' button in miner.php + + +Version 2.7.3 - August 22, 2012 + +- Minimise the number of getwork threads we generate. + + +Version 2.7.2 - August 22, 2012 + +- Pick worksize 256 with Cypress if none is specified. +- Give warning with sdk2.7 and phatk as well. +- Whitelist sdk2.7 for diablo kernel as well. +- Only keep the last 6 blocks in the uthash database to keep memory usage +constant. Storing more is unhelpful anyway. +- BFL Flash - always distribute source +- Increase kernel versions signifying changed APIs. +- BFL flash - include source in builds and more FPGA-README +- Check we haven't staged work while waiting for a curl entry before proceeding. +- Use atomic ops to never miss a nonce on opencl kernels, including nonce==0, +also allowing us to make the output buffer smaller. +- Remove compile errors/warnings and document compile/usage in FPGA-README +- bitforce-firmware-flash.c by Luke-jr +- Ignore the submit_fail flag when deciding whether to recruit more curls or not +since we have upper bounds on how many curls can be recruited, this test is +redundant and can lead to problems. +- API-README update cgminer version number +- API-README fix groups P: example mistake +- API-README add COIN and other edits +- gpu->hit should be reset on new work as well. +- Do not add time to dynamic opencl calculations over a getwork. +- miner.php allow 'coin' is custom pages + + +Version 2.7.1 - August 21, 2012 + +- Update windows build instructions courtesy of sharky. +- Increase max curls to number of mining threads + queue * 2, accounting for up +and downstream comms. +- Queue enough requests to get started. +- There is no point trying to clone_work in get_work() any more since we clone +on every get_work_thread where possible. +- There is no point subtracting 1 from maxq in get_work_thread. +- Only set lagging flag once there are no staged work items. +- select_pool does not switch back to the primary once lagging is disabled. +- miner.php allow page title to be defined in myminer.php +- Free work before retrying in get_work_thread. +- Increment total work counter under mutex lock. +- Increment the queued count after the curl is popped in case there's a delay +waiting on curls and we think we've queued work when in fact we're waiting +- API new command 'coin' with mining information +- Do the dynamic timing in opencl code over a single pass through scanhash to +make sure we're only getting opencl times contributing to the measured inte +- Increase curl reaping time to 5 minutes since comms between curl requests can +be 2 mins apart with lots of rolltime. +- No need for extra variable in hash_push. +- Remove short options -r and -R to allow them to be reused and remove readme +entries for deprecated options. +- Avoid attempting to recursively lock the console mutex by disabling warnings +in gpu_fanpercent when fanspeed monitoring fails on windows. Debugged by l +- Deprecate the opt_fail_pause parameter, leaving a null placeholder for +existing configurations. +- Don't pause after failed getwork, set lagging flag and reassess. +- Add message to share if it's a resubmit. +- We should not be pausing in trying to resubmit shares. +- Get rid of the extending fail pause on failed connects since we discard work +after a period. +- get_work always returns true so turn it into a void function. +- get_work never returns false so get rid of fail pause loop. +- Get rid of pause and retry from get_upstream_work so we only do it from one +place. +- Deprecate the opt_retries feature as no one wants cgminer to automatically +abort. Leave a null placeholder for configurations that still have it. +- Reinstate fix ADL gpu-map not working when there are more ADL devices than +openCL patch by Nite69. Add virtual adl mapping for when none is specified o +- miner.php show summary Diff1 Shares total +- miner.php fix Work Utility totals +- miner.php format new Work Utility and Diff1 Shares +- API V1.17 show Work Utility and Diff1 Shares + + + +Version 2.7.0 - August 18, 2012 + +- Introduce a new statistic, Work Utility, which is the number of difficulty 1 +shares solved per minute. This is useful for measuring a relative rate of work +that is independent of reject rate and target difficulty. +- Implement a new pool strategy, BALANCE, which monitors work performed per pool +as a rolling average every 10 minutes to try and distribute work evenly over all +the pools. Do this by monitoring diff1 solutions to allow different difficulty +target pools to be treated equally, along with solo mining. Update the +documentation to describe this strategy and more accurately describe the +load-balance one. +- Getwork fail was not being detected. Remove a vast amount of unused variables +and functions used in the old queue request mechanism and redefine the getfail +testing. +- Don't try to start devices that don't support scrypt when scrypt mining. +- 0 is a valid return value for read so only break out if read returns -1. +- Consider us lagging only once our queue is almost full and no staged work. +- Simplify the enough work algorithm dramatically. +- Only queue from backup pools once we have nothing staged. +- Don't keep queueing work indefinitely if we're in opt failover mode. +- Make sure we don't opt out of queueing more work if all the queued work is +from one pool. +- Set lagging flag if we're on the last of our staged items. +- Reinstate clone on grabbing work. +- Grab clones from hashlist wherever possible first. +- Cull all the early queue requests since we request every time work is popped +now. +- Keep track of staged rollable work item counts to speed up clone_available. +- Make expiry on should_roll to 2/3 time instead of share duration since some +hardware will have very fast share times. +- Do the cheaper comparison first. +- Check that we'll get 1 shares' worth of work time by rolling before saying we +should roll the work. +- Simplify all those total_secs usages by initialising it to 1 second. +- Overlap queued decrementing with staged incrementing. +- Artificially set the pool lagging flag on pool switch in failover only mode as +well. +- Artificially set the pool lagging flag on work restart to avoid messages about +slow pools after every longpoll. +- Factor in opt_queue value into enough work queued or staged. +- Roll work whenever we can on getwork. +- Queue requests for getwork regardless and test whether we should send for a +getwork from the getwork thread itself. +- Get rid of age_work(). +- 0 is a valid return value for read so only break out if read returns -1. +- Offset libusb reads/writes by length written as well in ztex. +- Cope with timeouts and partial reads in ztex code. +- fpga serial I/O extra debug (disabled by default) + + +Version 2.6.5 - August 15, 2012 + +- Don't try to get bitforce temperature if we're polling for a result to +minimise the chance of interleaved responses. +- Set memory clock based on memdiff if present from with engine changes, +allowing it to parallel manual changes from the menu as well. +- Increase the timeout on bitforce as per Paul Sheppard's suggestion to account +for throttling + work time + excess. +- Fix ADL gpu-map not working when there are more ADL devices than openCL. +Initial patch supplied by Nite69. Modified to suit. +- Windows' timer resolution is limited to 15ms accuracy. This was breaking +dynamic intensity since it tries to measure below this. Since we are repeatedly +sampling similar timeframes, we can average the gpu_us result over 5 different +values to get very fine precision. +- Fix harmless unused warnings in scrypt.h. +- api.c typo +- API allow display/change failover-only setting +- Check we are not lagging as well as there is enough work in getwork. +- Minimise locking and unlocking when getting counts by reusing shared mutex +lock functions. +- Avoid getting more work if by the time the getwork thread is spawned we find +ourselves with enough work. +- The bitforce buffer is cleared and hw error count incremented on return from a +failed send_work already so no need to do it within the send_work function. +- miner.php allow a custom page section to select all fields with '*' - e.g. to +create a STATS section on a custom page +- Escape " and \ when writing json config file +- miner.php optional single rig totals (on by default) + + +Version 2.6.4 - August 7, 2012 + +- Convert the serial autodetect functions to use int instead of char to +enumerate devices. +- Make the serial open timeout for BFL generically 1 second on windows. +- Deuglify windows autodetect code for BFL. +- There is no point zeroing temperature in BFL if we fail to get a response, and +we should register it as a HW error, suggesting throttling. +- Update SCRYPT README with information about HW errors. +- Use the scrypt CPU code to confirm results from OCL code, and mark failures as +HW errors, making it easier to tune scrypt parameters. +- We may as well leave one curl still available per pool instead of reaping the +last one. +- Need to recheck the pool->curls count on regaining the pool lock after the +pthread conditional wait returns. +- Display reaped debug message outside mutex lock to avoid recursive locking. +- Add specific information when ADL detects error -10 saying the device is not +enabled. +- api.c update API start message and include port number +- miner.php ignore arg when readonly +- miner.php allow pool inputs: delete, addpool, poolpriority + + +Version 2.6.3 - August 5, 2012 + +- Count likely throttling episodes on bitforce devices as hardware errors. +- Style cleanups. +- Use FTD2XX.DLL on Windows to autodetect BitFORCE SHA256 devices. +- Make pool_disabled the first in the enums == 0, fixing the pool enabled count +which compares if value is not enabled before enabling it. +- Correct writing of scrypt parameters to config file based on command line +parameters only. +- Use different variables for command line specified lookup gap and thread +concurrency to differentiate user defined versus auto chosen values. +- Queue a request on pool switch in case we have no work from the new pool yet. +- Display failover only mode in pool menu and allow it to be toggled live. +- Reinstate check for system queueing lag when the current pool's queue is maxed +out, there is no staged work, and the work is needed now. +- There is no need for pool active testing to be mandatory any more with queue +request changes. +- Fix harmless warnings. +- Check the current staged and global queued as well before queueing requests. +Discard stales before ageing work in the watchdog thread. Queue requests after +discarding and ageing work in watchdog thread. Display accurate global queued in +curses output. Reuse variable in age_work(). +- The queueing mechanism has become a complex state machine that is no longer +predictable. Rewrite it from scratch watching only current queues in flight and +staged work available on a pool by pool basis. +- API remove unused warning in non-GPU compile +- api.c in linux allow to open a closed socket in TIME_WAIT +- Queue an extra request whenever staged work drops below mining thread count in +hash_pop. +- Update debian package configs to v2.6.2 + + +Version 2.6.2 - August 3, 2012 + +- Scrypt mining does not support block testing yet so don't try to print it. +- Clear the bitforce buffer whenever we get an unexpected result as it has +likely throttled and we are getting cached responses out of order, and use the +temperature monitoring as a kind of watchdog to flush unexpected results. +- It is not critical getting the temperature response in bitforce so don't +mandatorily wait on the mutex lock. +- Check there is a cutoff temp actually set in bitforce before using it as a cut +off value otherwise it may think it's set to zero degrees. +- We dropped the temporary stopping of curl recruiting on submit_fail by +mistake, reinstate it. +- Make threads report in either side of the scanhash function in case we miss +reporting in when restarting work. +- Don't make mandatory work and its clones last forever. +- Make test work for pool_active mandatory work items to smooth out staged work +counts when in failover-only mode. +- Add debugging output when work is found stale as to why. +- Print the 3 parameters that are passed to applog for a debug line in +bitforce.c +- Clear bitforce buffer on init as previously. +- Add some headroom to the number of curls available per pool to allow for +longpoll and sendwork curls. +- Revert "Revert "Change BFL driver thread initialising to a constant 100ms +delay between devices instead of a random arrangement."" +- Revert "Remove bitforce_thread_init" +- Show the correct base units on GPU summary. +- Differentiate between the send return value being a bool and the get return +value when managing them in bitforce scanhash. +- 23a8c60 Revert "bitforce: Skip out of sending work if work restart requested" + + +Version 2.6.1 - July 30, 2012 + +- Display scrypt as being built in as well. +- Fix build warning about KL_SCRYPT when built without scrypt support. +- Remove the low hash count determinant of hardware being sick. A low hash rate +can be for poor network connectivity or scrypt mining, neither of which are due +to a sick device. +- api.c poolpriority changes + + +Version 2.6.0 - July 29, 2012 + +- Display kilohash when suitable, but store the global mhash value still truly +in megahashes to not break the API output. +- Don't try and print curses output for devices that won't fit on the screen. +- Add scrypt documentation in the form of a separate readme. +- Fix build error without scrypt enabled. +- Limit total number of curls recruited per pool to the number of mining threads +to prevent blasting the network when we only have one pool to talk to. +- bitforce: Skip out of sending work if work restart requested +- Keep a counter of enabled pools and use that instead of iterating over the +pool list. Use that value to ensure we don't set the last remaining active pool +to the rejecting state. +- fpgautils: add support for 57.6 kBd serial +- miner.php add a socket RCV timeout for if cgminer is hung and the API thread +is still running +- Limit thread concurrency for scrypt to 5xshaders if shaders is specified. +- Simplify repeated use of gpus[gpu]. in ocl.c +- Find the nearest power of 2 maximum alloc size for the scrypt buffer that can +successfully be allocated and is large enough to accomodate the thread +concurrency chosen, thus mapping it to an intensity. +- Don't make opt_scrypt mandatory blocking with opencl code. +- Update kernel versions reflecting changes in the API. +- Make the thread concurrency and lookup gap options hidden on the command line +and autotune parameters with a newly parsed --shaders option. +- Fix target testing with scrypt kernel as it would have been missing shares +below target. +- Bugfix: Use a mutex to control non-curses output +- Simplify code to a single vprintf path for curses-less printing +- Move opt_quiet check to my_log_curses, so it works for curses-less builds +- Use log_generic for vapplog to cut down on code duplication +- Add space to log output now that there is more screen real estate available. +- BFL force all code to timeout to avoid hanging +- Bugfix: Copy argv[0] given to dirname() +- Always create the largest possible padbuffer for scrypt kernels even if not +needed for thread_concurrency, giving us some headroom for intensity levels. +- Use the detected maximum allocable memory on a GPU to determine the optimal +scrypt settings when lookup_gap and thread_concurrency parameters are not given. +- Check the maximum allocable memory size per opencl device. +- Add debugging output if buffer allocation fails for scrypt and round up +bufsize to a multiple of 256. +- Nonce testing for btc got screwed up, leading to no accepted shares. Fix it. +- Display size of scrypt buffer used in debug. +- Allow intensities up to 20 if scrypt is compiled in. +- Add name to scrypt kernel copyright. +- Allow lookup gap and thread concurrency to be passed per device and store +details in kernel binary filename. +- Ignore negative intensities for scrypt. +- Change the scale of intensity for scrypt kernel and fix a build warning. +- Correct target value passed to scrypt kernel. +- Use 256 output slots for kernels to allow 1 for each worksize. +- Test the target in the actual scrypt kernel itself saving further +calculations. +- Reinstate GPU only opencl device detection. +- Decrease lookup gap to 1. Does not seem to help in any way being 2. +- Fix build. +- Make pad0 and pad1 local variable in scrypt kernel. +- Constify input variable in scrypt kernel. +- Send correct values to scrypt kernel to get it finally working. +- Create command queue before compiling program in opencl. +- Detach pthread from within the api thread in case it is terminated due to not +being instantiated before pthread_cancel is called from main, leading to a +segfault. +- Debug output per thread hashrate is out by a factor of 1000. +- Initialise mdplatform. +- Find the gpu platform with the most devices and use that if no platform option +is passed. +- Allow more platforms to be probed if first does not return GPUs. +- Fix external scrypt algo missing. +- Limit scrypt to 1 vector. +- Handle KL_SCRYPT in config write. +- Get rid of stuff. +- Don't enqueuewrite buffer at all for pad8 and pass work details around for +scrypt in dev_blk. +- Set the correct data for cldata and prepare for pad8 fixes. +- Bugfix: Fix build without curses but with OpenCL +- Find the gpu platform with the most devices and use that if no platform option +is passed. +- Allow more platforms to be probed if first does not return GPUs. +- Get rid of spaces in arrays in scrypt kernel. +- Start with smaller amount of hashes in cpu mining to enable scrypt to return +today sometime. +- Show Khash hashrates when scrypt is in use. +- Free the scratchbuf memory allocated in scrypt and don't check if CPUs are +sick since they can't be. Prepare for khash hash rates in display. +- Add cpumining capability for scrypt. +- Set scrypt settings and buffer size in ocl.c code to be future modifiable. +- Cope with when we cannot set intensity low enough to meet dynamic interval by +inducing a forced sleep. +- Make dynamic and scrypt opencl calls blocking. +- Calculate midstate in separate function and remove likely/unlikely macros +since they're dependent on pools, not code design. +- bitforce: Use "full work" vs "nonce range" for kernel name +- Display in debug mode when we're making the midstate locally. +- Fix nonce submission code for scrypt. +- Make sure goffset is set for scrypt and drop padbuffer8 to something +manageable for now. +- Set up buffer8 for scrypt. +- Build fix for opt scrypt. +- Don't check postcalc nonce with sha256 in scrypt. +- Don't test nonce with sha and various fixes for scrypt. +- Make scrypt buffers and midstate compatible with cgminer. +- Use cgminer specific output array entries in scrypt kernel. +- Provide initial support for the scrypt kernel to compile with and mine scrypt +with the --scrypt option. +- Enable completely compiling scrypt out. +- Begin import of scrypt opencl kernel from reaper. +- bitforce_get_result returns -1 on error now. +- Check return value of read in BFgets +- Bugfix: Make our Windows nanosleep/sleep replacements standards-compliant +(which fixes nmsleep) and include compat.h for bitforce (for sleep) +- rpc: Use a single switch statement for both stringifications of cgpu->status +- Fix whitespace mangling. +- miner.php fix rig # when miners fail +- Only try to shut down work cleanly if we've successfully connected and started +mining. +- Use switch statement for cgpu->status and fix spelling. +- Abbrv. correction +- Bugfix: Don't declare devices SICK if they're just busy initialising +- Bugfix: Calculate nsec in nmsleep correctly +- Bugfix: Adapt OpenCL scanhash errors to driver API change (errors are now -1, +not 0) +- Remove superfluous ave_wait +- Put kname change for broken nonce-range back in +- Add average wait time to api stats +- Change BFL driver thread initialising to a constant 100ms delay between +devices instead of a random arrangement. +- Spelling typo. +- Time opencl work from start of queueing a kernel till it's flushed when +calculating dynamic intensity. +- Modify te scanhash API to use an int64_t and return -1 on error, allowing zero +to be a valid return value. +- Check for work restart after the hashmeter is invoked for we lose the hashes +otherwise contributed in the count. +- Remove disabled: label from mining thread function, using a separate +mt_disable function. +- Style changes. +- Missed one nonce-range disabling. +- Add average return time to api stats +- miner.php allow rig names in number buttons +- Remove bitforce_thread_init The delay thing does nothing useful... when long +poll comes around, all threads restart at the same time anyway. +- Change timeouts to time-vals for accuracy. +- fix API support for big endian machines +- Cope with signals interrupting the nanosleep of nmsleep. +- Use standard cfsetispeed/cfsetospeed to set baud rate on *nix +- miner.php split() flagged deprecated in PHP 5.3.0 +- More BFL tweaks. Add delay between closing and reopening port. Remove buffer +clear in re-init Add kernel type (mini-rig or single) +- Make long timeout 10seconds on bitforce for when usleep or nanosleep just +can't be accurate... + + +Version 2.5.0 - July 6, 2012 + +- Fix --benchmark not working since the dynamic addition of pools and pool +stats. +- Make disabling BFL nonce range support a warning since it has to be explicitly +enabled on the command line now. +- miner.php allow renaming table headers +- Make bitforce nonce range support a command line option --bfl-range since +enabling it decrease hashrate by 1%. +- Add sanity checking to make sure we don't make sleep_ms less than 0 in +bitforce. +- The fastest minirig devices need a significantly smaller starting sleep time. +- Use a much shorter initial sleep time to account for faster devices and nonce +range working, and increase it if nonce range fails to work. +- Use nmsleep instead of usleep in bitforce. +- Provide a ms based sleep function that uses nanosleep to avoid the inaccuracy +of usleep on SMP systems. +- delay_time_ms is always set so need not be initialised in bitforce. +- Increase bitforce timeout to 10 seconds. +- Add more hysteresis and poll ~5 times to allow for timer delays in bitforce +devices. +- miner.php allow alternating line colours (off by default) +- Display the actual duration of wait when it is greater than the cutoff. +- Set nonce to maximum once we determine nonce range support is broken. +- Initial wait time is always known so no need to zero it beforehand in +bitforce. +- No point counting wait time until the work is actually sent to bitforce +devices. +- Use string comparison functions instead of explicit comparisons. +- Account for wait_ms time when nonce_range is in use on BFL. +- Split nonces up into 1/5 chunks when nonce range is supported. +- limit clear buffer iterations. +- Ad fd check to clear buffer. +- miner.php remove incorrect 'DATE' error message +- miner.php allow summary header in custom pages +- Disable nonce range support in BFL when broken support is detected. +- Restart_wait is only called with a ms value so incorporate that into the +function. +- Only try to adjust dev width when curses is built in. +- miner.php define custom sum fields as a simple array +- Fix off-by-one error in nonce increment in bfl. +- Use BE when setting nonce in bitforce nonce range work. +- Enable nonce range in the normal init sequence for bfl. +- Queue extra work at 2/3 differently depending on whether we're using nonce +range or not. +- Initially enable support for nonce range support on bfl, splitting nonces up +into 3/4 size and only disable it if it fails on work submit. +- Attempt to detect nonce range support in BFL by sending work requring its +support. +- Limit retrying on busy for up to BITFORCE_TIMEOUT_MS +- Attempt to initialise while bitforce device returns BUSY. +- Extend length of string that can be passed to BFL devices. +- Fix signedness warning. +- Adjust device width column to be consistent. +- Use cgpu-> not gpus[] in watchdog thread. +- Add api stats (sleep time) +- Timing tweaks Added long and short timeouts, short for detecting throttling, +long to give up totally. Reset sleep time when device re-initialised Still check +results after timeout Back up a larger time if result on first poll. +- Add API Notify counter 'Comms Error' +- Style police on api.c +- Do all logging outside of the bitforce mutex locking to avoid deadlocks. +- Remove applog call from bfwrite to prevent grabbing nested mutexes. +- Bitforce style changes. +- Minor style changes. +- Remove needless roundl define. +- Made JSON error message verbose. +- Fine-tune timing adjustment. Also remove old work_restart timing. +- Check for gpu return times of >= 0, not just 0, to fix intensity dropping to +-10. +- Restart is zeroed in the mining thread so no need to do it inside the bitforce +code. +- More improvements to comms. BFL return nothing when throttling, so should not +be considered an error. Instead repeat with a longer delay. +- Polling every 10ms there's not much point checking the pthread_cond_timedwait +as it just adds overhead. Simply check the value of work_restart in the bfl main +polling loop. +- Use a pthread conditional that is broadcast whenever work restarts are +required. Create a generic wait function waiting a specified time on that +conditional that returns if the condition is met or a specified time passed to +it has elapsed. Use this to do smarter polling in bitforce to abort work, queue +more work, and check for results to minimise time spent working needlessly. +- Add busy time to wait time. +- api.c put version up to 1.14 +- Add tiny delay after writing to BFL Change BFL errors to something more human +readable Send work busy re-tries after 10ms delay + + +Version 2.4.4 - July 1, 2012 + +- Fix builds on non gnu platforms. +- api.c ensure old mode is always available when not using --api-groups + quit() +on param errors +- Implement rudimentary X-Mining-Hashrate support. +- Detect large swings in temperature when below the target temperature range and +change fan by amounts dependant on the value of tdiff. +- Adjust the fanspeed by the magnitude of the temperature difference when in the +optimal range. +- Revert "Restarting cgminer from within after ADL has been corrupted only leads +to a crash. Display a warning only and disable fanspeed monitoring." +- api.c fix json already closed +- implement and document API option --api-groups +- Put upper bounds to under 2 hours that work can be rolled into the future for +bitcoind will deem it invalid beyond that. +- define API option --api-groups +- api.c allow unwell devices to be enabled so they can be cured +- miner.php - fix/enable autorefresh for custom pages +- miner.php allow custom summary pages - new 'Mobile' summary +- Work around pools that advertise very low expire= time inappropriately as this +leads to many false positives for stale shares detected. +- Only show ztex board count if any exist. +- There is no need for work to be a union in struct workio_cmd +- fpgautils.c include a debug message for all unknown open errors +- Don't keep rolling work right up to the expire= cut off. Use 2/3 of the time +between the scantime and the expiry as cutoff for reusing work. +- Log a specific error when serial opens fail due to lack of user permissions +- Increase GPU timing resolution to microsecond and add sanity check to ensure +times are positive. +- Opencl code may start executing before the clfinish order is given to it so +get the start timing used for dynamic intensity from before the kernel is +queued. +- fpgautils.c - set BAUD rate according to termio spec +- fpgautils.c - linux ordering back to the correct way +- miner.php remove unneeded '.'s +- miner.php add auto refresh options +- miner.php add 'restart' next to 'quit' +- miner.php make fontname/size configurable with myminer.php +- Make the pools array a dynamically allocated array to allow unlimited pools to +be added. +- Make the devices array a dynamically allocated array of pointers to allow +unlimited devices. +- Dynamic intensity for GPUs should be calculated on a per device basis. Clean +up the code to only calculate it if required as well. +- Use a queueing bool set under control_lock to prevent multiple calls to +queue_request racing. +- Use the work clone flag to determine if we should subtract it from the total +queued variable and provide a subtract queued function to prevent looping over +locked code. +- Don't decrement staged extras count from longpoll work. +- Count longpoll's contribution to the queue. +- Increase queued count before pushing message. +- Test we have enough work queued for pools with and without rolltime +capability. +- As work is sorted by age, we can discard the oldest work at regular intervals +to keep only 1 of the newest work items per mining thread. +- Roll work again after duplicating it to prevent duplicates on return to the +clone function. +- Abstract out work cloning and clone $mining_threads copies whenever a rollable +work item is found and return a clone instead. +- api.c display Pool Av in json +- Take into account average getwork delay as a marker of pool communications +when considering work stale. +- Work out a rolling average getwork delay stored in pool_stats. +- Getwork delay in stats should include retries for each getwork call. +- Walk through the thread list instead of searching for them when disabling +threads for dynamic mode. +- Extend nrolltime to support the expiry= parameter. Do this by turning the +rolltime bool into an integer set to the expiry time. If the pool supports +rolltime but not expiry= then set the expiry time to the standard scantime. +- When disabling fanspeed monitoring on adl failure, remove any twin GPU +association. This could have been leading to hangs on machines with dual GPU +cards when ADL failed. +- modminer: Don't delay 2nd+ FPGAs during work restart +- Disable OpenCL code when not available. +- Fix openwrt crashing on regeneratehash() by making check_solve a noop. +- FPGA - allow device detect override without an open failure +- Fix sign warning. + + +Version 2.4.3 - June 14, 2012 + +- can_roll and should_roll should have no bearing on the cycle period within the +miner_thread so remove it. +- Check for strategy being changed to load balance when enabling LPs. +- Check that all threads on the device that called get_work are waiting on +getwork before considering the pool lagging. +- Iterate over each thread belonging to each device in the hashmeter instead of +searching for them now that they're a list. +- When using rotate pool strategy, ensure we only select from alive enabled +pools. +- Start longpoll from every pool when load balance strategy is in use. +- Add mandatory and block fields to the work struct. Flag any shares that are +detected as blocks as mandatory to submit, along with longpoll work from a +previously rejecting pool. +- Consider the fan optimal if fanspeed is dropping but within the optimal speed +window. +- Fix typo in some API messages (succeess/success) +- api.c MMQ stat bugs +- Bugfix: Fix warnings when built without libudev support +- Bugfix: slay a variety of warnings +- Bugfix: modminer: Fix unsigned/signed comparison and similar warnings +- API add ModMinerQuad support +- Bugfix: Honour forceauto parameter in serial_detect functions +- modminer: Temperature sensor improvements +- modminer: Make log messages more consistent in format +- Only adjust GPU speed up if the fanspeed is within the normal fanrange and +hasn't been turned to maximum speed under overheat conditions. +- ModMiner use valid .name +- New driver: BTCFPGA ModMiner +- Abstract generally useful FPGA code into fpgautils.c +- API add stats for pool getworks +- miner.php option to hide specific fields from the display +- miner.php add version numbers to the summary page +- Update debian configs to v2.4.2 +- Add API and FPGA READMEs into Makefile to be included in source distribution. +- Icarus - fix unit64_t printf warnings + + +Version 2.4.2 - June 2, 2012 + +- API.class compiled with Java SE 6.0_03 - works with Win7x64 +- miner.php highlight devs too slow finding shares (possibly failing) +- API update version to V1.11 and document changes +- API save default config file if none specified +- api.c save success incorrectly returns error +- api.c replace BUFSIZ (linux/windows have different values) +- Move RPC API content out of README to API-README +- Open a longpoll connection if a pool is in the REJECTING state as it's the +only way to re-enable it automatically. +- Use only one longpoll as much as possible by using a pthread conditional +broadcast that each longpoll thread waits on and checks if it's the current pool +before +- If shares are known stale, don't use them to decide to disable a pool for +sequential rejects. +- Restarting cgminer from within after ADL has been corrupted only leads to a +crash. Display a warning only and disable fanspeed monitoring. +- Icarus: fix abort calculation/allow user specified abort +- Icarus: make --icarus-timing hidden and document it in FPGA-README +- Icarus: high accuracy timing and other bitstream speed support +- add-MIPSEB-to-icarus-for-BIG_ENDIAN +- work_decode only needs swab32 on midstate under BIG ENDIAN +- add compile command to api-example.c +- save config bugfix: writing an extra ',' when no gpus +- Add dpkg-source commits + + +Version 2.4.1 - May 6, 2012 + +- In the unlikely event of finding a block, display the block solved count with +the pool it came from for auditing. +- Display the device summary on exit even if a device has been disabled. +- Use correct pool enabled enums in api.c. +- Import Debian packaging configs +- Ensure we test for a pool recovering from idle so long as it's not set to +disabled. +- Fix pool number display. +- Give cgminer -T message only if curses is in use. +- Reinit_adl is no longer used. +- API 'stats' allow devices to add their own stats also for testing/debug +- API add getwork stats to cgminer - accesable from API 'stats' +- Don't initialise variables to zero when in global scope since they're already +initialised. +- Get rid of unitialised variable warning when it's false. +- Move a pool to POOL_REJECTING to be disabled only after 3 minutes of +continuous rejected shares. +- Some tweaks to reporting and logging. +- Change FPGA detection order since BFL hangs on an ICA +- API support new pool status +- Add a temporarily disabled state for enabled pools called POOL_REJECTING and +use the work from each longpoll to help determine when a rejecting pool has +started working again. Switch pools based on the multipool strategy once a pool +is re-enabled. +- Removing extra debug +- Fix the benchmark feature by bypassing the new networking code. +- Reset sequential reject counter after a pool is disabled for when it is +re-enabled. +- Icarus - correct MH/s and U: with work restart set at 8 seconds +- ztex updateFreq was always reporting on fpga 0 +- Trying harder to get 1.15y working +- Specifying threads on multi fpga boards extra cgpu +- Missing the add cgpu per extra fpga on 1.15y boards +- API add last share time to each pool +- Don't try to reap curls if benchmarking is enabled. + + +Version 2.4.0 - May 3, 2012 + +- Only show longpoll warning once when it has failed. +- Convert hashes to an unsigned long long as well. +- Detect pools that have issues represented by endless rejected shares and +disable them, with a parameter to optionally disable this feature. +- Bugfix: Use a 64-bit type for hashes_done (miner_thread) since it can overflow +32-bit on some FPGAs +- Implement an older header fix for a label existing before the pthread_cleanup +macro. +- Limit the number of curls we recruit on communication failures and with +delaynet enabled to 5 by maintaining a per-pool curl count, and using a pthread +conditional that wakes up when one is returned to the ring buffer. +- Generalise add_pool() functions since they're repeated in add_pool_details. +- Bugfix: Return failure, rather than quit, if BFwrite fails +- Disable failing devices such that the user can attempt to re-enable them +- Bugfix: thread_shutdown shouldn't try to free the device, since it's needed +afterward +- API bool's and 1TBS fixes +- Icarus - minimise code delays and name timer variables +- api.c V1.9 add 'restart' + redesign 'quit' so thread exits cleanly +- api.c bug - remove extra ']'s in notify command +- Increase pool watch interval to 30 seconds. +- Reap curls that are unused for over a minute. This allows connections to be +closed, thereby allowing the number of curl handles to always be the minimum +necessary to not delay networking. +- Use the ringbuffer of curls from the same pool for submit as well as getwork +threads. Since the curl handles were already connected to the same pool and are +immediately available, share submission will not be delayed by getworks. +- Implement a scaleable networking framework designed to cope with any sized +network requirements, yet minimise the number of connections being reopened. Do +this by create a ring buffer linked list of curl handles to be used by getwork, +recruiting extra handles when none is immediately available. +- There is no need for the submit and getwork curls to be tied to the pool +struct. +- Do not recruit extra connection threads if there have been connection errors +to the pool in question. +- We should not retry submitting shares indefinitely or we may end up with a +huge backlog during network outages, so discard stale shares if we failed to +submit them and they've become stale in the interim. + + +Version 2.3.6 - April 29, 2012 + +- Shorten stale share messages slightly. +- Protect the freeing of current_hash under mutex_lock to prevent racing on it +when set_curblock is hit concurrently. +- Change default behaviour to submitting stale, removing the --submit-stale +option and adding a --no-submit-stale option. +- Make sure to start the getwork and submit threads when a pool is added on the +fly. This fixes a crash when a pool is added to running cgminer and then +switched to. +- Faster hardware can easily outstrip the speed we can get work and submit +shares when using only one connection per pool. +- Test the queued list to see if any get/submits are already queued and if they +are, start recruiting extra connections by generating new threads. +- This allows us to reuse network connections at low loads but recuit new open +connections as they're needed, so that cgminer can scale to hardware of any +size. + + +Version 2.3.5 - April 28, 2012 + +- Restarting cgminer leads to a socket that can't be bound for 60 seconds, so +increase the interval that API binding waits to 30 seconds to minimise the +number of times it will retry, spamming the logs. +- Give a longpoll message for any longpoll that detects a block change, primary +or backup, and also display which pool it was. +- Decrease utility display to one decimal place. +- Small cosmetic output alignment. +- Add pool number to stale share message. +- Add space to log output now that there is more screen real estate available. +- Indentation clean up. +- Merge branch 'master' of github.com:ckolivas/cgminer +- Remove thread id display from rejected shares as well. +- Merge pull request #185 from Diapolo/diakgcn +- add goffset support for diakgcn with -v 1 and update kernel version +- Set have_longpoll to true when there is at least one pool with longpoll. +- Don't display the thread ID since it adds no useful information over the +device number. +- Don't display the first 8 bytes of a share since they will always be zero at +>= 1 difficulty. +- work->longpoll is reset across test_work_current so we need to recheck what +pool it belongs to. +- Use longpolls from backup pools with failover-only enabled just to check for +block changes, but don't use them as work. +- Start longpoll only after we have tried to extract the longpoll URL. +- Check for submitold flag on resubmit of shares, and give different message for +stale shares on retry. +- Check for submitold before submitstale. +- Don't force fresh curl connections on anything but longpoll threads. +- Create one longpoll thread per pool, using backup pools for those pools that +don't have longpoll. +- Use the work created from the longpoll return only if we don't have +failover-enabled, and only flag the work as a longpoll if it is the current +pool. +- This will work around the problem of trying to restart the single longpoll +thread on pool changes that was leading to race conditions. +- It will also have less work restarts from the multiple longpolls received from +different pools. +- Remove the ability to disable longpoll. It is not a useful feature and will +conflict with planned changes to longpoll code. +- Remove the invalid entries from the example configuration file. +- Add support for latest ATI SDK on windows. +- Export missing function from libztex. +- miner.php change socktimeoutsec = 10 (it only waits once) +- Bugfix: Make initial_args a const char** to satisfy exec argument type warning +(on Windows only) +- miner.php add a timeout so you don't sit and wait ... forever +- Create discrete persistent submit and get work threads per pool, thus allowing +all submitworks belonging to the same pool to reuse the same curl handle, and +all getworks to reuse their own handle. +- Use separate handles for submission to not make getwork potentially delay +share submission which is time critical. +- This will allow much more reusing of persistent connections instead of opening +new ones which can flood routers. +- This mandated a rework of the extra longpoll support (for when pools are +switched) and this is managed by restarting longpoll cleanly and waiting for a +thread join. +- miner.php only show the current date header once +- miner.php also add current time like single rig page +- miner.php display rig 'when' table at top of the multi-rig summary page +- README - add some Ztex details +- api.c include zTex in the FPGA support list +- api.c ensure 'devs' shows PGA's when only PGA code is compiled +- cgminer.c sharelog code consistency and compile warning fix +- README correct API version number +- README spelling error +- api.c combine all pairs of sprintfs() +- api.c uncomment and use BLANK (and COMMA) +- Code style cleanup +- Annotating frequency changes with the changed from value +- README clarification of 'notify' command +- README update for API RPC 'devdetails' +- api.c 'devdetails' list static details of devices +- Using less heap space as my TP-Link seems to not handle this much + + +Version 2.3.4 - April 25, 2012 + +- Extensively document the cause of GPU device issues and the use of --gpu-map. +- Support for share logging +- Detect poorly performing combination of SDK and phatk kernel and add verbose +warning at startup. +- Icarus update to new add_cgpu() +- Icarus driver working with Linux and Windows +- api.c fix unused variable compile warning +- Display all OpenCL devices when -n is called as well to allow debugging of +differential mapping of OpenCL to ADL. +- Add a --gpu-map option which will allow arbitrarily mapping ADL devices to +OpenCL devices for instances where association by enumeration alone fails. +- Increase upper limit on number of extra items to queue as some FPGA code can't +yet reliably keep many devices busy. +- Display configuration file information when -c option is passed and only when +file exists on loading default config file. +- Display configuration file loaded, if any, and debug output if configuration +file parsing failed. +- Add missing ztex header to Makefile for distribution. +- Document long-form COM port device names on Windows, required to specify +serial ports above 9 +- Include ztex bitstreams firmware in distribution and install if configured in. +- Style police on driver-ztex.c +- work_restart should only be changed by cgminer.c now +- Shut down the api cleanly when the api thread is cancelled. This should allow +the api socket to be closed successfully to next be reopened with app_restart. +- Make a union for cgpu device handles, and rename "device" to "device_ztex" +since it's Ztex-specific +- Initialise name variable. +- Remove unnecessary check for variable that always has memory allocated. +- Bugfix: Missing "break" no-op in default case +- Make the status window and log window as large as can fit on startup, +rechecking to see if it can be enlarged after the fact. This allows any number +of devices to be displayed provided the window is made long enough without +corrupting the output. +- Style police on libztex.c. +- API add removepool like the screen interface +- api.c escape required characters in return strings + pools returns the +username +- Set lp_path to NULL after free for consistency. +- Removing dmalloc import left behind by mistake +- Fixing leak in resp_hdr_cb +- miner.php warning highlight GPU stats if they are zero (e.g. ADL not enabled) +- miner.php highlight any device that isn't 'Enabled' +- miner.php highlight any Status that isn't 'Alive' +- miner.php optionally support multiple rigs +- Initial Ztex support 1.15x board. + + +Version 2.3.3 - April 15, 2012 + +- Don't even display that cpumining is disabled on ./configure to discourage +people from enabling it. +- Do a complete cgminer restart if the ATI Display Library fails, as it does on +windows after running for some time, when fanspeed reporting fails. +- Cache the initial arguments passed to cgminer and implement an attempted +restart option from the settings menu. +- Disable per-device status lines when there are more than 8 devices since +screen output will be corrupted, enumerating them to the log output instead at +startup. +- Reuse Vals[] array more than W[] till they're re-initialised on the second +sha256 cycle in poclbm kernel. +- Minor variable alignment in poclbm kernel. +- Make sure to disable devices with any status not being DEV_ENABLED to ensure +that thermal cutoff code works as it was setting the status to DEV_RECOVER. +- Re-initialising ADL simply made the driver fail since it is corruption over +time within the windows driver that's responsible. Revert "Attempt to +re-initialise ADL should a device that previously reported fanspeed stops +reporting it." +- Microoptimise poclbm kernel by ordering Val variables according to usage +frequency. + + +Version 2.3.2 - March 31, 2012 + +- Damping small changes in hashrate so dramatically has the tendency to always +make the hashrate underread so go back to gentle damping instead. +- Revert the crossover of variables from Vals to W in poclbm kernel now that +Vals are the first declared variables so they're used more frequently. +- Vals variables appearing first in the array in poclbm is faster. +- Change the preferred vector width to 1 for Tahiti only, not all poclbm +kernels. +- Use a time constant 0.63 for when large changes in hashrate are detected to +damp change in case the large change is an aliasing artefact instead of a real +chang +- Only increment stale counter if the detected stales are discarded. +- Attempt to re-initialise ADL should a device that previously reported fanspeed +stops reporting it. +- Move the ADL setup and clearing to separate functions and provide a reinit_adl +function to be used when adl fails while running. +- Use slightly more damping on the decay time function in the never-ending quest +to smooth off the hashmeter. +- Set the starting fanspeed to a safe and fairly neutral 50% when autofan is +enabled. +- Provide locking around updates of cgpu hashrates as well to prevent multiple +threads accessing data fields on the same device. +- Display the beginning of the new block in verbose mode in the logs. +- Reinstate old diablo kernel variable ordering from 120222, adding only goffset +and vector size hint. The massive variable ordering change only helped one SDK +on +- Change the version number on the correct kernels. +- api.c devicecode/osinfo incorrectly swapped for json +- Add extensive instructions on how to make a native windows build. +- Update version numbers of poclbm and diablo kernels as their APIs have also +changed. +- Use global offset parameter to diablo and poclbm kernel ONLY for 1 vector +kernels. +- Use poclbm preferentially on Tahiti now regardless of SDK. +- Remove unused constant passed to poclbm. +- Clean up use of macros in poclbm and use bitselect everywhere possible. +- Add vector type hint to diablo kernel. +- Add worksize and vector attribute hints to the poclbm kernel. +- Spaces for non-aligned variables in poclbm. +- More tidying of poclbm. +- Swap Vals and W variables where they can overlap in poclbm. +- More tidying of poclbm. +- Tidy up first half of poclbm. +- Clean up use of any() by diablo and poclbm kernels. +- Minor variable symmetry changes in poclbm. +- Put additions on separate lines for consistency in poclbm. +- Consolidate last use of W11 into Vals4 in poclbm. +- Change email due to SPAM +- api.c miner.php add a '*' to the front of all notify counters - simplifies +future support of new counters +- miner.php add display 'notify' command +- Small change to help arch's without processor affinity +- Fix bitforce compile error +- api.c notify should report disabled devices also - of course +- API returns the simple device history with the 'notify' command +- code changes for supporting a simple device history +- api.c Report an OS string in config to help with device issues +- api.c fix Log Interval - integer in JSON +- api.c config 'Device Code' to show list of compiled devices + README +- api.c increase buffer size close to current code allowable limit +- removed 8-component vector support from kernel, as this is not supported in +CGMINER anyway +- forgot to update kernel modification date, fixed ;) +- reordered an addition in the kernel, which results in less instructions used +in the GPU ISA code for GCN +- miner.php: option for readonly or check privileged access +- Ignore reduntant-with-build options --disable-gpu, --no-adl, and --no-restart +- miner.php: ereg_replace is DEPRECATED so use preg_replace instead +- Make curses TUI support optional at compile-time. +- Bugfix: AC_ARG_WITH provides withval instead of enableval +- miner.php split devs output for different devices +- api.c: correct error messages +- icarus.c modify (regular) timeout warning to only be debug +- icarus.c set the windows TODO timeout +- Allow specifying a specific driver for --scan-serial +- optimized nonce-check and output code for -v 2 and -v 4 +- Bugfix: Check for libudev header (not just library) in configure, and document +optional dependency +- Add API support for Icarus and Bitforce +- Next API version is 1.4 (1.3 is current) +- README/api.c add "When" the request was processed to STATUS +- Bugfix: ZLX to read BitFORCE temp, not ZKX -.- +- Use libudev to autodetect BitFORCE GPUs, if available +- Use the return value of fan_autotune to set fan_optimal instead of passing it +as a pointer. +- Pass the lasttemp from the device we're using to adjust fanspeed in twin +devices. +- fix the name to 3 chars, fix the multi-icarus support +- Bugfix: "-S auto" is the default if no -S is specified, and there is no such +delay in using it +- README add information missing from --scan-serial +- Update README RPC API Version comment +- Bugfix: Allow enabling CPU even without OpenCL support +- Change failed-to-mine number of requested shares messge to avoid segfault on +recursive calling of quit(). +- Get rid of extra char which is just truncated in poclbm kernel. +- only small code formating changes +- removed vec_step() as this could lead to errors on older SDKs +- unified code for generating nonce in kernel and moved addition of base to the +end -> faster + +Version 2.3.1 - February 24, 2012 + +- Revert input and output code on diakgcn and phatk kernels to old style which +worked better for older hardware and SDKs. +- Add a vector*worksize parameter passed to those kernels to avoid one op. +- Increase the speed of hashrate adaptation. +- Only send out extra longpoll requests if we want longpolls. +- API implement addpool command +- API return the untouched Total MH also (API now version 1.3) +- Add enable/disablepool to miner.php example and reduce font size 1pt + + +Version 2.3.0 - February 23, 2012 + +- Consider extra longpoll work items as staged_extra so as to make sure we queue +more work if queueing regular work items as longpolls. +- Use diablo kernel on all future SDKs for Tahiti and set preferred vector width +to 1 on poclbm kernel only. +- Explicitly type the constants in diakgcn kernel as uint, to be in line with +poclbm kernel. +- Reset all hash counters at the same time as resetting start times to get +accurate hashrates on exiting which is mandatory for benchmarking. +- Report thread out before it starts to avoid being flagged as sick when waiting +for the first work item. +- Don't disable and re-enable devices as they may recover and in the meantime +have their status set to OFF. +- API new commands enablepool and disablepool (version already incremented) +- Tolerate new-format temperature readings for bitforce +- Modify cgminer.c pool control to allow API to call it +- Bugfix: Fix BitFORCE driver memory leak in debug logging +- Extra byte was being unused in poclbm leading to failure on some platforms. +- Explicitly type the constants in poclbm kernel as uint. +- Don't save 'include' when saving the configuration +- Allow configuration file to include another recursively +- Use the SDK and hardware information to choose good performing default +kernels. +- Move phatk kernel to offset vector based nonce bases as well. +- Add a --benchmark feature which works on a fake item indefinitely to compare +device performance without any server or networking influence. +- Allow writing of multiple worksizes to the configuration file. +- Allow writing of multiple vector sizes to the configuration file. +- Allow writing of multiple kernels to the configuration file. +- Allow multiple different kernels to be chosen per device. +- Allow the worksize to be set per-device. +- Allow different vectors to be set per device. +- If we're well below the target temperature, increase gpu engine speed back to +maximum in case we have gotten lost between profiles during an idle period. +- We should be setting the value of fan_optimal, not its address. +- As all kernels will be new versions it's an opportunity to change the .bin +format and make it simpler. Specifying bitalign is redundant and long can be l. +- Use any() in kernel output code. +- Put the nonce for each vector offset in advance, avoiding one extra addition +in the kernel. +- Reset times after all mining threads are started to make estimating hashrates +easier at startup. +- Bugfix: allow no-exec (NX) stack +- Fix minor warning. +- fix the bitforce.c code style follow 1TBS +- fix icarus.c compile warning +- small changes to speedup no vec for AMD 898.1 OCL runtime +- Update licensing to GPL V3. +- Reset the longpoll flag after it's been used once to prevent it restarting +work again. +- Begin import of DiabloMiner kernel. +- Modify API debug messages to say API instead of DBG +- When API shuts down cgminer don't kill itself +- Don't make rolled work from the longpoll be seen as other longpoll work items. +- API add 'privileged' command so can verify access level +- Set the lp_sent variable under lock since there will almost always be a race +on setting this variable, potentially leading to multiple LPs being sent out. +- API restrict access to all non display commands by default +- Update API version to 1.2 for new 'Log Interval' +- API add --log Interval to 'config' reply +- --api-allow special case 0/0 means all + + +Version 2.2.7 - February 20, 2012 + +- Send out extra longpolls when we have switched pools and the longpoll thread +is still bound to the old one. This is particularly useful with p2pool where +longpolls do not correlate with main bitcoin block change and would have led to +high reject rates on failover. +- Store whether a work item is the result of a longpoll or not in struct work +and use it to help determine block changes directly from the work longpoll bool. +- Keep track of when a longpoll has been sent for a pool and if the current pool +is requesting work but has not sent a longpoll request, convert one of the work +items to a longpoll. +- Store the longpoll url in the pool struct and update it from the pool_active +test in case it changes. This is to allow further changes to longpoll management +on switching pools. +- Re-check for a longpoll supporting pool every 30 seconds if none is found +initially. +- Report threads as busy waiting on getwork on startup to avoid them being +flagged sick on startup during slow networking. +- Allow devices that are disabled due to overheating to be flagged as recovering +instead of disabling them and re-enable them if they're below ideal temperatures +- Tahiti prefers worksize 64 with poclbm. +- No need to expressly retain the opencl program now that the zero binary issue +is fixed. This actually fixes cgminer to work with the latest SDK included with +the ATI catalyst driver 12.2. +- Show error code on any opencl failure status. +- Add detection for version 898.1 SDK as well but only give SDK 2.6 warning once +on startup instead of with each device initialisation. +- Always use a fresh connection for longpoll as prolonged persistent connections +can fail for many reasons. +- Keep track of intended engine clock speed and only adjust up if it's higher +than the last intended speed. This avoids setting the clock speed to one +relative to a lower profile one by mistake. +- Use gpu-memdiff on startup if an engine clockspeed is set and a memdiff value +is set. +- Revert "Adjust engine speed up according to performance level engine setting, +not the current engine speed." - ineffectual. +- Freeze the queues on all threads that are sent the pause message to prevent +them trying to start up again with saved pings in their queues. +- Updates to diakgcn kernel/ +- Consolidate all screen updates to the watchdog thread and touch both windows +before refresh. +- Curses will be disabled in clean_up so don't do it early in kill_work, and +disable_adl so that GPU settings may be restored to normal in case shutting down +curses leads to instability on windows. +- Stop the mining threads before trying to kill them. +- Plain refresh() does not give reliably screen updates so get rid of all uses +of it. +- First release with working diakgcn kernel. + +Version 2.2.6 - February 16, 2012 + +- Provide warning on each startup about sdk 2.6 +- Fix unused warnings on win32. +- bitforce: Simplify BFopen WIN32 ifdef/else +- Fix initialization warning with jansson 1.3 +- bitforce: Cleanup extraneous TODO that isn't needed +- Move tcsetattr (and new tcflush) into *nix BFopen to simplify things a bit +- Add message explaining 2nd thread disabling for dynamic mode and how to tune +it. +- Move logwindow down once number of devices is known. +- Automatically choose phatk kernel for bitalign non-gcn ATI cards, and then +only select poclbm if SDK2.6 is detected. +- Allow the refresh interval to be adjusted in dynamic intensity with a +--gpu-dyninterval parameter. +- Make curses display visible right from the beginning and fix the window sizes +so the initial messages don't get lost once the status window is drawn. +- The amount of work scanned can fluctuate when intensity changes and since we +do this one cycle behind, we increment the work more than enough to prevent +repeati +- bitforce: Set a 30 second timeout for serial port on Windows, since the +default is undefined +- Use PreVal4addT1 instead of PreVal4 in poclbm kernel. +- Import PreVal4 and PreVal0 into poclbm kernel. +- Import more prepared constants into poclbm kernel. +- Keep variables in one array but use Vals[] name for consistency with other +kernel designs. +- Replace constants that are mandatorily added in poclbm kernel with one value. +- Remove addition of final constant before testing for result in poclbm kernel. +- Hand optimise variable addition order. +- Hand optimise first variable declaration order in poclbm kernel. +- Radical reordering machine based first pass to change variables as late as +possible, bringing their usage close together. +- fix strcpy NULL pointer if env HOME unset. +- bitforce: Disable automatic scanning when at least one device is specified +manually +- Unroll all poclbm additions to enable further optimisations. + + +Version 2.2.5 - February 13, 2012 + +- Make output buffer write only as per Diapolo's suggestion. +- Constify nonce in poclbm. +- Use local and group id on poclbm kernel as well. +- Microoptimise phatk kernel on return code. +- Adjust engine speed up according to performance level engine setting, not the +current engine speed. +- Try to load a binary if we've defaulted to the poclbm kernel on SDK2.6 +- Use the poclbm kernel on SDK2.6 with bitalign devices only if there is no +binary available. +- Further generic microoptimisations to poclbm kernel. +- The longstanding generation of a zero sized binary appears to be due to the +OpenCL library putting the binary in a RANDOM SLOT amongst 4 possible binary +locations. Iterate over each of them after building from source till the real +binary is found and use that. +- Fix harmless warnings with -Wsign-compare to allow cgminer to build with -W. +- Fix missing field initialisers warnings. +- Put win32 equivalents of nanosleep and sleep into compat.h fixing sleep() for +adl.c. +- Restore compatibility with Jansson 1.3 and 2.0 (api.c required 2.1) +- Modularized logging, support for priority based logging +- Move CPU chipset specific optimization into device-cpu + + +Version 2.2.4 - February 11, 2012 + +- Fix double definition of A0 B0 to zeroA zeroB. +- Retain cl program after successfully loading a binary image. May decrease +failures to build kernels at startup. +- Variable unused after this so remove setting it. +- BFI INT patching is not necessarily true on binary loading of files and not +true on ATI SDK2.6+. Report bitalign instead. +- Various string fixes for reject reason. +- Generalize --temp-cutoff and implement support for reading temperature from +BitFORCE FPGAs +- Change message from recovered to alive since it is used on startup as well as +when a pool has recovered. +- Start mining as soon as any pool is found active and rely on the watchpool +thread to bring up other pools. +- Delayed responses from testing pools that are down can hold up the watchdog +thread from getting to its device testing code, leading to false detection of +the GPU not checking in, and can substantially delay auto gpu/auto fan +management leading to overheating. Move pool watching to its own thread. +- Bugfix: BitFORCE index needs to be static to count correctly +- Space out retrieval of extra work according to the number of mining threads. +- Make shutdown more robust. Enable the input thread only after the other +threads exist. Don't kill off the workio thread and use it to exit main() only +if there is an unexpected problem. Use kill_work() for all anticipated shutdowns +where possible. Remove unused thread entry. +- Change poclbm version number. +- One array is faster than 2 separate arrays so change to that in poclbm kernel. +- Microoptimisations to poclbm kernel which increase throughput slightly. +- Import diablominer kernel. Currently disabled as not working. +- Import diapolo kernel. Currently disabled as not working. +- Conflicting entries of cl_kernel may have been causing problems, and +automatically chosen kernel type was not being passed on. Rename the enum to +cl_kernels and store the chosen kernel in each clState. +- Set cl_amd_media_ops with the BITALIGN flag and allow non-bitselect devices to +build. +- ALlow much longer filenames for kernels to load properly. +- Allow different kernels to be used by different devices and fix the logic fail +of overcorrecting on last commit with !strstr. +- Fix kernel selection process and build error. +- queue_phatk_kernel now uses CL_SET_VARG() for base-nonce(s), too +- added OpenCL >= 1.1 detection code, in preparation of OpenCL 1.1 global offset +parameter support +- Use K array explicitly to make it clear what is being added. +- Work items have a tendency to expire at exactly the same time and we don't +queue extra items when there are plenty in the queue, regardless of age. Allow +extra work items to be queued if adequate time has passed since we last +requested work even if over the limit. +- Discard work when failover-only is enabled and the work has come from a +different pool. +- Missing include to build on newer mingw32. +- Move from the thread safe localtime_r to regular localtime which is the only +one supported on newer pthread libraries on mingw32 to make it compile with the +newer ming. Thread safety is of no importance where localtime is used in this +code. +- Define in_addr_t in windows if required +- sys/wait.h not required in windows +- Allow API to restrict access by IP address +- Add pool switching to example miner.php +- Display X-Reject-Reason, when provided +- Remove the test for whether the device is on the highest profil level before +raising the GPU speed as it is ineffectual and may prevent raising the GPU +speed. +- Remove unnecessary check for opt_debug one every invocation of applog at +LOG_DEBUG level and place the check in applog(). + + +Version 2.2.3 - February 6, 2012 + +- Revert "Rewrite the convoluted get_work() function to be much simpler and roll +work as much as possible with each new work item." This seems to cause a race on +work in free_work(). Presumably other threads are still accessing the structure. + + +Version 2.2.2 - February 6, 2012 + +- Provide support for the submitold extension on a per-pool basis based on the +value being detected in a longpoll. +- Don't send a ping to a dynamic device if it's not enabled as that will just +enable it for one pass and then disable it again. +- Rewrite the convoluted get_work() function to be much simpler and roll work as +much as possible with each new work item. +- Roll as much work as possible from the work returned from a longpoll. +- Rolling work on each loop through the mining thread serves no purpose. +- Allow to stage more than necessary work items if we're just rolling work. +- Replace divide_work with reuse_work function used twice. +- Give rolled work a new ID to make sure there is no confusion in the hashtable +lookups. +- Remove now-defunct hash_div variables. +- Remove unused get_dondata function. +- Silence ADL warnings. +- Silence unused parameter warnings. +- Stagger the restart of every next thread per device to keep devices busy ahead +of accessory threads per device. +- Deprecate the --donation feature. Needlessly complex, questionable usefulness, +depends on author's server and a central pool of some kind, and was not heavily +adopted. +- It's devices that report back now, not threads, update message. +- Continue auto-management of fan and engine speeds even if a device is disabled +for safety reasons. +- No need to check we're highest performance level when throttling GPU engine +speed. +- Abstract out tests for whether work has come from a block that has been seen +before and whether a string is from a previously seen block. +- Probe but don't set the timeout to 15 seconds as some networks take a long +time to timeout. +- Remove most compiler warnings from api.c +- Add last share's pool info in cgpu_info +- Allow the OpenCL platform ID to be chosen with --gpu-platform. +- Iterate over all platforms displaying their information and number of devices +when --ndevs is called. +- Deprecate main.c +- Some networks can take a long time to resolve so go back to 60 second timeouts +instead of 15. +- Only enable curses on failure if curses is desired. +- Fix warnings in bitforce.c +- Bugfix: Need to open BitForce tty for read-write +- Fix various build issues. +- Modularize code: main.c -> device-cpu + device-gpu +- Fix phatk kernel not working on non-bitalign capable devices (Nvidia, older +ATI). +- Update poclbm kernel for better performance on GCN and new SDKs with bitalign +support when not BFI INT patching. Update phatk kernel to work properly for non +BFI INT patched kernels, providing support for phatk to run on GCN and non-ATI +cards. +- Return last accepted share pool/time for devices +- Display accepted share pool/time for CPUs +- Bug intensity always shows GPU 0 +- Update example web miner.php to use new API commands + + +Version 2.2.1 - January 30, 2012 + +NOTE - The GPU Device reordering in 2.2.0 by default was considered a bad idea +so the original GPU ordering is used by default again unless reordering is +explicitly requested. + +- Fix bitforce failing to build into cgminer. +- Add missing options to write config function. +- Add a --gpu-reorder option to only reorder devices according to PCI Bus ID +when requested. +- Fix for midstate support being broken on pools that supported no-midstate +work by ensuring numbers are 32 bits in sha2.c +- Set virtual GPUs to work when ADL is disabled or all mining will occur on GPU +0. +- Add information about paused threads in the menu status. +- Disable all but the first thread on GPUs in dynamic mode for better +interactivity. +- Set the latest network access time on share submission for --net-delay even if +we're not delaying that submission for further network access. +- Clear adl on exiting after probing values since it may attempt to overclock. +- As share submission is usually staggered, and delays can be costly, submit +shares without delay even when --net-delay is enabled. +- Display GPU number and device name when ADL is successfully enabled on it. +- Display GPU ordering remapping in verbose mode. +- Don't fail in the case the number of ADL and OpenCL devices do not match, and +do not attempt to reorder devices unless they match. Instead give a warning +about +- Display error codes should ADL not return ADL_OK in the more critical function +calls. +- Fix unused warning. +- Fix compile warnings in api.c +- Add extensive ADL based device info in debug mode. +- Make --ndevs display verbose opencl information as well to make debugging +version information easier. +- Display information about the opencl platform with verbose enabled. +- Explicitly check for nvidia in opencl platform strings as well. + + +Version 2.2.0 - January 29, 2012 + +NOTE: GPU Device order will change with this release with ATI GPUs as cgminer +now can enumerate them according to their Bus ID which means the values should +now correlate with their physical position on the motherboard. + +- Default to poclbm kernel on Tahiti (7970) since phatk does not work, even +though performance is sub-standard so that at least it will mine successfully by +defau +- Retain cl program after every possible place we might build the program. +- Update ADL SDK URL. +- Fix potential overflow. +- Map GPU devices to virtual devices in their true physical order based on +BusNumber. +- Change the warning that comes with failure to init cl on a device to be more +generic and accurate. +- Advertise longpoll support in X-Mining-Extensions +- Detect dual GPU cards by iterating through all GPUs, finding ones without +fanspeed and matching twins with fanspeed one bus ID apart. +- Do not attempt to build the program that becomes the kernel twice. This could +have been leading to failures on initialising cl. +- Some opencl compilers have issues with no spaces after -D in the compiler +options. +- Allow intensity up to 14. +- Use calloced stack memory for CompilerOptions to ensure sprintf writes to the +beginning of the char. +- Whitelist 79x0 cards to prefer no vectors as they perform better without. +- Adjust fan speed gently while in the optimal range when temperature is +drifting to minimise overshoot in either direction. +- Detect dual GPU cards via the indirect information of - 1st card has a fan +controller. 2nd card does not have a fan controller, cards share the same device +name +- Instead of using the BFI_INT patching hack on any device reporting +cl_amd_media_ops, create a whitelist of devices that need it. This should enable +GCN architec +- Fixed API compiling issue on OS X +- Add more explanation of JSON format and the 'save' command +- Return an error if using ADL API commands when it's not available +- Read off lpThermalControllerInfo from each ADL device. +- Add ADL_Overdrive5_ThermalDevices_Enum interface. +- Add API commands: config, switchpool, gpu settings, save +- Implement socks4 proxy support. +- Fix send() for JSON strings +- Introduce a --net-delay option which guarantees at least 250ms between any +networking requests to not overload slow routers. +- Generalise locking init code. +- Allow invalid values to be in the configuration file, just skipping over them +provided the rest of the file is valid JSON. This will allow older configurat +- Allow CPU mining explicitly enable only if other mining support is built in. +- BitForce FPGA support +- Configure out building and support of all CPU mining code unless +--enable-cpumining is enabled. +- Allow parsed values to be zero which will allow 0 values in the config file to +work. +- Advertise that we can make our own midstate, so the pool can skip generating +it for us +- Refactor the CPU scanhash_* functions to use a common API. Fixes bugs. +- Don't consider a pool lagging if a request has only just been filed. This +should decrease the false positives for "pool not providing work fast enough". +- Invalidating work after longpoll made hash_pop return no work giving a false +positive for dead pool. Rework hash_pop to retry while finds no staged work u +- Remove TCP_NODELAY from curl options as many small packets may be contributing +to network overload, when --net-delay is enabled. +- Refactor miner_thread to be common code for any kind of device +- Simplify submit_nonce loop and avoid potentially missing FOUND - 1 entry. +Reported by Luke-Jr. +- Micro-optimisation in sha256_sse2 code courtesy of Guido Ascioti +guido.ascioti@gmail.com +- Refactor to abstract device-specific code + + +Version 2.1.2 - January 6, 2012 + +- If api-description is specified, save it when writing the config file +- Adjust utility width to be constant maximum as well. +- Add percent signs to reject ratio outputs +- Should the donation pool fail, don't make the fallover pool behave as though +the primary pool is lagging. +- Use an alternative pool should the donation getwork fail. + + +Version 2.1.1 - January 1, 2012 + +- Include API examples in distribution tarball. +- Don't attempt to pthread_join when cancelling threads as they're already +detached and doing so can lead to a segfault. +- Give more generic message if slow pool at startup is the donation pool. +- Continue to attempt restarting GPU threads if they're flagged dead at 1 min. +intervals. +- Don't attempt to restart sick flagged GPUs while they're still registering +activity. +- Make curl use fresh connections whenever there is any communication issue +in case there are dead persistent connections preventing further comms from +working. +- Display pool in summary if only 1 pool. +- Adjust column width of A/R/HW to be the maximum of any device and align them. + + +Version 2.1.0 - December 27, 2011 + +- Major infrastructure upgrade with RPC interface for controlling via sockets +encoded with/without JSON courtesy of Andrew Smith. Added documentation for +use of the API and sample code to use with it. +- Updated linux-usb-cgminer document. +- Rewrite of longpoll mechanism to choose the current pool wherever possible to +use for the longpoll, or any pool that supports longpoll if the current one +does not. +- Display information about longpoll when the chosen server has changed. +- Fix the bug where longpoll generated work may have been sent back to the +wrong pool, causing rejects. +- Fix a few race conditions on closing cgminer which caused some of the crashes +on exit. +- Only adjust gpu engine speed in autotune mode if the gpu is currently at the +performance level of that being adjusted. +- Various fixes for parsing/writing of configuration files. +- Do not add blank lines for threads of unused CPUs. +- Show which pool is unresponsive on startup. +- Only show GPU management menu item if GPUs are in use. +- Align most device columns in the curses display. + + +Version 2.0.8 - November 11, 2011 + +- Make longpoll do a mandatory flushing of all work even if the block hasn't +changed, thus supporting longpoll initiated work change of any sort and merged +mining. +- Byteswap computed hash in hashtest so it can be correctly checked. This fixes +the very rare possibility that a block solve on solo mining was missed. +- Add x86_64 w64 mingw32 target +- Allow a fixed speed difference between memory and GPU clock speed with +--gpu-memdiff that will change memory speed when GPU speed is changed in +autotune mode. +- Don't load the default config if a config file is specified on the command +line. +- Don't build VIA on apple since -a auto bombs instead of gracefully ignoring +VIA failing. +- Build fix for dlopen/dlclose errors in glibc. + + +Version 2.0.7 - October 17, 2011 + +- Support work without midstate or hash1, which are deprecated in bitcoind 0.5+ +- Go to kernel build should we fail to clCreateProgramWithBinary instead of +failing on that device. This should fix the windows problems with devices not +initialising. +- Support new configuration file format courtesy of Chris Savery which can write +the config file from the menu and will load it on startup. +- Write unix configuration to .cgminer/cgminer.conf by default and prompt to +overwrite if given a filename from the menu that exists. + + +Version 2.0.6 - October 9, 2011 + +- Must initialise the donorpool mutex or it fails on windows. +- Don't make donation work interfere with block change detection allowing +donation to work regardless of the block chain we're mining on. +- Expire shares as stale with a separate timeout from the scantime, defaulting +to 120 seconds. +- Retry pools after a delay of 15 seconds if none can be contacted on startup +unless a key is pressed. +- Don't try to build adl features without having adl. +- Properly check shares against target difficulty - This will no longer show +shares when solo mining at all unless they're considered to be a block solve. +- Add altivec 4 way (cpu mining) support courtesy of Gilles Risch. +- Try to use SSL if the server supports it. +- Display the total solved blocks on exit (LOL if you're lucky). +- Use ADL activity report to tell us if a sick GPU is still busy suggesting it +is hard hung and do not attempt to restart it. + + +Version 2.0.5 - September 27, 2011 + +- Intensity can now be set to dynamic or static values per-device. +- New donation feature --donation sends a proportion of shares to author's +account of choice, but is disabled by default! +- The hash being displayed and block detection has been fixed. +- Devices not being mined on will not attempt to be ADL managed. +- Intensity is now displayed per GPU device. +- Make longpoll attempt to restart as often as opt_retries specifies. +- We weren't rolling work as often as we could. +- Correct some memory management issues. +- Build fixes. +- Don't mess with GPUs if we don't have them. + + +Version 2.0.4 - September 23, 2011 + +- Confused Longpoll messages should be finally fixed with cgminer knowing for +sure who found the new block and possibly avoiding a rare crash. +- Display now shows the actual hash and will say BLOCK! if a block is deemed +solved. +- Extra spaces, which would double space lines on small terminals, have been +removed. +- Fan speed change is now damped if it is already heading in the correct +direction to minimise overshoot. +- Building without opencl libraries is fixed. +- GPUs are autoselected if there is only one when in the GPU management menu. +- GPU menu is refreshed instead of returning to status after a GPU change. + + +Version 2.0.3 - September 17, 2011 + +- Various modes of failure to set fanspeeds and adl values have been addressed +and auto-fan should work now on most hardware, and possibly other values +which previously would not have worked. +- Fixed a crash that can occur on switching pools due to longpoll thread races. +- Use ATISTREAMSDKROOT if available at build time. +- Fanspeed management is returned to the driver default on exit instead of +whatever it was when cgminer was started. +- Logging of events deemed WARNING or ERR now will display even during +periods where menu input is being awaited on. + + +Version 2.0.2 - September 11, 2011 + +- Exit cleanly if we abort before various threads are set up or if they no +longer exist. +- Fix a rare crash in HASH_DEL due to using different mutexes to protect the +data. +- Flag devices that have never started and don't allow enabling of devices +without restarting them. +- Only force the adapter speed to high if we've flagged this device as being +managed. +- Flag any devices with autofan or autogpu as being managed. +- Use a re-entrant value to store what fanspeed we're trying to set in case the +card doesn't support small changes. Force it to a multiple of 10% if it +fails on trying to speed up the fan. +- Do not bother resetting values to old ones if changes to GPU parameters report +failure, instead returning a failure code only if the return value from get() +differs. +- Remove redundant check. +- Only display supported values from fanspeed on change settings. +- Missing bracket from output. +- Display fan percentage on devices that only support reporting percent and not +RPM. +- Properly substitute DLOPEN flags to build with ADL support when -ldl is needed +and not when opencl is not found. + + +Version 2.0.1 - September 9, 2011 + +- Fix building on 32bit glibc with dlopen with -lpthread and -ldl +- ByteReverse is not used and the bswap opcode breaks big endian builds. Remove +it. +- Ignore whether the display is active or not since only display enabled devices +work this way, and we skip over repeat entries anwyay. +- Only reset values on exiting if we've ever modified them. +- Flag adl as active if any card is successfully activated. +- Add a thermal cutoff option as well and set it to 95 degrees by default. +- Change the fan speed by only 5% if it's over the target temperature but less +than the hysteresis value to minimise overshoot down in temperature. +- Add a --no-adl option to disable ADL monitoring and GPU settings. +- Only show longpoll received delayed message at verbose level. +- Allow temperatures greater than 100 degrees. +- We should be passing a float for the remainder of the vddc values. +- Implement accepting a range of engine speeds as well to allow a lower limit to +be specified on the command line. +- Allow per-device fan ranges to be set and use them in auto-fan mode. +- Display which GPU has overheated in warning message. +- Allow temperature targets to be set on a per-card basis on the command line. +- Display fan range in autofan status. +- Setting the hysteresis is unlikely to be useful on the fly and doesn't belong +in the per-gpu submenu. +- With many cards, the GPU summaries can be quite long so use a terse output +line when showing them all. +- Use a terser device status line to show fan RPM as well when available. +- Define max gpudevices in one macro. +- Allow adapterid 0 cards to enumerate as a device as they will be non-AMD +cards, and enable ADL on any AMD card. +- Do away with the increasingly confusing and irrelevant total queued and +efficiency measures per device. +- Only display values in the log if they're supported and standardise device log +line printing. + + +Version 2.0.0 - September 6, 2011 + +Major feature upgrade - GPU monitoring, (over)clocking and fan control for ATI +GPUs. + +New command line switches: +--auto-fan- Automatically adjust all GPU fan speeds to maintain a target +temperature +--auto-gpu- Automatically adjust all GPU engine clock speeds to maintain +a target temperature +--gpu-engine Set the GPU engine (over)clock in Mhz - one value for all or +separate by commas for per card. +--gpu-fan Set the GPU fan percentage - one value for all or separate +by commas for per card. +--gpu-memclock Set the GPU memory (over)clock in Mhz - one value for all +or separate by commas for per card. +--gpu-powertune Set the GPU powertune percentage - one value for all or +separate by commas for per card. +--gpu-vddc Set the GPU voltage in Volts - one value for all or separate +by commas for per card. +--temp-hysteresis Set how much the temperature can fluctuate outside +limits when automanaging speeds (default: 3) +--temp-overheat Set the overheat temperature when automatically managing +fan and GPU speeds (default: 85) +--temp-target Set the target temperature when automatically managing fan +and GPU speeds (default: 75) + +- Implement ATI ADL support for GPU parameter monitoring now and setting later +(temp, fan, clocks etc.). +- Check for the presence of the ADL header files in ADL_SDK. +- Import adl_functions.h from amd overdrive ctrl. +- Implement a setup function that tries to detect GPUs that support the ADL and +link in the parameters into the gpus struct. +- Put a summary of monitoring information from the GPU menu. +- Implement changing memory speed and voltage on the fly. +- Implement fan speed setting. +- Minor corrections to set fan speed by percentage. +- Make sure to read off the value in RPM only. +- Implement auto fanspeed adjustment to maintain a target temperature and +fanspeed below 85%, with an overheat check that will speed the fan up to 100%. +- Add an --auto-fan command line option to allow all GPUs to have autofan +enabled from startup. +- Add a gpu autotune option which adjusts GPU speed to maintain a target +temperature within the bounds of the default GPU speed and any overclocking set. +- Avoid a dereference if the longpoll thread doesn't exist. +- Clean up by setting performance profiles and fan settings to startup levels on +exit. +- Add a small amount of hysteresis before lowering clock speed. +- Allow target, overheat and hysteresis temperatures to be set from command +line. +- Combine all stats collating into one function to avoid repeating function +calls on each variable. +- Add gpu statistics to debugging output via the watchdog thread. +- Implement menus to change temperature limits. +- Implement setting the GPU engine clock speed of all devices or each device as +a comma separated value. +- Implement setting the GPU memory clock speed of all devices or each device as +a comma separated value. +- Implement setting the GPU voltage of all devices or each device as a comma +separated value. +- Implement setting the GPU fan speed of all devices or each device as a comma +separated value. +- Add support for monitoring powertune setting. +- Implement changing of powertune value from the GPU change settings menu. +- Get the value of powertune in get_stats. +- Implement setting the GPU powertune value of all devices or each device as a +comma separated value. +- Remove the safety checks in speed setting since confirmation is done first in +the menu, then show the new current values after a short pause. +- Force the speed to high on startup and restore it to whatever the setting was +on exit. +- Add temperature to standard output where possible and use more compact output. +- Move and print at the same time in curses to avoid random trampling display +errors. +- Update the status window only from the watchdog thread, do not rewrite the top +status messages and only refresh once all the status window is complete, +clearing the window each time to avoid corruption. +- Set a safe starting fan speed if we're automanaging the speeds. +- Provide locking around all adl calls to prevent races. +- Lower profile settings cannot be higher than higher profile ones so link any +drops in settings. +- Add new needed text files to distribution. +- Queue requests ignoring the number of staged clones since they get discarded +very easily leading to false positives for pool not providing work fast enough. +- Include libgen.h in opt.c to fix win32 compilation warnings. +- Fix compilation warning on win32. +- Add the directory name from the arguments cgminer was called from as well to +allow it running from a relative pathname. +- Add a --disable-adl option to configure and only enable it if opencl support +exists. +- Retry before returning a failure to get upstream work as a failure to avoid +false positives for pool dead. +- Retry also if the decoding of work fails. +- Use the presence of X-Roll-Ntime in the header as a bool for exists unless N +is found in the response. + + +Version 1.6.2 - September 2, 2011 + +- Add --failover-only option to not leak work to backup pools when the primary +pool is lagging. +- Change recommendation to intensity 9 for dedicated miners. +- Fix the bouncing short term value by allowing it to change dynamically when +the latest value is very different from the rolling value, but damp the change +when it gets close. +- Use the curses_lock to protect the curses_active variable and test it under +lock. +- Go back to requesting work 2/3 of the way through the current scantime with +CPU mining as reports of mining threads running out of work have occurred with +only 5 seconds to retrieve work. +- Add start and stop time scheduling for regular time of day running or once off +start/stop options. +- Print summary on quit modes. +- Put some sanity checks on the times that can be input. +- Give a verbose message when no active pools are found and pause before +exiting. +- Add verbose message when a GPU fails to initialise, and disable the correct +GPU. +- Cryptopp asm32 was not correctly updated to the incremental nonce code so the +hash counter was bogus. +- Get rid of poorly executed curl check. +- If curl does not have sockopts, do not try to compile the +json_rpc_call_sockopt_cb function, making it possible to build against older +curl libraries. +- Most people expect /usr/local when an unspecified prefix is used so change to +that. +- Rename localgen occasions to getwork fail occasions since localgen is +unrelated now. + + +Version 1.6.1 - August 29, 2011 + +- Copy cgminer path, not cat it. +- Switching between redrawing windows does not fix the crash with old +libncurses, so redraw both windows, but only when the window size hasn't +changed. +- Reinstate minimum 1 extra in queue to make it extremely unlikely to ever have +0 staged work items and any idle time. +- Return -1 if no input is detected from the menu to prevent it being +interpreted as a 0. +- Make pthread, libcurl and libcurses library checks mandatory or fail. +- Add a --disable-opencl configure option to make it possible to override +detection of opencl and build without GPU mining support. +- Confusion over the variable name for number of devices was passing a bogus +value which likely was causing the zero sized binary issue. +- cgminer no longer supports default url user and pass so remove them. +- Don't show value of intensity since it's dynamic by default. +- Add options to explicitly enable CPU mining or disable GPU mining. +- Convert the opt queue into a minimum number of work items to have queued +instead of an extra number to decrease risk of getting idle devices without +increasing risk of higher rejects. +- Statify tv_sort. +- Check for SSE2 before trying to build 32 bit SSE2 assembly version. Prevents +build failure when yasm is installed but -msse2 is not specified. +- Add some defines to configure.ac to enable exporting of values and packaging, +and clean up output. +- Give convenient summary at end of ./configure. +- Display version information and add --version command line option, and make +sure we flush stdout. +- Enable curses after the mining threads are set up so that failure messages +won't be lost in the curses interface. +- Disable curses after inputting a pool if we requested no curses interface. +- Add an option to break out after successfully mining a number of accepted +shares. +- Exit with a failed return code if we did not reach opt_shares. +- The cpu mining work data can get modified before we copy it if we submit it +async, and the sync submission is not truly sync anyway, so just submit it sync. + + +Version 1.6.0 - August 26, 2011 + +- Make restarting of GPUs optional for systems that hang on any attempt to +restart them. Fix DEAD status by comparing it to last live time rather than +last attempted restart time since that happens every minute. +- Move staged threads to hashes so we can sort them by time. +- Create a hash list of all the blocks created and search them to detect when a +new block has definitely appeared, using that information to detect stale work +and discard it. +- Update configure.ac for newer autoconf tools. +- Use the new hashes directly for counts instead of the fragile counters +currently in use. +- Update to latest sse2 code from cpuminer-ng. +- Allow LP to reset block detect and block detect lp flags to know who really +came first. +- Get start times just before mining begins to not have very slow rise in +average. +- Add message about needing one server. +- We can queue all the necessary work without hitting frequent stales now with +the time and string stale protection active all the time. This prevents a +pool being falsely labelled as not providing work fast enough. +- Include uthash.h in distro. +- Implement SSE2 32 bit assembly algorithm as well. +- Fail gracefully if unable to open the opencl files. +- Make cgminer look in the install directory for the .cl files making make +install work correctly. +- Allow a custom kernel path to be entered on the command line. +- Bump threshhold for lag up to maximum queued but no staged work. +- Remove fragile source patching for bitalign, vectors et. al and simply pass it +with the compiler options. +- Actually check the value returned for the x-roll-ntime extension to make sure +it isn't saying N. +- Prevent segfault on exit for when accessory threads don't exist. +- Disable curl debugging with opt protocol since it spews to stderr. + + +Version 1.5.8 - August 23, 2011 + +- Minimise how much more work can be given in cpu mining threads each interval. +- Make the fail-pause progressively longer each time it fails until the network +recovers. +- Only display the lagging message if we've requested the work earlier. +- Clean up the pool switching to not be dependent on whether the work can roll +or not by setting a lagging flag and then the idle flag. +- Only use one thread to determine if a GPU is sick or well, and make sure to +reset the sick restart attempt time. +- The worksize was unintentionally changed back to 4k by mistake, this caused a +slowdown. + + +Version 1.5.7 - August 22, 2011 + +- Fix a crash with --algo auto +- Test at appropriate target difficulty now. +- Add per-device statics log output with --per-device-stats +- Fix breakage that occurs when 1 or 4 vectors are chosen on new phatk. +- Make rolltime report debug level only now since we check it every work +item. +- Add the ability to enable/disable per-device stats on the fly and match +logging on/off. +- Explicitly tell the compiler to retain the program to minimise the chance of +the zero sized binary errors. +- Add one more instruction to avoid one branch point in the common path in the +cl return code. Although this adds more ALUs overall and more branch points, the +common path code has the same number of ALUs and one less jmp, jmps being more +expensive. +- Explicitly link in ws2_32 on the windows build and update README file on how +to compile successfully on windows. +- Release cl resources should the gpu mining thread abort. +- Attempt to restart a GPU once every minute while it's sick. +- Don't kill off the reinit thread if it fails to init a GPU but returns safely. +- Only declare a GPU dead if there's been no sign of activity from the reinit +thread for 10 mins. +- Never automatically disable any pools but just specify them as idle if they're +unresponsive at startup. +- Use any longpoll available, and don't disable it if switching to a server that +doesn't have it. This allows you to mine solo, yet use the longpoll from a pool +even if the pool is the backup server. +- Display which longpoll failed and don't free the ram for lp_url since it +belongs to the pool hdr path. +- Make the tcp setsockopts unique to linux in the hope it allows freebsd et. al +to compile. + + +Version 1.5.6 - August 17, 2011 + +- New phatk and poclbm kernels. Updated phatk to be in sync with latest 2.2 +courtesy of phateus. Custom modified to work best with cgminer. +- Updated output buffer code to use a smaller buffer with the kernels. +- Clean up the longpoll management to ensure the right paths go to the right +pool and display whether we're connected to LP or not in the status line. + + +Version 1.5.5 - August 16, 2011 + +- Rework entirely the GPU restart code. Strike a balance between code that +re-initialises the GPU entirely so that soft hangs in the code are properly +managed, but if a GPU is completely hung, the thread restart code fails +gracefully, so that it does not take out any other code or devices. This will +allow cgminer to keep restarting GPUs that can be restarted, but continue +mining even if one or more GPUs hangs which would normally require a reboot. +- Add --submit-stale option which submits all shares, regardless of whether they +would normally be considered stale. +- Keep options in alphabetical order. +- Probe for slightly longer for when network conditions are lagging. +- Only display the CPU algo when we're CPU mining. +- As we have keepalives now, blaming network flakiness on timeouts appears to +have been wrong. Set a timeout for longpoll to 1 hour, and most other +network connectivity to 1 minute. +- Simplify output code and remove HW errors from CPU stats. +- Simplify code and tidy output. +- Only show cpu algo in summary if cpu mining. +- Log summary at the end as per any other output. +- Flush output. +- Add a linux-usb-cgminer guide courtesy of Kano. + + +Version 1.5.4 - August 14, 2011 + +- Add new option: --monitor Option lets user specify a command that +will get forked by cgminer on startup. cgminer's stderr output subsequently gets +piped directly to this command. +- Allocate work from one function to be able to initialise variables added +later. +- Add missing fflush(stdout) for --ndevs and conclusion summary. +- Preinitialise the devices only once on startup. +- Move the non cl_ variables into the cgpu info struct to allow creating a new +cl state on reinit, preserving known GPU variables. +- Create a new context from scratch in initCQ in case something was corrupted to +maximise our chance of succesfully creating a new worker thread. Hopefully this +makes thread restart on GPU failure more reliable, without hanging everything +in the case of a completely wedged GPU. +- Display last initialised time in gpu management info, to know if a GPU has +been re-initialised. +- When pinging a sick cpu, flush finish and then ping it in a separate thread in +the hope it recovers without needing a restart, but without blocking code +elsewhere. +- Only consider a pool lagging if we actually need the work and we have none +staged despite queue requests stacking up. This decreases significantly the +amount of work that leaks to the backup pools. +- The can_roll function fails inappropriately in stale_work. +- Only put the message that a pool is down if not pinging it every minute. This +prevents cgminer from saying pool down at 1 minute intervals unless in debug +mode. +- Free all work in one place allowing us to perform actions on it in the future. +- Remove the extra shift in the output code which was of dubious benefit. In +fact in cgminer's implementation, removing this caused a miniscule speedup. +- Test each work item to see if it can be rolled instead of per-pool and roll +whenever possible, adhering to the 60 second timeout. This makes the period +after a longpoll have smaller dips in throughput, as well as requiring less +getworks overall thus increasing efficiency. +- Stick to rolling only work from the current pool unless we're in load balance +mode or lagging to avoid aggressive rolling imitating load balancing. +- If a work item has had any mining done on it, don't consider it discarded +work. + + +Version 1.5.3 - July 30, 2011 + +- Significant work went into attempting to make the thread restart code robust +to identify sick threads, tag them SICK after 1 minute, then DEAD after 5 +minutes of inactivity and try to restart them. Instead of re-initialising the +GPU completely, only a new cl context is created to avoid hanging the rest of +the GPUs should the dead GPU be hung irrevocably. +- Use correct application name in syslog. +- Get rid of extra line feeds. +- Use pkg-config to check for libcurl version +- Implement per-thread getwork count with proper accounting to not over-account +queued items when local work replaces it. +- Create a command queue from the program created from source which allows us +to flush the command queue in the hope it will not generate a zero sized binary +any more. +- Be more willing to get work from the backup pools if the work is simply being +queued faster than it is being retrieved. + + +Version 1.5.2 - July 28, 2011 + +- Restarting a hung GPU can hang the rest of the GPUs so just declare it dead +and provide the information in the status. +- The work length in the miner thread gets smaller but doesn't get bigger if +it's under 1 second. This could end up leading to CPU under-utilisation and +lower and lower hash rates. Fix it by increasing work length if it drops +under 1 second. +- Make the "quiet" mode still update the status and display errors, and add a +new --real-quiet option which disables all output and can be set once while +running. +- Update utility and efficiency figures when displaying them. +- Some Intel HD graphics support the opencl commands but return errors since +they don't support opencl. Don't fail with them, just provide a warning and +disable GPU mining. +- Add http:// if it's not explicitly set for URL entries. +- Log to the output file at any time with warnings and errors, instead of just +when verbose mode is on. +- Display the correct current hash as per blockexplorer, truncated to 16 +characters, with just the time. + + +Version 1.5.1 - July 27, 2011 + +- Two redraws in a row cause a crash in old libncurses so just do one redraw +using the main window. +- Don't adjust hash_div only up for GPUs. Disable hash_div adjustment for GPUs. +- Only free the thread structures if the thread still exists. +- Update both windows separately, but not at the same time to prevent the double +refresh crash that old libncurses has. Do the window resize check only when +about to redraw the log window to minimise ncurses cpu usage. +- Abstract out the decay time function and use it to make hash_div a rolling +average so it doesn't change too abruptly and divide work in chunks large enough +to guarantee they won't overlap. +- Sanity check to prove locking. +- Don't take more than one lock at a time. +- Make threads report out when they're queueing a request and report if they've +failed. +- Make cpu mining work submission asynchronous as well. +- Properly detect stale work based on time from staging and discard instead of +handing on, but be more lax about how long work can be divided for up to the +scantime. +- Do away with queueing work separately at the start and let each thread grab +its own work as soon as it's ready. +- Don't put an extra work item in the queue as each new device thread will do so +itself. +- Make sure to decrease queued count if we discard the work. +- Attribute split work as local work generation. +- If work has been cloned it is already at the head of the list and when being +reinserted into the queue it should be placed back at the head of the list. +- Dividing work is like the work is never removed at all so treat it as such. +However the queued bool needs to be reset to ensure we *can* request more work +even if we didn't initially. +- Make the display options clearer. +- Add debugging output to tq_push calls. +- Add debugging output to all tq_pop calls. + + +Version 1.5.0 - July 26, 2011 + +- Increase efficiency of slow mining threads such as CPU miners dramatically. Do +this by detecting which threads cannot complete searching a work item within the +scantime and then divide up a work item into multiple smaller work items. +Detect the age of the work items and if they've been cloned before to prevent +doing the same work over. If the work is too old to be divided, then see if it +can be time rolled and do that to generate work. This dramatically decreases the +number of queued work items from a pool leading to higher overall efficiency +(but the same hashrate and share submission rate). +- Don't request work too early for CPUs as CPUs will scan for the full +opt_scantime anyway. +- Simplify gpu management enable/disable/restart code. +- Implement much more accurate rolling statistics per thread and per gpu and +improve accuracy of rolling displayed values. +- Make the rolling log-second average more accurate. +- Add a menu to manage GPUs on the fly allowing you to enable/disable GPUs or +try restarting them. +- Keep track of which GPUs are alive versus enabled. +- Start threads for devices that are even disabled, but don't allow them to +start working. +- The last pool is when we are low in total_pools, not active_pools. +- Make the thread restart do a pthread_join after disabling the device, only +re-enabling it if we succeed in restarting the thread. Do this from a separate +thread so as to not block any other code.This will allow cgminer to continue +even if one GPU hangs. +- Try to do every curses manipulation under the curses lock. +- Only use the sockoptfunction if the version of curl is recent enough. + + +Version 1.4.1 - July 24, 2011 + +- Do away with GET for dealing with longpoll forever. POST is the one that works +everywhere, not the other way around. +- Detect when the primary pool is lagging and start queueing requests on backup +pools if possible before needing to roll work. +- Load balancing puts more into the current pool if there are disabled pools. +Fix. +- Disable a GPU device should the thread fail to init. +- Out of order command queue may fail on osx. Try without if it fails. +- Fix possible dereference on blank inputs during input_pool. +- Defines missing would segfault on --help when no sse mining is built in. +- Revert "Free up resources/stale compilers." - didn't help. +- Only try to print the status of active devices or it would crash. +- Some hardware might benefit from the less OPS so there's no harm in leaving +kernel changes that do that apart from readability of the code. + +Version 1.4.0 - July 23, 2011 + +- Feature upgrade: Add keyboard input during runtime to allow modification of +and viewing of numerous settings such as adding/removing pools, changing +multipool management strategy, switching pools, changing intensiy, verbosity, +etc. with a simple keypress menu system. +- Free up resources/stale compilers. +- Kernels are safely flushed in a way that allows out of order execution to +work. +- Sometimes the cl compiler generates zero sized binaries and only a reboot +seems to fix it. +- Don't try to stop/cancel threads that don't exist. +- Only set option to show devices and exit if built with opencl support. +- Enable curses earlier and exit with message in main for messages to not be +lost in curses windows. +- Make it possible to enter server credentials with curses input if none are +specified on the command line. +- Abstract out a curses input function and separate input pool function to allow +for live adding of pools later. +- Remove the nil arguments check to allow starting without parameters. +- Disable/enable echo & cbreak modes. +- Add a thread that takes keyboard input and allow for quit, silent, debug, +verbose, normal, rpc protocol debugging and clear screen options. +- Add pool option to input and display current pool status, pending code to +allow live changes. +- Add a bool for explicit enabling/disabling of pools. +- Make input pool capable of bringing up pools while running. +- Do one last check of the work before submitting it. +- Implement the ability to live add, enable, disable, and switch to pools. +- Only internally test for block changes when the work matches the current pool +to prevent interleaved block change timing on multipools. +- Display current pool management strategy to enable changing it on the fly. +- The longpoll blanking of the current_block data may not be happening before +the work is converted and appears to be a detected block change. Blank the +current block be +- Make --no-longpoll work again. +- Abstract out active pools count. +- Allow the pool strategy to be modified on the fly. +- Display pool information on the fly as well. +- Add a menu and separate out display options. +- Clean up the messy way the staging thread communicates with the longpoll +thread to determine who found the block first. +- Make the input windows update immediately instead of needing a refresh. +- Allow log interval to be set in the menu. +- Allow scan settings to be modified at runtime. +- Abstract out the longpoll start and explicitly restart it on pool change. +- Make it possible to enable/disable longpoll. +- Set priority correctly on multipools. Display priority and alive/dead +information in display_pools. +- Implement pool removal. +- Limit rolltime work generation to 10 iterations only. +- Decrease testing log to info level. +- Extra refresh not required. +- With huge variation in GPU performance, allow intensity to go from -10 to +10. +- Tell getwork how much of a work item we're likely to complete for future +splitting up of work. +- Remove the mandatory work requirement at startup by testing for invalid work +being passed which allows for work to be queued immediately. This also +removes the requirem +- Make sure intensity is carried over to thread count and is at least the +minimum necessary to work. +- Unlocking error on retry. Locking unnecessary anyway so remove it. +- Clear log window from consistent place. No need for locking since logging is +disabled during input. +- Cannot print the status of threads that don't exist so just queue enough work +for the number of mining threads to prevent crash with -Q N. +- Update phatk kernel to one with new parameters for slightly less overhead +again. Make the queue kernel parameters call a function pointer to select +phatk or poclbm. +- Make it possible to select the choice of kernel on the command line. +- Simplify the output part of the kernel. There's no demonstrable advantage from +more complexity. +- Merge pull request #18 from ycros/cgminer +- No need to make leaveok changes win32 only. +- Build support in for all SSE if possible and only set the default according to +machine capabilities. +- Win32 threading and longpoll keepalive fixes. +- Win32: Fix for mangled output on the terminal on exit. + + +Version 1.3.1 - July 20, 2011 + +- Feature upgrade; Multiple strategies for failover. Choose from default which +now falls back to a priority order from 1st to last, round robin which only +changes pools when one is idle, rotate which changes pools at user-defined +intervals, and load-balance which spreads the work evenly amongst all pools. +- Implement pool rotation strategy. +- Implement load balancing algorithm by rotating requests to each pool. +- Timeout on failed discarding of staged requests. +- Implement proper flagging of idle pools, test them with the watchdog thread, +and failover correctly. +- Move pool active test to own function. +- Allow multiple strategies to be set for multipool management. +- Track pool number. +- Don't waste the work items queued on testing the pools at startup. +- Reinstate the mining thread watchdog restart. +- Add a getpoll bool into the thread information and don't restart threads stuck +waiting on work. +- Rename the idlenet bool for the pool for later use. +- Allow the user/pass userpass urls to be input in any order. +- When json rpc errors occur they occur in spits and starts, so trying to limit +them with the comms error bool doesn't stop a flood of them appearing. +- Reset the queued count to allow more work to be queued for the new pool on +pool switch. + +Version 1.3.0 - July 19, 2011 + +- Massive infrastructure update to support pool failover. +- Accept multiple parameters for url, user and pass and set up structures of +pool data accordingly. +- Probe each pool for what it supports. +- Implement per pool feature support according to rolltime support as +advertised by server. +- Do switching automatically based on a 300 second timeout of locally generated +work or 60 seconds of no response from a server that doesn't support rolltime. +- Implement longpoll server switching. +- Keep per-pool data and display accordingly. +- Make sure cgminer knows how long the pool has actually been out for before +deeming it a prolonged outage. +- Fix bug with ever increasing staged work in 1.2.8 that eventually caused +infinite rejects. +- Make warning about empty http requests not show by default since many +servers do this regularly. + + +Version 1.2.8 - July 18, 2011 + +- More OSX build fixes. +- Add an sse4 algorithm to CPU mining. +- Fix CPU mining with other algorithms not working. +- Rename the poclbm file to ensure a new binary is built since. +- We now are guaranteed to have one fresh work item after a block change and we +should only discard staged requests. +- Don't waste the work we retrieve from a longpoll. +- Provide a control lock around global bools to avoid racing on them. +- Iterating over 1026 nonces when confirming data from the GPU is old code +and unnecessary and can lead to repeats/stales. +- The poclbm kernel needs to be updated to work with the change to 4k sized +output buffers. +- longpoll seems to work either way with post or get but some servers prefer +get so change to httpget. + + +Version 1.2.7 - July 16, 2011 + +- Show last 8 characters of share submitted in log. +- Display URL connected to and user logged in as in status. +- Display current block and when it was started in the status line. +- Only pthread_join the mining threads if they exist as determined by +pthread_cancel and don't fail on pthread_cancel. +- Create a unique work queue for all getworks instead of binding it to thread 0 +to avoid any conflict over thread 0's queue. +- Clean up the code to make it clear it's watchdog thread being messaged to +restart the threads. +- Check the current block description hasn't been blanked pending the real +new current block data. +- Re-enable signal handlers once the signal has been received to make it +possible to kill cgminer if it fails to shut down. +- Disable restarting of CPU mining threads pending further investigation. +- Update longpoll messages. +- Add new block data to status line. +- Fix opencl tests for osx. +- Only do local generation of work if the work item is not stale itself. +- Check for stale work within the mining threads and grab new work if +positive. +- Test for idle network conditions and prevent threads from being restarted +by the watchdog thread under those circumstances. +- Make sure that local work generation does not continue indefinitely by +stopping it after 10 minutes. +- Tweak the kernel to have a shorter path using a 4k buffer and a mask on the +nonce value instead of a compare and loop for a shorter code path. +- Allow queue of zero and make that default again now that we can track how +work is being queued versus staged. This can decrease reject rates. +- Queue precisely the number of mining threads as longpoll_staged after a +new block to not generate local work. + + +Version 1.2.6 - July 15, 2011 + +- Put a current system status line beneath the total work status line +- Fix a counting error that would prevent cgminer from correctly detecting +situations where getwork was failing - this would cause stalls sometimes +unrecoverably. +- Limit the maximum number of requests that can be put into the queue which +otherwise could get arbitrarily long during a network outage. +- Only count getworks that are real queue requests. + + +Version 1.2.5 - July 15, 2011 + +- Conflicting -n options corrected +- Setting an intensity with -I disables dynamic intensity setting +- Removed option to manually disable dynamic intensity +- Improve display output +- Implement signal handler and attempt to clean up properly on exit +- Only restart threads that are not stuck waiting on mandatory getworks +- Compatibility changes courtesy of Ycros to build on mingw32 and osx +- Explicitly grab first work item to prevent false positive hardware errors +due to working on uninitialised work structs +- Add option for non curses --text-only output +- Ensure we connect at least once successfully before continuing to retry to +connect in case url/login parameters were wrong +- Print an executive summary when cgminer is terminated +- Make sure to refresh the status window + +Versions -> 1.2.4 + +- Con Kolivas - July 2011. New maintainership of code under cgminer name. +- Massive rewrite to incorporate GPU mining. +- Incorporate original oclminer c code. +- Rewrite gpu mining code to efficient work loops. +- Implement per-card detection and settings. +- Implement vector code. +- Implement bfi int patching. +- Import poclbm and phatk ocl kernels and use according to hardware type. +- Implement customised optimised versions of opencl kernels. +- Implement binary kernel generation and loading. +- Implement preemptive asynchronous threaded work gathering and pushing. +- Implement variable length extra work queues. +- Optimise workloads to be efficient miners instead of getting lots of extra + work. +- Implement total hash throughput counters, per-card accepted, rejected and + hw error count. +- Staging and watchdog threads to prevent fallover. +- Stale and reject share guarding. +- Autodetection of new blocks without longpoll. +- Dynamic setting of intensity to maintain desktop interactivity. +- Curses interface with generous statistics and information. +- Local generation of work (xroll ntime) when detecting poor network +connectivity. + +Version 1.0.2 + +- Linux x86_64 optimisations - Con Kolivas +- Optimise for x86_64 by default by using sse2_64 algo +- Detects CPUs and sets number of threads accordingly +- Uses CPU affinity for each thread where appropriate +- Sets scheduling policy to lowest possible +- Minor performance tweaks + +Version 1.0.1 - May 14, 2011 + +- OSX support + +Version 1.0 - May 9, 2011 + +- jansson 2.0 compatibility +- correct off-by-one in date (month) display output +- fix platform detection +- improve yasm configure bits +- support full URL, in X-Long-Polling header + +Version 0.8.1 - March 22, 2011 + +- Make --user, --pass actually work + +- Add User-Agent HTTP header to requests, so that server operators may + more easily identify the miner client. + +- Fix minor bug in example JSON config file + +Version 0.8 - March 21, 2011 + +- Support long polling: http://deepbit.net/longpolling.php + +- Adjust max workload based on scantime (default 5 seconds, + or 60 seconds for longpoll) + +- Standardize program output, and support syslog on Unix platforms + +- Suport --user/--pass options (and "user" and "pass" in config file), + as an alternative to the current --userpass + +Version 0.7.2 - March 14, 2011 + +- Add port of ufasoft's sse2 assembly implementation (Linux only) + This is a substantial speed improvement on Intel CPUs. + +- Move all JSON-RPC I/O to separate thread. This reduces the + number of HTTP connections from one-per-thread to one, reducing resource + usage on upstream bitcoind / pool server. + +Version 0.7.1 - March 2, 2011 + +- Add support for JSON-format configuration file. See example + file example-cfg.json. Any long argument on the command line + may be stored in the config file. +- Timestamp each solution found +- Improve sha256_4way performance. NOTE: This optimization makes + the 'hash' debug-print output for sha256_way incorrect. +- Use __builtin_expect() intrinsic as compiler micro-optimization +- Build on Intel compiler +- HTTP library now follows HTTP redirects + +Version 0.7 - February 12, 2011 + +- Re-use CURL object, thereby reuseing DNS cache and HTTP connections +- Use bswap_32, if compiler intrinsic is not available +- Disable full target validation (as opposed to simply H==0) for now + +Version 0.6.1 - February 4, 2011 + +- Fully validate "hash < target", rather than simply stopping our scan + if the high 32 bits are 00000000. +- Add --retry-pause, to set length of pause time between failure retries +- Display proof-of-work hash and target, if -D (debug mode) enabled +- Fix max-nonce auto-adjustment to actually work. This means if your + scan takes longer than 5 seconds (--scantime), the miner will slowly + reduce the number of hashes you work on, before fetching a new work unit. + +Version 0.6 - January 29, 2011 + +- Fetch new work unit, if scanhash takes longer than 5 seconds (--scantime) +- BeeCee1's sha256 4way optimizations +- lfm's byte swap optimization (improves via, cryptopp) +- Fix non-working short options -q, -r + +Version 0.5 - December 28, 2010 + +- Exit program, when all threads have exited +- Improve JSON-RPC failure diagnostics and resilience +- Add --quiet option, to disable hashmeter output. + +Version 0.3.3 - December 27, 2010 + +- Critical fix for sha256_cryptopp 'cryptopp_asm' algo + +Version 0.3.2 - December 23, 2010 + +- Critical fix for sha256_via + +Version 0.3.1 - December 19, 2010 + +- Critical fix for sha256_via +- Retry JSON-RPC failures (see --retry, under "minerd --help" output) + +Version 0.3 - December 18, 2010 + +- Add crypto++ 32bit assembly implementation +- show version upon 'minerd --help' +- work around gcc 4.5.x bug that killed 4way performance + +Version 0.2.2 - December 6, 2010 + +- VIA padlock implementation works now +- Minor build and runtime fixes + +Version 0.2.1 - November 29, 2010 + +- avoid buffer overflow when submitting solutions +- add Crypto++ sha256 implementation (C only, ASM elided for now) +- minor internal optimizations and cleanups + +Version 0.2 - November 27, 2010 + +- Add script for building a Windows installer +- improve hash performance (hashmeter) statistics +- add tcatm 4way sha256 implementation +- Add experimental VIA Padlock sha256 implementation + +Version 0.1.2 - November 26, 2010 + +- many small cleanups and micro-optimizations +- build win32 exe using mingw +- RPC URL, username/password become command line arguments +- remove unused OpenSSL dependency + +Version 0.1.1 - November 24, 2010 + +- Do not build sha256_generic module separately from cpuminer. + +Version 0.1 - November 24, 2010 + +- Initial release. + diff --git a/README b/README new file mode 100644 index 0000000..91a2170 --- /dev/null +++ b/README @@ -0,0 +1,1058 @@ +This is a multi-threaded multi-pool FPGA and ASIC miner for bitcoin. + +This code is provided entirely free of charge by the programmer in his spare +time so donations would be greatly appreciated. Please consider donating to the +address below. + +Con Kolivas +15qSxP1SQcUX3o4nhkfdbgyoWEFMomJ4rZ + +NOTE: This code is licensed under the GPLv3. This means that the source to any +modifications you make to this code MUST be provided by law if you distribute +modified binaries. See COPYING for details. + + +DOWNLOADS: + +http://ck.kolivas.org/apps/cgminer + +GIT TREE: + +https://github.com/ckolivas/cgminer + +Support thread: + +http://bitcointalk.org/index.php?topic=28402.0 + +IRC Channel: + +irc://irc.freenode.net/cgminer + +SEE ALSO API-README, ASIC-README and FGPA-README FOR MORE INFORMATION ON EACH. + +--- + +EXECUTIVE SUMMARY ON USAGE: + +Single pool: + +cgminer -o http://pool:port -u username -p password + +Multiple pools: + +cgminer -o http://pool1:port -u pool1username -p pool1password -o http://pool2:port -u pool2usernmae -p pool2password + +Single pool with a standard http proxy: + +cgminer -o "http:proxy:port|http://pool:port" -u username -p password + +Single pool with a socks5 proxy: + +cgminer -o "socks5:proxy:port|http://pool:port" -u username -p password + +Single pool with stratum protocol support: + +cgminer -o stratum+tcp://pool:port -u username -p password + +Solo mining to local bitcoind: + +cgminer -o http://localhost:8332 -u username -p password --btc-address 15qSxP1SQcUX3o4nhkfdbgyoWEFMomJ4rZ + +The list of proxy types are: + http: standard http 1.1 proxy + http0: http 1.0 proxy + socks4: socks4 proxy + socks5: socks5 proxy + socks4a: socks4a proxy + socks5h: socks5 proxy using a hostname + +If you compile cgminer with a version of CURL before 7.19.4 then some of the above will +not be available. All are available since CURL version 7.19.4 + +If you specify the --socks-proxy option to cgminer, it will only be applied to all pools +that don't specify their own proxy setting like above + + +After saving configuration from the menu, you do not need to give cgminer any +arguments and it will load your configuration. + +Any configuration file may also contain a single + "include" : "filename" +to recursively include another configuration file. +Writing the configuration will save all settings from all files in the output. + + +--- +BUILDING CGMINER FOR YOURSELF + +DEPENDENCIES: +Mandatory: + pkg-config http://www.freedesktop.org/wiki/Software/pkg-config + libtool http://www.gnu.org/software/libtool/ +Optional: + curl dev library http://curl.haxx.se/libcurl/ + (libcurl4-openssl-dev - Must tell configure --disable-libcurl otherwise + it will attempt to compile it in) + + curses dev library + (libncurses5-dev or libpdcurses on WIN32 for text user interface) + + libudev dev library (libudev-dev) + (This is only required for USB device support and is linux only) + +If building from git: + autoconf + automake + +If building on Red Hat: + sudo yum install autoconf automake autoreconf libtool openssl-compat-bitcoin-devel.x86_64 \ + curl libcurl libcurl-devel openssh + +If building on Ubuntu: + sudo apt-get install build-essential autoconf automake libtool pkg-config \ + libcurl3-dev libudev-dev + +CGMiner specific configuration options: + --enable-bmsc Compile support for BitMain Single Chain(default disabled) + --enable-bitmain Compile support for BitMain Multi Chain(default disabled) + --enable-avalon Compile support for Avalon (default disabled) + --enable-avalon2 Compile support for Avalon2 (default disabled) + --enable-avalon4 Compile support for Avalon4 (default disabled) + --enable-bab Compile support for BlackArrow Bitfury (default + disabled) + --enable-bflsc Compile support for BFL ASICs (default disabled) + --enable-bitforce Compile support for BitForce FPGAs (default + disabled) + --enable-bitfury Compile support for BitFury ASICs (default disabled) + --enable-bitmine_A1 Compile support for Bitmine.ch A1 ASICs (default + disabled) + --enable-blockerupter Compile support for ASICMINER BlockErupter Tube/Prisma + (default disabled) + --enable-cointerra Compile support for Cointerra ASICs (default disabled) + --enable-drillbit Compile support for Drillbit BitFury ASICs (default + disabled) + --enable-hashfast Compile support for Hashfast (default disabled) + --enable-icarus Compile support for Icarus (default disabled) + --enable-klondike Compile support for Klondike (default disabled) + --enable-knc Compile support for KnC miners (default disabled) + --enable-minion Compile support for Minion BlackArrow ASIC (default + disabled) + --enable-modminer Compile support for ModMiner FPGAs(default disabled) + --enable-sp10 Compile support for Spondoolies SP10 (default + disabled) + --enable-sp30 Compile support for Spondoolies SP30 (default + disabled) + --disable-libcurl Disable building with libcurl for getwork and GBT + support + --without-curses Compile support for curses TUI (default enabled) + --with-system-libusb Compile against dynamic system libusb (default use + included static libusb) + +Basic *nix build instructions: + To actually build: + + ./autogen.sh # only needed if building from git repo + CFLAGS="-O2 -Wall -march=native" ./configure + make + + No installation is necessary. You may run cgminer from the build + directory directly, but you may do make install if you wish to install + cgminer to a system location or location you specified. + +Building for windows: + +It is actually easiest to build a windows binary using cross compilation tools +provided by "mxe" available at http://mxe.cc/ (use the 32 bit one!) +Once you have followed the instructions for building mxe: + export PATH=(path/to/mxe)/usr/bin/:$PATH + CFLAGS="-O2 -Wall -W -march=i686" ./configure --host=i686-pc-mingw32 + make + +Native WIN32 build instructions: see windows-build.txt but these instructions +are now hopelessly out of date. + +--- + +Usage instructions: Run "cgminer --help" to see options: + +Usage: cgminer [-DdElmpPQqUsTouOchnV] + +Options for both config file and command line: +--anu-freq Set AntminerU1/2 frequency in MHz, range 125-500 (default: 250.0) +--api-allow Allow API access only to the given list of [G:]IP[/Prefix] addresses[/subnets] +--api-description Description placed in the API status header, default: cgminer version +--api-groups API one letter groups G:cmd:cmd[,P:cmd:*...] defining the cmds a groups can use +--api-listen Enable API, default: disabled +--api-mcast Enable API Multicast listener, default: disabled +--api-mcast-addr API Multicast listen address +--api-mcast-code Code expected in the API Multicast message, don't use '-' +--api-mcast-des Description appended to the API Multicast reply, default: '' +--api-mcast-port API Multicast listen port (default: 4028) +--api-network Allow API (if enabled) to listen on/for any address, default: only 127.0.0.1 +--api-port Port number of miner API (default: 4028) +--au3-freq Set AntminerU3 frequency in MHz, range 100-250 (default: 225.0) +--au3-volt Set AntminerU3 voltage in mv, range 725-850, 0 to not set (default: 750) +--avalon-auto Adjust avalon overclock frequency dynamically for best hashrate +--avalon-cutoff Set avalon overheat cut off temperature (default: 60) +--avalon-fan Set fanspeed percentage for avalon, single value or range (default: 20-100) +--avalon-freq Set frequency range for avalon-auto, single value or range +--avalon-options Set avalon options baud:miners:asic:timeout:freq:tech +--avalon-temp Set avalon target temperature (default: 50) +--avalon2-freq Set frequency range for Avalon2, single value or range +--avalon2-voltage Set Avalon2 core voltage, in millivolts +--avalon2-fan Set Avalon2 target fan speed +--avalon2-cutoff Set Avalon2 overheat cut off temperature (default: 88) +--avalon2-fixed-speed Set Avalon2 fan to fixed speed +--avalon4-automatic-voltage Automatic adjust voltage base on module DH +--avalon4-voltage Set Avalon4 core voltage, in millivolts, step: 125 +--avalon4-freq Set frequency for Avalon4, 1 to 3 values, example: 445:385:370 +--avalon4-fan Set Avalon4 target fan speed range +--avalon4-temp Set Avalon4 target temperature (default: 42) +--avalon4-cutoff Set Avalon4 overheat cut off temperature (default: 65) +--avalon4-polling-delay Set Avalon4 polling delay value (ms) (default: 20) +--avalon4-ntime-offset Set Avalon4 MM ntime rolling max offset (default: 4) +--avalon4-aucspeed Set Avalon4 AUC IIC bus speed (default: 400000) +--avalon4-aucxdelay Set Avalon4 AUC IIC xfer read delay, 4800 ~= 1ms (default: 9600) +--bab-options Set BaB options max:def:min:up:down:hz:delay:trf +--balance Change multipool strategy from failover to even share balance +--benchfile Run cgminer in benchmark mode using a work file - produces no shares +--benchfile-display Display each benchfile nonce found +--benchmark Run cgminer in benchmark mode - produces no shares +--bet-clk Set clockspeed of ASICMINER Tube/Prisma to (arg+1)*10MHz (default: 23) +--bfl-range Use nonce range on bitforce devices if supported +--bflsc-overheat Set overheat temperature where BFLSC devices throttle, 0 to disable (default: 85) +--bitburner-fury-voltage Set BitBurner Fury core voltage, in millivolts +--bitburner-fury-options Override avalon-options for BitBurner Fury boards baud:miners:asic:timeout:freq +--bitburner-voltage Set BitBurner (Avalon) core voltage, in millivolts +--bitmain-auto Adjust bitmain overclock frequency dynamically for best hashrate +--bitmain-cutoff Set bitmain overheat cut off temperature +--bitmain-fan Set fanspeed percentage for bitmain, single value or range (default: 20-100) +--bitmain-freq Set frequency range for bitmain-auto, single value or range +--bitmain-hwerror Set bitmain device detect hardware error +--bitmain-options Set bitmain options baud:miners:asic:timeout:freq +--bitmain-temp Set bitmain target temperature +--bxf-bits Set max BXF/HXF bits for overclocking (default: 54) +--bxf-temp-target Set target temperature for BXF/HXF devices (default: 82) +--bxm-bits Set BXM bits for overclocking (default: 54) +--btc-address Set bitcoin target address when solo mining to bitcoind +--btc-sig Set signature to add to coinbase when solo mining (optional) +--compact Use compact display without per device statistics +--debug|-D Enable debug output +--disable-rejecting Automatically disable pools that continually reject shares +--drillbit-options Set drillbit options :clock[:clock_divider][:voltage] +--expiry|-E Upper bound on how many seconds after getting work we consider a share from it stale (default: 120) +--failover-only Don't leak work to backup pools when primary pool is lagging +--fix-protocol Do not redirect to a different getwork protocol (eg. stratum) +--hfa-hash-clock Set hashfast clock speed (default: 550) +--hfa-fail-drop Set how many MHz to drop clockspeed each failure on an overlocked hashfast device (default: 10) +--hfa-fan Set fanspeed percentage for hashfast, single value or range (default: 10-85) +--hfa-name Set a unique name for a single hashfast device specified with --usb or the first device found +--hfa-noshed Disable hashfast dynamic core disabling feature +--hfa-options Set hashfast options name:clock (comma separated) +--hfa-temp-overheat Set the hashfast overheat throttling temperature (default: 95) +--hfa-temp-target Set the hashfast target temperature (0 to disable) (default: 88) +--hro-freq Set the hashratio clock frequency (default: 280) +--hotplug Seconds between hotplug checks (0 means never check) +--klondike-options Set klondike options clock:temptarget +--load-balance Change multipool strategy from failover to quota based balance +--log|-l Interval in seconds between log output (default: 5) +--lowmem Minimise caching of shares for low memory applications +--minion-chipreport Seconds to report chip 5min hashrate, range 0-100 (default: 0=disabled) +--minion-freq Set minion chip frequencies in MHz, single value or comma list, range 100-1400 (default: 1200) +--minion-freqchange Millisecond total time to do frequency changes (default: 1000) +--minion-freqpercent Percentage to use when starting up a chip (default: 70%) +--minion-idlecount Report when IdleCount is >0 or changes +--minion-ledcount Turn off led when more than this many chips below the ledlimit (default: 0) +--minion-ledlimit Turn off led when chips GHs are below this (default: 90) +--minion-noautofreq Disable automatic frequency adjustment +--minion-overheat Enable directly halting any chip when the status exceeds 100C +--minion-spidelay Add a delay in microseconds after each SPI I/O +--minion-spireset SPI regular reset: iNNN for I/O count or sNNN for seconds - 0 means none +--minion-spisleep Sleep time in milliseconds when doing an SPI reset +--minion-temp Set minion chip temperature threshold, single value or comma list, range 120-160 (default: 135C) +--monitor|-m Use custom pipe cmd for output messages +--nfu-bits Set nanofury bits for overclocking, range 32-63 (default: 50) +--net-delay Impose small delays in networking to not overload slow routers +--no-submit-stale Don't submit shares if they are detected as stale +--osm-led-mode Set LED mode for OneStringMiner devices (default: 4) +--pass|-p Password for bitcoin JSON-RPC server +--per-device-stats Force verbose mode and output per-device statistics +--protocol-dump|-P Verbose dump of protocol-level activities +--queue|-Q Minimum number of work items to have queued (0+) (default: 1) +--quiet|-q Disable logging output, display status and errors +--quota|-U quota;URL combination for server with load-balance strategy quotas +--real-quiet Disable all output +--rock-freq Set RockMiner frequency in MHz, range 200-400 (default: 270) +--rotate Change multipool strategy from failover to regularly rotate at N minutes (default: 0) +--round-robin Change multipool strategy from failover to round robin on failure +--scan-time|-s Upper bound on time spent scanning current work, in seconds (default: -1) +--sched-start Set a time of day in HH:MM to start mining (a once off without a stop time) +--sched-stop Set a time of day in HH:MM to stop mining (will quit without a start time) +--sharelog Append share log to file +--shares Quit after mining N shares (default: unlimited) +--socks-proxy Set socks4 proxy (host:port) +--suggest-diff Suggest miner difficulty for pool to user (default: none) +--syslog Use system log for output messages (default: standard error) +--temp-cutoff Temperature where a device will be automatically disabled, one value or comma separated list (default: 95) +--text-only|-T Disable ncurses formatted screen output +--url|-o URL for bitcoin JSON-RPC server +--usb USB device selection +--user|-u Username for bitcoin JSON-RPC server +--userpass|-O Username:Password pair for bitcoin JSON-RPC server +--verbose Log verbose output to stderr as well as status output +--widescreen Use extra wide display without toggling +--worktime Display extra work time debug information +Options for command line only: +--config|-c Load a JSON-format configuration file +See example.conf for an example configuration. +--default-config Specify the filename of the default config file +Loaded at start and used when saving without a name. +--help|-h Print this message +--ndevs|-n Display all USB devices and exit +--version|-V Display version and exit + + +Silent USB device (ASIC and FPGA) options: + +--icarus-options Set specific FPGA board configurations - one set of values for all or comma separated +--icarus-timing Set how the Icarus timing is calculated - one setting/value for all or comma separated +--usb-dump (See FPGA-README) + +See FGPA-README or ASIC-README for more information regarding these. + + +ASIC only options: + +--anu-freq Set AntminerU1/2 frequency in MHz, range 125-500 (default: 250.0) +--au3-freq Set AntminerU3 frequency in MHz, range 100-250 (default: 225.0) +--au3-volt Set AntminerU3 voltage in mv, range 725-850, 0 to not set (default: 750) +--avalon-auto Adjust avalon overclock frequency dynamically for best hashrate +--avalon-cutoff Set avalon overheat cut off temperature (default: 60) +--avalon-fan Set fanspeed percentage for avalon, single value or range (default: 20-100) +--avalon-freq Set frequency range for avalon-auto, single value or range +--avalon-options Set avalon options baud:miners:asic:timeout:freq:tech +--avalon-temp Set avalon target temperature (default: 50) +--avalon2-freq Set frequency range for Avalon2, single value or range +--avalon2-voltage Set Avalon2 core voltage, in millivolts +--avalon2-fan Set Avalon2 target fan speed +--avalon2-cutoff Set Avalon2 overheat cut off temperature (default: 88) +--avalon2-fixed-speed Set Avalon2 fan to fixed speed +--avalon4-automatic-voltage Automatic adjust voltage base on module DH +--avalon4-voltage Set Avalon4 core voltage, in millivolts, step: 125 +--avalon4-freq Set frequency for Avalon4, 1 to 3 values, example: 445:385:370 +--avalon4-fan Set Avalon4 target fan speed range +--avalon4-temp Set Avalon4 target temperature (default: 42) +--avalon4-cutoff Set Avalon4 overheat cut off temperature (default: 65) +--avalon4-polling-delay Set Avalon4 polling delay value (ms) (default: 20) +--avalon4-ntime-offset Set Avalon4 MM ntime rolling max offset (default: 4) +--avalon4-aucspeed Set Avalon4 AUC IIC bus speed (default: 400000) +--avalon4-aucxdelay Set Avalon4 AUC IIC xfer read delay, 4800 ~= 1ms (default: 9600) +--bab-options Set BaB options max:def:min:up:down:hz:delay:trf +--bflsc-overheat Set overheat temperature where BFLSC devices throttle, 0 to disable (default: 90) +--bitburner-fury-options Override avalon-options for BitBurner Fury boards baud:miners:asic:timeout:freq +--bitburner-fury-voltage Set BitBurner Fury core voltage, in millivolts +--bitburner-voltage Set BitBurner (Avalon) core voltage, in millivolts +--bitmine-a1-options ::: +--bxf-temp-target Set target temperature for BXF devices (default: 82) +--bxm-bits Set BXM bits for overclocking (default: 50) +--hfa-hash-clock Set hashfast clock speed (default: 550) +--hfa-fail-drop Set how many MHz to drop clockspeed each failure on an overlocked hashfast device (default: 10) +--hfa-fan Set fanspeed percentage for hashfast, single value or range (default: 10-85) +--hfa-name Set a unique name for a single hashfast device specified with --usb or the first device found +--hfa-noshed Disable hashfast dynamic core disabling feature +--hfa-temp-overheat Set the hashfast overheat throttling temperature (default: 95) +--hfa-temp-target Set the hashfast target temperature (0 to disable) (default: 88) +--hro-freq Set the hashratio clock frequency (default: 280) +--klondike-options Set klondike options clock:temptarget +--rock-freq Set RockMiner frequency in MHz, range 125-500 (default: 270) + +See ASIC-README for more information regarding these. + + +FPGA only options: + +--bfl-range Use nonce range on bitforce devices if supported + +See FGPA-README for more information regarding this. + + +Cgminer should automatically find all of your Avalon ASIC, BFL ASIC, BitForce +FPGAs, Icarus bitstream FPGAs, Klondike ASIC, ASICMINER usb block erupters, +KnC ASICs, BaB ASICs, Hashfast ASICs, ModMiner FPGAs, BPMC/BGMC BF1 USB ASICs, +Bi*fury USB ASICs, Onestring miner USB ASICs, Hexfury USB ASICs, Nanofury USB +ASICs, Antminer U1/U2/U2+ U3 USB ASICs, Cointerra devices, BFx2 USB ASICs, +Rockminer R-Box/RK-Box/T1 USB ASICs, Avalon2/3/4 USB ASICs and Hashratio USB +ASICs. + +--- + +SETTING UP USB DEVICES + +WINDOWS: + +On windows, the direct USB support requires the installation of a WinUSB +driver (NOT the ftdi_sio driver), and attach it to the chosen USB device. +When configuring your device, plug it in and wait for windows to attempt to +install a driver on its own. It may think it has succeeded or failed but wait +for it to finish regardless. This is NOT the driver you want installed. At this +point you need to associate your device with the WinUSB driver. The easiest +way to do this is to use the zadig utility which you must right click on and +run as administrator. Then once you plug in your device you can choose the +"list all devices" from the "option" menu and you should be able to see the +device as something like: "BitFORCE SHA256 SC". Choose the install or replace +driver option and select WinUSB. You can either google for zadig or download +it from the cgminer directory in the DOWNLOADS link above. + +When you first switch a device over to WinUSB with zadig and it shows that +correctly on the left of the zadig window, but it still gives permission +errors, you may need to unplug the USB miner and then plug it back in. Some +users may need to reboot at this point. + + +LINUX: + +The short version: + + sudo cp 01-cgminer.rules /etc/udev/rules.d/ + + +The long version: + +On linux, the direct USB support requires no drivers at all. However due to +permissions issues, you may not be able to mine directly on the devices as a +regular user without giving the user access to the device or by mining as +root (administrator). In order to give your regular user access, you can make +him a member of the plugdev group with the following commands: + + sudo usermod -G plugdev -a `whoami` + +If your distribution does not have the plugdev group you can create it with: + + sudo groupadd plugdev + +In order for the USB devices to instantly be owned by the plugdev group and +accessible by anyone from the plugdev group you can copy the file +"01-cgminer.rules" from the cgminer archive into the /etc/udev/rules.d +directory with the following command: + + sudo cp 01-cgminer.rules /etc/udev/rules.d/ + +After this you can either manually restart udev and re-login, or more easily +just reboot. + + +OSX: + +On OSX, like Linux, no drivers need to be installed. However some devices +like the bitfury USB sticks automatically load a driver thinking they're a +modem and the driver needs to be unloaded for cgminer to work: + +sudo kextunload -b com.apple.driver.AppleUSBCDC +sudo kextunload -b com.apple.driver.AppleUSBCDCACMData + +There may be a limit to the number of USB devices that you are allowed to start. +The following set of commands, followed by a reboot will increase that: + + sudo su + touch /etc/sysctl.conf + echo kern.sysv.semume=100 >> /etc/sysctl.conf + chown root:wheel /etc/sysctl.conf + chmod 0644 /etc/sysctl.conf + +Some devices need superuser access to mine on them so cgminer may need to +be started with sudo +i.e.: +sudo cgminer + + +--- + +Advanced USB options: + +The --usb option can restrict how many USB devices are found: + + --usb 1:2,1:3,1:4,1:* +or + --usb BAS:1,BFL:1,MMQ:0,ICA:0,KLN:0 +or + --usb :10 + +You can only use one of the above 3 + +The first version + --usb 1:2,1:3,1:4,1:* +allows you to select which devices to mine on with a list of USB + bus_number:device_address +All other USB devices will be ignored +Hotplug will also only look at the devices matching the list specified and +find nothing new if they are all in use +You can specify just the USB bus_number to find all devices like 1:* +which means any devices on USB bus_number 1 +This is useful if you unplug a device then plug it back in the same port, +it usually reappears with the same bus_number but a different device_address + +You can see the list of all USB devices on linux with 'sudo lsusb' +Cgminer will list the recognised USB devices + +with the '-n' option or the +'--usb-dump 0' option +The '--usb-dump N' option with a value of N greater than 0 will dump a lot +of details about each recognised USB device +If you wish to see all USB devices, include the --usb-list-all option + +The second version + --usb BAS:1,BFL:1,MMQ:0,ICA:0,KLN:0 +allows you to specify how many devices to choose based on each device +driver cgminer has - the current USB drivers are: +AVA, BAS, BFL, BF1, DRB, HFA, ICA, KLN and MMQ. + +N.B. you can only specify which device driver to limit, not the type of +each device, e.g. with BAS:n you can limit how many BFL ASIC devices will +be checked, but you cannot limit the number of each type of BFL ASIC + +Also note that the MMQ count is the number of MMQ backplanes you have +not the number of MMQ FPGAs + +The third version + --usb :10 +means only use a maximum of 10 devices of any supported USB devices +Once cgminer has 10 devices it will not configure any more and hotplug will +not scan for any more +If one of the 10 devices stops working, hotplug - if enabled, as is default +- will scan normally again until it has 10 devices + + --usb :0 will disable all USB I/O other than to initialise libusb + +--- + +WHILE RUNNING: + +The following options are available while running with a single keypress: + + [U]SB management [P]ool management [S]ettings [D]isplay options [Q]uit + + +U gives you: + +[S]ummary of device information +[E]nable device +[D]isable device +[U]nplug to allow hotplug restart +[R]eset device USB +[L]ist all known devices +[B]lacklist current device from current instance of cgminer +[W]hitelist previously blacklisted device +[H]otplug interval (0 to disable) + + +P gives you: + +Current pool management strategy: Failover +[F]ailover only disabled +[A]dd pool [R]emove pool [D]isable pool [E]nable pool +[C]hange management strategy [S]witch pool [I]nformation + + +S gives you: + +[Q]ueue: 1 +[S]cantime: 60 +[E]xpiry: 120 +[W]rite config file +[C]gminer restart + + +D gives you: + +[N]ormal [C]lear [S]ilent mode (disable all output) +[D]ebug:off +[P]er-device:off +[Q]uiet:off +[V]erbose:off +[R]PC debug:off +[W]orkTime details:off +co[M]pact: off +[T]oggle status switching:enabled +[Z]ero statistics +[L]og interval:5 + + +Q quits the application. + + +The running log shows output like this: + + [2013-11-09 11:04:41] Accepted 01b3bde7 Diff 150/128 AVA 1 pool 0 + [2013-11-09 11:04:49] Accepted 015df995 Diff 187/128 AVA 1 pool 0 + [2013-11-09 11:04:50] Accepted 01163b68 Diff 236/128 AVA 1 pool 0 + [2013-11-09 11:04:53] Accepted 9f745840 Diff 411/128 BAS 1 pool 0 + +The 8 byte hex value are the 1st nonzero bytes of the share being submitted to +the pool. The 2 diff values are the actual difficulty target that share reached +followed by the difficulty target the pool is currently asking for. + +--- +Also many issues and FAQs are covered in the forum thread +dedicated to this program, + http://forum.bitcoin.org/index.php?topic=28402.0 + +DISPLAY: + +The display is roughly split into two portions, the top status window and the +bottom scrolling log window. + + +STATUS WINDOW +The status window is split into overall status and per device status. + +Overall status: + +The output line shows the following: + (5s):2.469T (1m):2.677T (5m):2.040T (15m):1.014T (avg):2.733Th/s + +These are exponentially decaying average hashrates over 5s/1m/5m/15m and an +average since the start. + +Followed by: + A:290391 R:5101 HW:145 WU:37610.4/m + +Each column is as follows: +A: The total difficulty of Accepted shares +R: The total difficulty of Rejected shares +HW: The number of HardWare errors +WU: The Work Utility defined as the number of diff1 shares work / minute + (accepted or rejected). + +alternating with: + ST: 22 SS: 0 NB: 2 LW: 356090 GF: 0 RF: 0 + +ST is STaged work items (ready to use). +SS is Stale Shares discarded (detected and not submitted so don't count as rejects) +NB is New Blocks detected on the network +LW is Locally generated Work items +GF is Getwork Fail Occasions (server slow to provide work) +RF is Remote Fail occasions (server slow to accept work) + +Followed by: + Connected to pool.com diff 3.45K with stratum as user me + +The diff shown is the current vardiff requested by the pool currently being +mined at. + +Followed by: +Block: ca0d237f... Diff:5.01G Started: [00:14:27] Best share: 1.18M + +This shows a short stretch about the current block, when the new block started, +and the all time best difficulty share you've found since starting cgminer +this time. + +Per device status: + + 6: HFS Random : 645MHz 85C 13% 0.79V | 2.152T / 1.351Th/s + +Each column is as follows: +Temperature (if supported) +Fanspeed (if supported) +Voltage (if supported) + +A 5 second exponentially decaying average hash rate +An all time average hash rate + +alternating with + + 6: HFS Random : 645MHz 86C 13% 0.80V | A:290348 R:1067 HW:88 WU:18901.8/m + +The total difficulty of accepted shares +The total difficulty of rejected shares +The number of hardware erorrs +The work utility defined as the number of diff1 shares work / minute + + +LOG WINDOW + +All running information is shown here, usually share submission results and +block update notifications, along with device messages and warnings. + + [2014-03-29 00:24:09] Accepted 1397768d Diff 3.35K/2727 HFS 0 pool 0 + [2014-03-29 00:24:13] Stratum from pool 0 detected new block + + +--- +MULTIPOOL + +FAILOVER STRATEGIES WITH MULTIPOOL: +A number of different strategies for dealing with multipool setups are +available. Each has their advantages and disadvantages so multiple strategies +are available by user choice, as per the following list: + +FAILOVER: +The default strategy is failover. This means that if you input a number of +pools, it will try to use them as a priority list, moving away from the 1st +to the 2nd, 2nd to 3rd and so on. If any of the earlier pools recover, it will +move back to the higher priority ones. + +ROUND ROBIN: +This strategy only moves from one pool to the next when the current one falls +idle and makes no attempt to move otherwise. + +ROTATE: +This strategy moves at user-defined intervals from one active pool to the next, +skipping pools that are idle. + +LOAD BALANCE: +This strategy sends work to all the pools on a quota basis. By default, all +pools are allocated equal quotas unless specified with --quota. This +apportioning of work is based on work handed out, not shares returned so is +independent of difficulty targets or rejected shares. While a pool is disabled +or dead, its quota is dropped until it is re-enabled. Quotas are forward +looking, so if the quota is changed on the fly, it only affects future work. +If all pools are set to zero quota or all pools with quota are dead, it will +fall back to a failover mode. See quota below for more information. + +The failover-only flag has special meaning in combination with load-balance +mode and it will distribute quota back to priority pool 0 from any pools that +are unable to provide work for any reason so as to maintain quota ratios +between the rest of the pools. + +BALANCE: +This strategy monitors the amount of difficulty 1 shares solved for each pool +and uses it to try to end up doing the same amount of work for all pools. + + +--- +QUOTAS + +The load-balance multipool strategy works off a quota based scheduler. The +quotas handed out by default are equal, but the user is allowed to specify any +arbitrary ratio of quotas. For example, if all the quota values add up to 100, +each quota value will be a percentage, but if 2 pools are specified and pool0 +is given a quota of 1 and pool1 is given a quota of 9, pool0 will get 10% of +the work and pool1 will get 90%. Quotas can be changed on the fly by the API, +and do not act retrospectively. Setting a quota to zero will effectively +disable that pool unless all other pools are disabled or dead. In that +scenario, load-balance falls back to regular failover priority-based strategy. +While a pool is dead, it loses its quota and no attempt is made to catch up +when it comes back to life. + +To specify quotas on the command line, pools should be specified with a +semicolon separated --quota(or -U) entry instead of --url. Pools specified with +--url are given a nominal quota value of 1 and entries can be mixed. + +For example: +--url poola:porta -u usernamea -p passa --quota "2;poolb:portb" -u usernameb -p passb +Will give poola 1/3 of the work and poolb 2/3 of the work. + +Writing configuration files with quotas is likewise supported. To use the above +quotas in a configuration file they would be specified thus: + +"pools" : [ + { + "url" : "poola:porta", + "user" : "usernamea", + "pass" : "passa" + }, + { + "quota" : "2;poolb:portb", + "user" : "usernameb", + "pass" : "passb" + } +] + + +--- +SOLO MINING + +Solo mining can be done efficiently as a single pool entry or a backup to +any other pooled mining and it is recommended everyone have solo mining set up +as their final backup in case all their other pools are DDoSed/down for the +security of the network. To enable solo mining, one must be running a local +bitcoind/bitcoin-qt or have one they have rpc access to. To do this, edit your +bitcoind configuration file (bitcoin.conf) with the following extra lines, +using your choice of username and password: + +rpcuser=username +rpcpassword=password + +Restart bitcoind, then start cgminer, pointing to the bitcoind and choose a +btc address with the following options, altering to suit their setup: + +cgminer -o http://localhost:8332 -u username -p password --btc-address 15qSxP1SQcUX3o4nhkfdbgyoWEFMomJ4rZ + +Note the http:// is mandatory for solo mining. + +--- +LOGGING + +cgminer will log to stderr if it detects stderr is being redirected to a file. +To enable logging simply add 2>logfile.txt to your command line and logfile.txt +will contain the logged output at the log level you specify (normal, verbose, +debug etc.) + +In other words if you would normally use: +./cgminer -o xxx -u yyy -p zzz +if you use +./cgminer -o xxx -u yyy -p zzz 2>logfile.txt +it will log to a file called logfile.txt and otherwise work the same. + +There is also the -m option on linux which will spawn a command of your choice +and pipe the output directly to that command. + +The WorkTime details 'debug' option adds details on the end of each line +displayed for Accepted or Rejected work done. An example would be: + + <-00000059.ed4834a3 M:X D:1.0 G:17:02:38:0.405 C:1.855 (2.995) W:3.440 (0.000) S:0.461 R:17:02:47 + +The first 2 hex codes are the previous block hash, the rest are reported in +seconds unless stated otherwise: +The previous hash is followed by the getwork mode used M:X where X is one of +P:Pool, T:Test Pool, L:LP or B:Benchmark, +then D:d.ddd is the difficulty required to get a share from the work, +then G:hh:mm:ss:n.nnn, which is when the getwork or LP was sent to the pool and +the n.nnn is how long it took to reply, +followed by 'O' on it's own if it is an original getwork, or 'C:n.nnn' if it was +a clone with n.nnn stating how long after the work was recieved that it was cloned, +(m.mmm) is how long from when the original work was received until work started, +W:n.nnn is how long the work took to process until it was ready to submit, +(m.mmm) is how long from ready to submit to actually doing the submit, this is +usually 0.000 unless there was a problem with submitting the work, +S:n.nnn is how long it took to submit the completed work and await the reply, +R:hh:mm:ss is the actual time the work submit reply was received + +If you start cgminer with the --sharelog option, you can get detailed +information for each share found. The argument to the option may be "-" for +standard output (not advisable with the ncurses UI), any valid positive number +for that file descriptor, or a filename. + +To log share data to a file named "share.log", you can use either: +./cgminer --sharelog 50 -o xxx -u yyy -p zzz 50>share.log +./cgminer --sharelog share.log -o xxx -u yyy -p zzz + +For every share found, data will be logged in a CSV (Comma Separated Value) +format: + timestamp,disposition,target,pool,dev,thr,sharehash,sharedata +For example (this is wrapped, but it's all on one line for real): + 1335313090,reject, + ffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000, + http://localhost:8337,ASC0,0, + 6f983c918f3299b58febf95ec4d0c7094ed634bc13754553ec34fc3800000000, + 00000001a0980aff4ce4a96d53f4b89a2d5f0e765c978640fe24372a000001c5 + 000000004a4366808f81d44f26df3d69d7dc4b3473385930462d9ab707b50498 + f681634a4f1f63d01a0cd43fb338000000000080000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000080020000 + +--- + +BENCHMARK + +The --benchmark option hashes a single fixed work item over and over and does +not submit shares to any pools. + +The --benchfile option hashes the work given in the file supplied. +The format of the work file is: +version,merkleroot,prevhash,diffbits,noncetime +Any empty line or any line starting with '#' or '/' is ignored. +When it reaches the end of the file it continues back at the top. + +The format of the data items matches the byte ordering and format of the +the bitcoind getblock RPC output. + +An example file containing bitcoin block #1 would be: + +# Block 1 +1,0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098,00000000001 +9d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f,1d00ffff,1231469665 + +However, the work data should be one line without the linebreak in the middle + +If you use --benchfile , then --benchfile-display will output a log line, +for each nonce found, showing the nonce value in decimal and hex and the work +used to find it in hex. + +--- + +RPC API + +For RPC API details see the API-README file + +--- + +FAQ + +Q: Help, I've started cgminer and everything reads zero!? +A: Welcome to bitcoin mining. Your computer by itself cannot mine bitcoin no +matter how powerful it is. You have to purchase dedicated mining hardware +called ASICs to plug into your computer. See Q regarding ASICs below. + +Q: I have multiple USB stick devices but I can't get them all to work at once? +A: Very few USB hubs deliver the promised power required to run as many devices +as they fit if all of them draw power from USB. + +Q: I've plugged my devices into my USB hub but nothing shows up? +A: RPis and Windows have incomplete or non-standard USB3 support so they may +never work. It may be possible to get a USB3 hub to work by plugging it into +a USB2 hub. When choosing a hub, USB2 hubs are preferable whenever possible +due to better support all round. + +Q: Can I mine on servers from different networks (eg xxxcoin and bitcoin) at +the same time? +A: No, cgminer keeps a database of the block it's working on to ensure it does +not work on stale blocks, and having different blocks from two networks would +make it invalidate the work from each other. + +Q: Can I configure cgminer to mine with different login credentials or pools +for each separate device? +A: No. + +Q: Can I put multiple pools in the config file? +A: Yes, check the example.conf file. Alternatively, set up everything either on +the command line or via the menu after startup and choose settings->write +config file and the file will be loaded one each startup. + +Q: The build fails with gcc is unable to build a binary. +A: Remove the "-march=native" component of your CFLAGS as your version of gcc +does not support it. Also -O2 is capital o 2, not zero 2. + +Q: Can you implement feature X? +A: I can, but time is limited, and people who donate are more likely to get +their feature requests implemented. + +Q: Work keeps going to my backup pool even though my primary pool hasn't +failed? +A: Cgminer checks for conditions where the primary pool is lagging and will +pass some work to the backup servers under those conditions. The reason for +doing this is to try its absolute best to keep the devices working on something +useful and not risk idle periods. You can disable this behaviour with the +option --failover-only. + +Q: Is this a virus? +A: Cgminer is being packaged with other trojan scripts and some antivirus +software is falsely accusing cgminer.exe as being the actual virus, rather +than whatever it is being packaged with. If you installed cgminer yourself, +then you do not have a virus on your computer. Complain to your antivirus +software company. They seem to be flagging even source code now from cgminer +as viruses, even though text source files can't do anything by themself. + +Q: Can you modify the display to include more of one thing in the output and +less of another, or can you change the quiet mode or can you add yet another +output mode? +A: Everyone will always have their own view of what's important to monitor. +The defaults are very sane and I have very little interest in changing this +any further. There is far more detail in the API output than can be reasonably +displayed on the small console window, and using an external interface such +as miner.php is much more useful for setups with many devices. + +Q: What are the best parameters to pass for X pool/hardware/device. +A: Virtually always, the DEFAULT parameters give the best results. Most user +defined settings lead to worse performance. + +Q: What happened to CPU and GPU mining? +A: Their efficiency makes them irrelevant in the bitcoin mining world today +and the author has no interest in supporting alternative coins that are better +mined by these devices. + +Q: GUI version? +A: No. The RPC interface makes it possible for someone else to write one +though. + +Q: I'm having an issue. What debugging information should I provide? +A: Start cgminer with your regular commands and add -D -T --verbose and provide +the full startup output and a summary of your hardware and operating system. + +Q: Why don't you provide win64 builds? +A: Win32 builds work everywhere and there is precisely zero advantage to a +64 bit build on windows. + +Q: Is it faster to mine on windows or linux? +A: It makes no difference in terms of performance. It comes down to choice of +operating system for their various features and your comfort level. However +linux is the primary development platform and is virtually guaranteed to be +more stable. + +Q: My network gets slower and slower and then dies for a minute? +A; Try the --net-delay option if you are on a getwork or GBT server. This does +nothing with stratum mining. + +Q: How do I tune for p2pool? +A: It is also recommended to use --failover-only since the work is effectively +like a different block chain, and not enabling --no-submit-stale. If mining with +a BFL (fpga) minirig, it is worth adding the --bfl-range option. + +Q: I run PHP on windows to access the API with the example miner.php. Why does +it fail when php is installed properly but I only get errors about Sockets not +working in the logs? +A: http://us.php.net/manual/en/sockets.installation.php + +Q: What is a PGA? +A: Cgminer supports 3 FPGAs: BitForce, Icarus and ModMiner. +They are Field-Programmable Gate Arrays that have been programmed to do Bitcoin +mining. Since the acronym needs to be only 3 characters, the "Field-" part has +been skipped. + +Q: What is an ASIC? +A: They are Application Specify Integrated Circuit devices and provide the +highest performance per unit power due to being dedicated to only one purpose. +They are the only meaningful way to mine bitcoin today. + +Q: What is stratum and how do I use it? +A: Stratum is a protocol designed for pooled mining in such a way as to +minimise the amount of network communications, yet scale to hardware of any +speed. With versions of cgminer 2.8.0+, if a pool has stratum support, cgminer +will automatically detect it and switch to the support as advertised if it can. +If you input the stratum port directly into your configuration, or use the +special prefix "stratum+tcp://" instead of "http://", cgminer will ONLY try to +use stratum protocol mining. The advantages of stratum to the miner are no +delays in getting more work for the miner, less rejects across block changes, +and far less network communications for the same amount of mining hashrate. If +you do NOT wish cgminer to automatically switch to stratum protocol even if it +is detected, add the --fix-protocol option. + +Q: Why don't the statistics add up: Accepted, Rejected, Stale, Hardware Errors, +Diff1 Work, etc. when mining greater than 1 difficulty shares? +A: As an example, if you look at 'Difficulty Accepted' in the RPC API, the number +of difficulty shares accepted does not usually exactly equal the amount of work +done to find them. If you are mining at 8 difficulty, then you would expect on +average to find one 8 difficulty share, per 8 single difficulty shares found. +However, the number is actually random and converges over time, it is an average, +not an exact value, thus you may find more or less than the expected average. + +Q: My keyboard input momentarily pauses or repeats keys every so often on +windows while mining? +A: The USB implementation on windows can be very flaky on some hardware and +every time cgminer looks for new hardware to hotplug it it can cause these +sorts of problems. You can disable hotplug with: +--hotplug 0 + +Q: What should my Work Utility (WU) be? +A: Work utility is the product of hashrate * luck and only stabilises over a +very long period of time. Assuming all your work is valid work, bitcoin mining +should produce a work utility of approximately 1 per 71.6MH. This means at +5GH you should have a WU of 5000 / 71.6 or ~ 69. You cannot make your machine +do "better WU" than this - it is luck related. However you can make it much +worse if your machine produces a lot of hardware errors producing invalid work. + +Q: What should I build in for a generic distribution binary? +A: There are a number of drivers that expect to be used on dedicated standalone +hardware. That said, the drivers that are designed to work generically with +USB on any hardware are the following: + +--enable-avalon +--enable-avalon2 +--enable-avalon4 +--enable-bflsc +--enable-bitfury +--enable-blockerupter +--enable-cointerra +--enable-drillbit +--enable-hashfast +--enable-hashratio +--enable-icarus +--enable-klondike + +--- + +This code is provided entirely free of charge by the programmer in his spare +time so donations would be greatly appreciated. Please consider donating to the +address below. + +Con Kolivas +15qSxP1SQcUX3o4nhkfdbgyoWEFMomJ4rZ diff --git a/api-example.c b/api-example.c new file mode 100644 index 0000000..f363b81 --- /dev/null +++ b/api-example.c @@ -0,0 +1,334 @@ +/* + * Copyright 2011 Kano + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +/* Compile: + * gcc api-example.c -Icompat/jansson-2.6/src -Icompat/libusb-1.0/libusb -o bmminer-api + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include "miner.h" + +#if defined(unix) + #include + #include + #include + #include + #include + + #define SOCKETFAIL(a) ((a) < 0) + #define INVSOCK -1 + #define CLOSESOCKET close + + #define SOCKETINIT {} + + #define SOCKERRMSG strerror(errno) +#endif + +#ifdef WIN32 + #include + + #define SOCKETTYPE SOCKET + #define SOCKETFAIL(a) ((a) == SOCKET_ERROR) + #define INVSOCK INVALID_SOCKET + #define CLOSESOCKET closesocket + + static char WSAbuf[1024]; + + struct WSAERRORS { + int id; + char *code; + } WSAErrors[] = { + { 0, "No error" }, + { WSAEINTR, "Interrupted system call" }, + { WSAEBADF, "Bad file number" }, + { WSAEACCES, "Permission denied" }, + { WSAEFAULT, "Bad address" }, + { WSAEINVAL, "Invalid argument" }, + { WSAEMFILE, "Too many open sockets" }, + { WSAEWOULDBLOCK, "Operation would block" }, + { WSAEINPROGRESS, "Operation now in progress" }, + { WSAEALREADY, "Operation already in progress" }, + { WSAENOTSOCK, "Socket operation on non-socket" }, + { WSAEDESTADDRREQ, "Destination address required" }, + { WSAEMSGSIZE, "Message too long" }, + { WSAEPROTOTYPE, "Protocol wrong type for socket" }, + { WSAENOPROTOOPT, "Bad protocol option" }, + { WSAEPROTONOSUPPORT, "Protocol not supported" }, + { WSAESOCKTNOSUPPORT, "Socket type not supported" }, + { WSAEOPNOTSUPP, "Operation not supported on socket" }, + { WSAEPFNOSUPPORT, "Protocol family not supported" }, + { WSAEAFNOSUPPORT, "Address family not supported" }, + { WSAEADDRINUSE, "Address already in use" }, + { WSAEADDRNOTAVAIL, "Can't assign requested address" }, + { WSAENETDOWN, "Network is down" }, + { WSAENETUNREACH, "Network is unreachable" }, + { WSAENETRESET, "Net connection reset" }, + { WSAECONNABORTED, "Software caused connection abort" }, + { WSAECONNRESET, "Connection reset by peer" }, + { WSAENOBUFS, "No buffer space available" }, + { WSAEISCONN, "Socket is already connected" }, + { WSAENOTCONN, "Socket is not connected" }, + { WSAESHUTDOWN, "Can't send after socket shutdown" }, + { WSAETOOMANYREFS, "Too many references, can't splice" }, + { WSAETIMEDOUT, "Connection timed out" }, + { WSAECONNREFUSED, "Connection refused" }, + { WSAELOOP, "Too many levels of symbolic links" }, + { WSAENAMETOOLONG, "File name too long" }, + { WSAEHOSTDOWN, "Host is down" }, + { WSAEHOSTUNREACH, "No route to host" }, + { WSAENOTEMPTY, "Directory not empty" }, + { WSAEPROCLIM, "Too many processes" }, + { WSAEUSERS, "Too many users" }, + { WSAEDQUOT, "Disc quota exceeded" }, + { WSAESTALE, "Stale NFS file handle" }, + { WSAEREMOTE, "Too many levels of remote in path" }, + { WSASYSNOTREADY, "Network system is unavailable" }, + { WSAVERNOTSUPPORTED, "Winsock version out of range" }, + { WSANOTINITIALISED, "WSAStartup not yet called" }, + { WSAEDISCON, "Graceful shutdown in progress" }, + { WSAHOST_NOT_FOUND, "Host not found" }, + { WSANO_DATA, "No host data of that type was found" }, + { -1, "Unknown error code" } + }; + + static char *WSAErrorMsg() + { + char *msg; + int i; + int id = WSAGetLastError(); + + /* Assume none of them are actually -1 */ + for (i = 0; WSAErrors[i].id != -1; i++) + if (WSAErrors[i].id == id) + break; + + sprintf(WSAbuf, "Socket Error: (%d) %s", id, WSAErrors[i].code); + + return &(WSAbuf[0]); + } + + #define SOCKERRMSG WSAErrorMsg() + + static WSADATA WSA_Data; + + #define SOCKETINIT int wsa; \ + if (wsa = WSAStartup(0x0202, &WSA_Data)) { \ + printf("Socket startup failed: %d\n", wsa); \ + return 1; \ + } + + #ifndef SHUT_RDWR + #define SHUT_RDWR SD_BOTH + #endif +#endif + +static const char SEPARATOR = '|'; +static const char COMMA = ','; +static const char EQ = '='; +static int ONLY; + +void display(char *buf) +{ + char *nextobj, *item, *nextitem, *eq; + int itemcount; + + while (buf != NULL) { + nextobj = strchr(buf, SEPARATOR); + if (nextobj != NULL) + *(nextobj++) = '\0'; + + if (*buf) { + item = buf; + itemcount = 0; + while (item != NULL) { + nextitem = strchr(item, COMMA); + if (nextitem != NULL) + *(nextitem++) = '\0'; + + if (*item) { + eq = strchr(item, EQ); + if (eq != NULL) + *(eq++) = '\0'; + + if (itemcount == 0) + printf("[%s%s] =>\n(\n", item, (eq != NULL && isdigit(*eq)) ? eq : ""); + + if (eq != NULL) + printf(" [%s] => %s\n", item, eq); + else + printf(" [%d] => %s\n", itemcount, item); + } + + item = nextitem; + itemcount++; + } + if (itemcount > 0) + puts(")"); + } + + buf = nextobj; + } +} + +#define SOCKSIZ 65535 + +int callapi(char *command, char *host, short int port) +{ + struct hostent *ip; + struct sockaddr_in serv; + SOCKETTYPE sock; + int ret = 0; + int n; + char *buf = NULL; + size_t len, p; + + SOCKETINIT; + + ip = gethostbyname(host); + if (!ip) { + printf("Couldn't get hostname: '%s'\n", host); + return 1; + } + + sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock == INVSOCK) { + printf("Socket initialisation failed: %s\n", SOCKERRMSG); + return 1; + } + + memset(&serv, 0, sizeof(serv)); + serv.sin_family = AF_INET; + serv.sin_addr = *((struct in_addr *)ip->h_addr); + serv.sin_port = htons(port); + + if (SOCKETFAIL(connect(sock, (struct sockaddr *)&serv, sizeof(struct sockaddr)))) { + printf("Socket connect failed: %s\n", SOCKERRMSG); + return 1; + } + + n = send(sock, command, strlen(command), 0); + if (SOCKETFAIL(n)) { + printf("Send failed: %s\n", SOCKERRMSG); + ret = 1; + } + else { + len = SOCKSIZ; + buf = malloc(len+1); + if (!buf) { + printf("Err: OOM (%d)\n", (int)(len+1)); + return 1; + } + p = 0; + while (42) { + if ((len - p) < 1) { + len += SOCKSIZ; + buf = realloc(buf, len+1); + if (!buf) { + printf("Err: OOM (%d)\n", (int)(len+1)); + return 1; + } + } + + n = recv(sock, &buf[p], len - p , 0); + + if (SOCKETFAIL(n)) { + printf("Recv failed: %s\n", SOCKERRMSG); + ret = 1; + break; + } + + if (n == 0) + break; + + p += n; + } + buf[p] = '\0'; + + if (ONLY) + printf("%s\n", buf); + else { + printf("Reply was '%s'\n", buf); + display(buf); + } + } + + CLOSESOCKET(sock); + + return ret; +} + +static char *trim(char *str) +{ + char *ptr; + + while (isspace(*str)) + str++; + + ptr = strchr(str, '\0'); + while (ptr-- > str) { + if (isspace(*ptr)) + *ptr = '\0'; + } + + return str; +} + +int main(int argc, char *argv[]) +{ + char *command = "summary"; + char *host = "127.0.0.1"; + short int port = 4028; + char *ptr; + int i = 1; + + if (argc > 1) + if (strcmp(argv[1], "-?") == 0 + || strcmp(argv[1], "-h") == 0 + || strcmp(argv[1], "--help") == 0) { + fprintf(stderr, "usAge: %s [command [ip/host [port]]]\n", argv[0]); + return 1; + } + + if (argc > 1) + if (strcmp(argv[1], "-o") == 0) { + ONLY = 1; + i = 2; + } + + if (argc > i) { + ptr = trim(argv[i++]); + if (strlen(ptr) > 0) + command = ptr; + } + + if (argc > i) { + ptr = trim(argv[i++]); + if (strlen(ptr) > 0) + host = ptr; + } + + if (argc > i) { + ptr = trim(argv[i]); + if (strlen(ptr) > 0) + port = atoi(ptr); + } + + return callapi(command, host, port); +} diff --git a/api-example.php b/api-example.php new file mode 100644 index 0000000..6ce2a21 --- /dev/null +++ b/api-example.php @@ -0,0 +1,116 @@ + 0) + { + $items = explode(',', $obj); + $item = $items[0]; + $id = explode('=', $items[0], 2); + if (count($id) == 1 or !ctype_digit($id[1])) + $name = $id[0]; + else + $name = $id[0].$id[1]; + + if (strlen($name) == 0) + $name = 'null'; + + if (isset($data[$name])) + { + $num = 1; + while (isset($data[$name.$num])) + $num++; + $name .= $num; + } + + $counter = 0; + foreach ($items as $item) + { + $id = explode('=', $item, 2); + if (count($id) == 2) + $data[$name][$id[0]] = $id[1]; + else + $data[$name][$counter] = $id[0]; + + $counter++; + } + } + } + + return $data; + } + + return null; +} +# +if (isset($argv) and count($argv) > 1) + $r = request($argv[1]); +else + $r = request('summary'); +# +echo print_r($r, true)."\n"; +# +?> diff --git a/api-example.py b/api-example.py new file mode 100644 index 0000000..5b8cbb2 --- /dev/null +++ b/api-example.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python2.7 + +# Copyright 2013 Setkeh Mkfr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any later +# version. See COPYING for more details. + +#Short Python Example for connecting to The Cgminer API +#Written By: setkeh +#Thanks to Jezzz for all his Support. +#NOTE: When adding a param with a pipe | in bash or ZSH you must wrap the arg in quotes +#E.G "pga|0" + +import socket +import json +import sys + +def linesplit(socket): + buffer = socket.recv(4096) + done = False + while not done: + more = socket.recv(4096) + if not more: + done = True + else: + buffer = buffer+more + if buffer: + return buffer + +api_command = sys.argv[1].split('|') + +if len(sys.argv) < 3: + api_ip = '127.0.0.1' + api_port = 4028 +else: + api_ip = sys.argv[2] + api_port = sys.argv[3] + +s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) +s.connect((api_ip,int(api_port))) +if len(api_command) == 2: + s.send(json.dumps({"command":api_command[0],"parameter":api_command[1]})) +else: + s.send(json.dumps({"command":api_command[0]})) + +response = linesplit(s) +response = response.replace('\x00','') +response = json.loads(response) +print response +s.close() diff --git a/api-example.rb b/api-example.rb new file mode 100644 index 0000000..28c4402 --- /dev/null +++ b/api-example.rb @@ -0,0 +1,38 @@ +#!/usr/bin/env ruby + +# Copyright 2014 James Hilliard +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any later +# version. See COPYING for more details. + +require 'socket' +require 'json' + +api_command = ARGV[0].split(":") + +if ARGV.length == 3 + api_ip = ARGV[1] + api_port = ARGV[2] +elsif ARGV.length == 2 + api_ip = ARGV[1] + api_port = 4028 +else + api_ip = "127.0.0.1" + api_port = 4028 +end + +s = TCPSocket.open(api_ip, api_port) + +if api_command.count == 2 + s.write({ :command => api_command[0], :parameter => api_command[1]}.to_json) +else + s.write({ :command => api_command[0]}.to_json) +end + +response = s.read.strip +response = JSON.parse(response) + +puts response +s.close diff --git a/api.c b/api.c new file mode 100644 index 0000000..c046ba6 --- /dev/null +++ b/api.c @@ -0,0 +1,5256 @@ +/* + * Copyright 2011-2014 Andrew Smith + * Copyright 2011-2014 Con Kolivas + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ +#define _MEMORY_DEBUG_MASTER 1 + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include "miner.h" +#include "util.h" +#include "klist.h" + +#if defined(USE_BFLSC) || defined(USE_AVALON) || defined(USE_AVALON2) || defined(USE_AVALON4) || \ + defined(USE_HASHFAST) || defined(USE_BITFURY) || defined(USE_BLOCKERUPTER) || defined(USE_KLONDIKE) || \ + defined(USE_KNC) || defined(USE_BAB) || defined(USE_DRILLBIT) || \ + defined(USE_MINION) || defined(USE_COINTERRA) || defined(USE_BITMINE_A1) || \ + defined(USE_BMSC) || defined(USE_BITMAIN) || defined(USE_SP10) || defined(USE_SP30) || \ + defined(USE_ICARUS) || defined(USE_HASHRATIO) +#define HAVE_AN_ASIC 1 +#endif + +#if defined(USE_BITFORCE) || defined(USE_MODMINER) +#define HAVE_AN_FPGA 1 +#endif + +// BUFSIZ varies on Windows and Linux +#define TMPBUFSIZ 8192 + +// Number of requests to queue - normally would be small +// However lots of PGA's may mean more +#define QUEUE 100 + +#if defined WIN32 +static char WSAbuf[1024]; + +struct WSAERRORS { + int id; + char *code; +} WSAErrors[] = { + { 0, "No error" }, + { WSAEINTR, "Interrupted system call" }, + { WSAEBADF, "Bad file number" }, + { WSAEACCES, "Permission denied" }, + { WSAEFAULT, "Bad address" }, + { WSAEINVAL, "Invalid argument" }, + { WSAEMFILE, "Too many open sockets" }, + { WSAEWOULDBLOCK, "Operation would block" }, + { WSAEINPROGRESS, "Operation now in progress" }, + { WSAEALREADY, "Operation already in progress" }, + { WSAENOTSOCK, "Socket operation on non-socket" }, + { WSAEDESTADDRREQ, "Destination address required" }, + { WSAEMSGSIZE, "Message too long" }, + { WSAEPROTOTYPE, "Protocol wrong type for socket" }, + { WSAENOPROTOOPT, "Bad protocol option" }, + { WSAEPROTONOSUPPORT, "Protocol not supported" }, + { WSAESOCKTNOSUPPORT, "Socket type not supported" }, + { WSAEOPNOTSUPP, "Operation not supported on socket" }, + { WSAEPFNOSUPPORT, "Protocol family not supported" }, + { WSAEAFNOSUPPORT, "Address family not supported" }, + { WSAEADDRINUSE, "Address already in use" }, + { WSAEADDRNOTAVAIL, "Can't assign requested address" }, + { WSAENETDOWN, "Network is down" }, + { WSAENETUNREACH, "Network is unreachable" }, + { WSAENETRESET, "Net connection reset" }, + { WSAECONNABORTED, "Software caused connection abort" }, + { WSAECONNRESET, "Connection reset by peer" }, + { WSAENOBUFS, "No buffer space available" }, + { WSAEISCONN, "Socket is already connected" }, + { WSAENOTCONN, "Socket is not connected" }, + { WSAESHUTDOWN, "Can't send after socket shutdown" }, + { WSAETOOMANYREFS, "Too many references, can't splice" }, + { WSAETIMEDOUT, "Connection timed out" }, + { WSAECONNREFUSED, "Connection refused" }, + { WSAELOOP, "Too many levels of symbolic links" }, + { WSAENAMETOOLONG, "File name too long" }, + { WSAEHOSTDOWN, "Host is down" }, + { WSAEHOSTUNREACH, "No route to host" }, + { WSAENOTEMPTY, "Directory not empty" }, + { WSAEPROCLIM, "Too many processes" }, + { WSAEUSERS, "Too many users" }, + { WSAEDQUOT, "Disc quota exceeded" }, + { WSAESTALE, "Stale NFS file handle" }, + { WSAEREMOTE, "Too many levels of remote in path" }, + { WSASYSNOTREADY, "Network system is unavailable" }, + { WSAVERNOTSUPPORTED, "Winsock version out of range" }, + { WSANOTINITIALISED, "WSAStartup not yet called" }, + { WSAEDISCON, "Graceful shutdown in progress" }, + { WSAHOST_NOT_FOUND, "Host not found" }, + { WSANO_DATA, "No host data of that type was found" }, + { -1, "Unknown error code" } +}; + +char *WSAErrorMsg(void) { + int i; + int id = WSAGetLastError(); + + /* Assume none of them are actually -1 */ + for (i = 0; WSAErrors[i].id != -1; i++) + if (WSAErrors[i].id == id) + break; + + sprintf(WSAbuf, "Socket Error: (%d) %s", id, WSAErrors[i].code); + + return &(WSAbuf[0]); +} +#endif + +#if defined(__APPLE__) +#define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP +#define IPV6_DROP_MEMBERSHIP IPV6_LEAVE_GROUP +#endif + +static const char *UNAVAILABLE = " - API will not be available"; +static const char *MUNAVAILABLE = " - API multicast listener will not be available"; + +static const char *BLANK = ""; +static const char *COMMA = ","; +#define COMSTR "," +static const char SEPARATOR = '|'; +#define SEPSTR "|" +#define CMDJOIN '+' +#define JOIN_CMD "CMD=" +#define BETWEEN_JOIN SEPSTR + +static const char *APIVERSION = "3.1"; +static const char *DEAD = "Dead"; +static const char *SICK = "Sick"; +static const char *NOSTART = "NoStart"; +static const char *INIT = "Initialising"; +static const char *DISABLED = "Disabled"; +static const char *ALIVE = "Alive"; +static const char *REJECTING = "Rejecting"; +static const char *UNKNOWN = "Unknown"; + +static __maybe_unused const char *NONE = "None"; + +static const char *YES = "Y"; +static const char *NO = "N"; +static const char *NULLSTR = "(null)"; + +static const char *TRUESTR = "true"; +static const char *FALSESTR = "false"; + +static const char *SHA256STR = "sha256"; + +static const char *DEVICECODE = "" +#ifdef USE_BMSC + "BTM " +#endif +#ifdef USE_BITMAIN + "BTM " +#endif +#ifdef USE_AVALON + "AVA " +#endif +#ifdef USE_BAB + "BaB " +#endif +#ifdef USE_BFLSC + "BAS " +#endif +#ifdef USE_BITFORCE + "BFL " +#endif +#ifdef USE_BITFURY + "BFU " +#endif +#ifdef USE_BLOCKERUPTER + "BET " +#endif +#ifdef USE_DRILLBIT + "DRB " +#endif +#ifdef USE_HASHFAST + "HFA " +#endif +#ifdef USE_HASHRATIO + "HRO " +#endif +#ifdef USE_BITMINE_A1 + "BA1 " +#endif +#ifdef USE_ICARUS + "ICA " +#endif +#ifdef USE_KNC + "KnC " +#endif +#ifdef USE_MINION + "MBA " +#endif +#ifdef USE_MODMINER + "MMQ " +#endif +#ifdef USE_COINTERRA + "CTA " +#endif +#ifdef USE_SP10 + "SPN " +#endif +#ifdef USE_SP30 + "S30 " +#endif + + + ""; + +static const char *OSINFO = +#if defined(__linux) + "Linux"; +#else +#if defined(__APPLE__) + "Apple"; +#else +#if defined (WIN32) + "Windows"; +#else +#if defined(unix) + "Unix"; +#else + "Unknown"; +#endif +#endif +#endif +#endif + +#define _DEVS "DEVS" +#define _POOLS "POOLS" +#define _SUMMARY "SUMMARY" +#define _STATUS "STATUS" +#define _VERSION "VERSION" +#define _MINECONFIG "CONFIG" + +#ifdef HAVE_AN_FPGA +#define _PGA "PGA" +#endif + +#ifdef HAVE_AN_ASIC +#define _ASC "ASC" +#endif + +#define _PGAS "PGAS" +#define _ASCS "ASCS" +#define _NOTIFY "NOTIFY" +#define _DEVDETAILS "DEVDETAILS" +#define _BYE "BYE" +#define _RESTART "RESTART" +#define _MINESTATS "STATS" +#define _CHECK "CHECK" +#define _MINECOIN "COIN" +#define _DEBUGSET "DEBUG" +#define _SETCONFIG "SETCONFIG" +#define _USBSTATS "USBSTATS" +#define _LCD "LCD" + +static const char ISJSON = '{'; +#define JSON0 "{" +#define JSON1 "\"" +#define JSON2 "\":[" +#define JSON3 "]" +#define JSON4 ",\"id\":1" +// If anyone cares, id=0 for truncated output +#define JSON4_TRUNCATED ",\"id\":0" +#define JSON5 "}" +#define JSON6 "\":" + +#define JSON_START JSON0 +#define JSON_DEVS JSON1 _DEVS JSON2 +#define JSON_POOLS JSON1 _POOLS JSON2 +#define JSON_SUMMARY JSON1 _SUMMARY JSON2 +#define JSON_STATUS JSON1 _STATUS JSON2 +#define JSON_VERSION JSON1 _VERSION JSON2 +#define JSON_MINECONFIG JSON1 _MINECONFIG JSON2 +#define JSON_ACTION JSON0 JSON1 _STATUS JSON6 + +#ifdef HAVE_AN_FPGA +#define JSON_PGA JSON1 _PGA JSON2 +#endif + +#ifdef HAVE_AN_ASIC +#define JSON_ASC JSON1 _ASC JSON2 +#endif + +#define JSON_PGAS JSON1 _PGAS JSON2 +#define JSON_ASCS JSON1 _ASCS JSON2 +#define JSON_NOTIFY JSON1 _NOTIFY JSON2 +#define JSON_DEVDETAILS JSON1 _DEVDETAILS JSON2 +#define JSON_BYE JSON1 _BYE JSON1 +#define JSON_RESTART JSON1 _RESTART JSON1 +#define JSON_CLOSE JSON3 +#define JSON_MINESTATS JSON1 _MINESTATS JSON2 +#define JSON_CHECK JSON1 _CHECK JSON2 +#define JSON_MINECOIN JSON1 _MINECOIN JSON2 +#define JSON_DEBUGSET JSON1 _DEBUGSET JSON2 +#define JSON_SETCONFIG JSON1 _SETCONFIG JSON2 +#define JSON_USBSTATS JSON1 _USBSTATS JSON2 +#define JSON_LCD JSON1 _LCD JSON2 +#define JSON_END JSON4 JSON5 +#define JSON_END_TRUNCATED JSON4_TRUNCATED JSON5 +#define JSON_BETWEEN_JOIN "," + +static const char *JSON_COMMAND = "command"; +static const char *JSON_PARAMETER = "parameter"; + +#define MSG_POOL 7 +#define MSG_NOPOOL 8 +#define MSG_DEVS 9 +#define MSG_NODEVS 10 +#define MSG_SUMM 11 +#define MSG_INVCMD 14 +#define MSG_MISID 15 + +#define MSG_VERSION 22 +#define MSG_INVJSON 23 +#define MSG_MISCMD 24 +#define MSG_MISPID 25 +#define MSG_INVPID 26 +#define MSG_SWITCHP 27 +#define MSG_MISVAL 28 +#define MSG_NOADL 29 +#define MSG_INVINT 31 +#define MSG_MINECONFIG 33 +#define MSG_MISFN 42 +#define MSG_BADFN 43 +#define MSG_SAVED 44 +#define MSG_ACCDENY 45 +#define MSG_ACCOK 46 +#define MSG_ENAPOOL 47 +#define MSG_DISPOOL 48 +#define MSG_ALRENAP 49 +#define MSG_ALRDISP 50 +#define MSG_DISLASTP 51 +#define MSG_MISPDP 52 +#define MSG_INVPDP 53 +#define MSG_TOOMANYP 54 +#define MSG_ADDPOOL 55 + +#ifdef HAVE_AN_FPGA +#define MSG_PGANON 56 +#define MSG_PGADEV 57 +#define MSG_INVPGA 58 +#endif + +#define MSG_NUMPGA 59 +#define MSG_NOTIFY 60 + +#ifdef HAVE_AN_FPGA +#define MSG_PGALRENA 61 +#define MSG_PGALRDIS 62 +#define MSG_PGAENA 63 +#define MSG_PGADIS 64 +#define MSG_PGAUNW 65 +#endif + +#define MSG_REMLASTP 66 +#define MSG_ACTPOOL 67 +#define MSG_REMPOOL 68 +#define MSG_DEVDETAILS 69 +#define MSG_MINESTATS 70 +#define MSG_MISCHK 71 +#define MSG_CHECK 72 +#define MSG_POOLPRIO 73 +#define MSG_DUPPID 74 +#define MSG_MISBOOL 75 +#define MSG_INVBOOL 76 +#define MSG_FOO 77 +#define MSG_MINECOIN 78 +#define MSG_DEBUGSET 79 +#define MSG_PGAIDENT 80 +#define MSG_PGANOID 81 +#define MSG_SETCONFIG 82 +#define MSG_UNKCON 83 +#define MSG_INVNUM 84 +#define MSG_CONPAR 85 +#define MSG_CONVAL 86 +#define MSG_USBSTA 87 +#define MSG_NOUSTA 88 + +#ifdef HAVE_AN_FPGA +#define MSG_MISPGAOPT 89 +#define MSG_PGANOSET 90 +#define MSG_PGAHELP 91 +#define MSG_PGASETOK 92 +#define MSG_PGASETERR 93 +#endif + +#define MSG_ZERMIS 94 +#define MSG_ZERINV 95 +#define MSG_ZERSUM 96 +#define MSG_ZERNOSUM 97 +#define MSG_PGAUSBNODEV 98 +#define MSG_INVHPLG 99 +#define MSG_HOTPLUG 100 +#define MSG_DISHPLG 101 +#define MSG_NOHPLG 102 +#define MSG_MISHPLG 103 + +#define MSG_NUMASC 104 +#ifdef HAVE_AN_ASIC +#define MSG_ASCNON 105 +#define MSG_ASCDEV 106 +#define MSG_INVASC 107 +#define MSG_ASCLRENA 108 +#define MSG_ASCLRDIS 109 +#define MSG_ASCENA 110 +#define MSG_ASCDIS 111 +#define MSG_ASCUNW 112 +#define MSG_ASCIDENT 113 +#define MSG_ASCNOID 114 +#endif +#define MSG_ASCUSBNODEV 115 + +#ifdef HAVE_AN_ASIC +#define MSG_MISASCOPT 116 +#define MSG_ASCNOSET 117 +#define MSG_ASCHELP 118 +#define MSG_ASCSETOK 119 +#define MSG_ASCSETERR 120 +#endif + +#define MSG_INVNEG 121 +#define MSG_SETQUOTA 122 +#define MSG_LOCKOK 123 +#define MSG_LOCKDIS 124 +#define MSG_LCD 125 + +enum code_severity { + SEVERITY_ERR, + SEVERITY_WARN, + SEVERITY_INFO, + SEVERITY_SUCC, + SEVERITY_FAIL +}; + +enum code_parameters { + PARAM_PGA, + PARAM_ASC, + PARAM_PID, + PARAM_PGAMAX, + PARAM_ASCMAX, + PARAM_PMAX, + PARAM_POOLMAX, + +// Single generic case: have the code resolve it - see below + PARAM_DMAX, + + PARAM_CMD, + PARAM_POOL, + PARAM_STR, + PARAM_BOTH, + PARAM_BOOL, + PARAM_SET, + PARAM_INT, + PARAM_NONE +}; + +struct CODES { + const enum code_severity severity; + const int code; + const enum code_parameters params; + const char *description; +} codes[] = { + { SEVERITY_SUCC, MSG_POOL, PARAM_PMAX, "%d Pool(s)" }, + { SEVERITY_ERR, MSG_NOPOOL, PARAM_NONE, "No pools" }, + + { SEVERITY_SUCC, MSG_DEVS, PARAM_DMAX, +#ifdef HAVE_AN_ASIC + "%d ASC(s)" +#endif +#if defined(HAVE_AN_ASIC) && defined(HAVE_AN_FPGA) + " - " +#endif +#ifdef HAVE_AN_FPGA + "%d PGA(s)" +#endif + }, + + { SEVERITY_ERR, MSG_NODEVS, PARAM_NONE, "No " +#ifdef HAVE_AN_ASIC + "ASCs" +#endif +#if defined(HAVE_AN_ASIC) && defined(HAVE_AN_FPGA) + "/" +#endif +#ifdef HAVE_AN_FPGA + "PGAs" +#endif + }, + + { SEVERITY_SUCC, MSG_SUMM, PARAM_NONE, "Summary" }, + { SEVERITY_ERR, MSG_INVCMD, PARAM_NONE, "Invalid command" }, + { SEVERITY_ERR, MSG_MISID, PARAM_NONE, "Missing device id parameter" }, +#ifdef HAVE_AN_FPGA + { SEVERITY_ERR, MSG_PGANON, PARAM_NONE, "No PGAs" }, + { SEVERITY_SUCC, MSG_PGADEV, PARAM_PGA, "PGA%d" }, + { SEVERITY_ERR, MSG_INVPGA, PARAM_PGAMAX, "Invalid PGA id %d - range is 0 - %d" }, + { SEVERITY_INFO, MSG_PGALRENA,PARAM_PGA, "PGA %d already enabled" }, + { SEVERITY_INFO, MSG_PGALRDIS,PARAM_PGA, "PGA %d already disabled" }, + { SEVERITY_INFO, MSG_PGAENA, PARAM_PGA, "PGA %d sent enable message" }, + { SEVERITY_INFO, MSG_PGADIS, PARAM_PGA, "PGA %d set disable flag" }, + { SEVERITY_ERR, MSG_PGAUNW, PARAM_PGA, "PGA %d is not flagged WELL, cannot enable" }, +#endif + { SEVERITY_SUCC, MSG_NUMPGA, PARAM_NONE, "PGA count" }, + { SEVERITY_SUCC, MSG_NUMASC, PARAM_NONE, "ASC count" }, + { SEVERITY_SUCC, MSG_VERSION, PARAM_NONE, "BMMiner versions" }, + { SEVERITY_ERR, MSG_INVJSON, PARAM_NONE, "Invalid JSON" }, + { SEVERITY_ERR, MSG_MISCMD, PARAM_CMD, "Missing JSON '%s'" }, + { SEVERITY_ERR, MSG_MISPID, PARAM_NONE, "Missing pool id parameter" }, + { SEVERITY_ERR, MSG_INVPID, PARAM_POOLMAX, "Invalid pool id %d - range is 0 - %d" }, + { SEVERITY_SUCC, MSG_SWITCHP, PARAM_POOL, "Switching to pool %d:'%s'" }, + { SEVERITY_SUCC, MSG_MINECONFIG,PARAM_NONE, "BMMiner config" }, + { SEVERITY_ERR, MSG_MISFN, PARAM_NONE, "Missing save filename parameter" }, + { SEVERITY_ERR, MSG_BADFN, PARAM_STR, "Can't open or create save file '%s'" }, + { SEVERITY_SUCC, MSG_SAVED, PARAM_STR, "Configuration saved to file '%s'" }, + { SEVERITY_ERR, MSG_ACCDENY, PARAM_STR, "Access denied to '%s' command" }, + { SEVERITY_SUCC, MSG_ACCOK, PARAM_NONE, "Privileged access OK" }, + { SEVERITY_SUCC, MSG_ENAPOOL, PARAM_POOL, "Enabling pool %d:'%s'" }, + { SEVERITY_SUCC, MSG_POOLPRIO,PARAM_NONE, "Changed pool priorities" }, + { SEVERITY_ERR, MSG_DUPPID, PARAM_PID, "Duplicate pool specified %d" }, + { SEVERITY_SUCC, MSG_DISPOOL, PARAM_POOL, "Disabling pool %d:'%s'" }, + { SEVERITY_INFO, MSG_ALRENAP, PARAM_POOL, "Pool %d:'%s' already enabled" }, + { SEVERITY_INFO, MSG_ALRDISP, PARAM_POOL, "Pool %d:'%s' already disabled" }, + { SEVERITY_ERR, MSG_DISLASTP,PARAM_POOL, "Cannot disable last active pool %d:'%s'" }, + { SEVERITY_ERR, MSG_MISPDP, PARAM_NONE, "Missing addpool details" }, + { SEVERITY_ERR, MSG_INVPDP, PARAM_STR, "Invalid addpool details '%s'" }, + { SEVERITY_ERR, MSG_TOOMANYP,PARAM_NONE, "Reached maximum number of pools (%d)" }, + { SEVERITY_SUCC, MSG_ADDPOOL, PARAM_POOL, "Added pool %d: '%s'" }, + { SEVERITY_ERR, MSG_REMLASTP,PARAM_POOL, "Cannot remove last pool %d:'%s'" }, + { SEVERITY_ERR, MSG_ACTPOOL, PARAM_POOL, "Cannot remove active pool %d:'%s'" }, + { SEVERITY_SUCC, MSG_REMPOOL, PARAM_BOTH, "Removed pool %d:'%s'" }, + { SEVERITY_SUCC, MSG_NOTIFY, PARAM_NONE, "Notify" }, + { SEVERITY_SUCC, MSG_DEVDETAILS,PARAM_NONE, "Device Details" }, + { SEVERITY_SUCC, MSG_MINESTATS,PARAM_NONE, "BMMiner stats" }, + { SEVERITY_ERR, MSG_MISCHK, PARAM_NONE, "Missing check cmd" }, + { SEVERITY_SUCC, MSG_CHECK, PARAM_NONE, "Check command" }, + { SEVERITY_ERR, MSG_MISBOOL, PARAM_NONE, "Missing parameter: true/false" }, + { SEVERITY_ERR, MSG_INVBOOL, PARAM_NONE, "Invalid parameter should be true or false" }, + { SEVERITY_SUCC, MSG_FOO, PARAM_BOOL, "Failover-Only set to %s" }, + { SEVERITY_SUCC, MSG_MINECOIN,PARAM_NONE, "BMMiner coin" }, + { SEVERITY_SUCC, MSG_DEBUGSET,PARAM_NONE, "Debug settings" }, +#ifdef HAVE_AN_FPGA + { SEVERITY_SUCC, MSG_PGAIDENT,PARAM_PGA, "Identify command sent to PGA%d" }, + { SEVERITY_WARN, MSG_PGANOID, PARAM_PGA, "PGA%d does not support identify" }, +#endif + { SEVERITY_SUCC, MSG_SETCONFIG,PARAM_SET, "Set config '%s' to %d" }, + { SEVERITY_ERR, MSG_UNKCON, PARAM_STR, "Unknown config '%s'" }, + { SEVERITY_ERR, MSG_INVNUM, PARAM_BOTH, "Invalid number (%d) for '%s' range is 0-9999" }, + { SEVERITY_ERR, MSG_INVNEG, PARAM_BOTH, "Invalid negative number (%d) for '%s'" }, + { SEVERITY_SUCC, MSG_SETQUOTA,PARAM_SET, "Set pool '%s' to quota %d'" }, + { SEVERITY_ERR, MSG_CONPAR, PARAM_NONE, "Missing config parameters 'name,N'" }, + { SEVERITY_ERR, MSG_CONVAL, PARAM_STR, "Missing config value N for '%s,N'" }, + { SEVERITY_SUCC, MSG_USBSTA, PARAM_NONE, "USB Statistics" }, + { SEVERITY_INFO, MSG_NOUSTA, PARAM_NONE, "No USB Statistics" }, +#ifdef HAVE_AN_FPGA + { SEVERITY_ERR, MSG_MISPGAOPT, PARAM_NONE, "Missing option after PGA number" }, + { SEVERITY_WARN, MSG_PGANOSET, PARAM_PGA, "PGA %d does not support pgaset" }, + { SEVERITY_INFO, MSG_PGAHELP, PARAM_BOTH, "PGA %d set help: %s" }, + { SEVERITY_SUCC, MSG_PGASETOK, PARAM_BOTH, "PGA %d set OK" }, + { SEVERITY_ERR, MSG_PGASETERR, PARAM_BOTH, "PGA %d set failed: %s" }, +#endif + { SEVERITY_ERR, MSG_ZERMIS, PARAM_NONE, "Missing zero parameters" }, + { SEVERITY_ERR, MSG_ZERINV, PARAM_STR, "Invalid zero parameter '%s'" }, + { SEVERITY_SUCC, MSG_ZERSUM, PARAM_STR, "Zeroed %s stats with summary" }, + { SEVERITY_SUCC, MSG_ZERNOSUM, PARAM_STR, "Zeroed %s stats without summary" }, +#ifdef USE_USBUTILS + { SEVERITY_ERR, MSG_PGAUSBNODEV, PARAM_PGA, "PGA%d has no device" }, + { SEVERITY_ERR, MSG_ASCUSBNODEV, PARAM_PGA, "ASC%d has no device" }, +#endif + { SEVERITY_ERR, MSG_INVHPLG, PARAM_STR, "Invalid value for hotplug (%s) must be 0..9999" }, + { SEVERITY_SUCC, MSG_HOTPLUG, PARAM_INT, "Hotplug check set to %ds" }, + { SEVERITY_SUCC, MSG_DISHPLG, PARAM_NONE, "Hotplug disabled" }, + { SEVERITY_WARN, MSG_NOHPLG, PARAM_NONE, "Hotplug is not available" }, + { SEVERITY_ERR, MSG_MISHPLG, PARAM_NONE, "Missing hotplug parameter" }, +#ifdef HAVE_AN_ASIC + { SEVERITY_ERR, MSG_ASCNON, PARAM_NONE, "No ASCs" }, + { SEVERITY_SUCC, MSG_ASCDEV, PARAM_ASC, "ASC%d" }, + { SEVERITY_ERR, MSG_INVASC, PARAM_ASCMAX, "Invalid ASC id %d - range is 0 - %d" }, + { SEVERITY_INFO, MSG_ASCLRENA,PARAM_ASC, "ASC %d already enabled" }, + { SEVERITY_INFO, MSG_ASCLRDIS,PARAM_ASC, "ASC %d already disabled" }, + { SEVERITY_INFO, MSG_ASCENA, PARAM_ASC, "ASC %d sent enable message" }, + { SEVERITY_INFO, MSG_ASCDIS, PARAM_ASC, "ASC %d set disable flag" }, + { SEVERITY_ERR, MSG_ASCUNW, PARAM_ASC, "ASC %d is not flagged WELL, cannot enable" }, + { SEVERITY_SUCC, MSG_ASCIDENT,PARAM_ASC, "Identify command sent to ASC%d" }, + { SEVERITY_WARN, MSG_ASCNOID, PARAM_ASC, "ASC%d does not support identify" }, + { SEVERITY_ERR, MSG_MISASCOPT, PARAM_NONE, "Missing option after ASC number" }, + { SEVERITY_WARN, MSG_ASCNOSET, PARAM_ASC, "ASC %d does not support ascset" }, + { SEVERITY_INFO, MSG_ASCHELP, PARAM_BOTH, "ASC %d set help: %s" }, + { SEVERITY_SUCC, MSG_ASCSETOK, PARAM_BOTH, "ASC %d set OK" }, + { SEVERITY_ERR, MSG_ASCSETERR, PARAM_BOTH, "ASC %d set failed: %s" }, +#endif + { SEVERITY_SUCC, MSG_LCD, PARAM_NONE, "LCD" }, + { SEVERITY_SUCC, MSG_LOCKOK, PARAM_NONE, "Lock stats created" }, + { SEVERITY_WARN, MSG_LOCKDIS, PARAM_NONE, "Lock stats not enabled" }, + { SEVERITY_FAIL, 0, 0, NULL } +}; + +static const char *localaddr = "127.0.0.1"; + +static int my_thr_id = 0; +static bool bye; + +// Used to control quit restart access to shutdown variables +static pthread_mutex_t quit_restart_lock; + +static bool do_a_quit; +static bool do_a_restart; + +static time_t when = 0; // when the request occurred + +struct IPACCESS { + struct in6_addr ip; + struct in6_addr mask; + char group; +}; + +#define GROUP(g) (toupper(g)) +#define PRIVGROUP GROUP('W') +#define NOPRIVGROUP GROUP('R') +#define ISPRIVGROUP(g) (GROUP(g) == PRIVGROUP) +#define GROUPOFFSET(g) (GROUP(g) - GROUP('A')) +#define VALIDGROUP(g) (GROUP(g) >= GROUP('A') && GROUP(g) <= GROUP('Z')) +#define COMMANDS(g) (apigroups[GROUPOFFSET(g)].commands) +#define DEFINEDGROUP(g) (ISPRIVGROUP(g) || COMMANDS(g) != NULL) + +struct APIGROUPS { + // This becomes a string like: "|cmd1|cmd2|cmd3|" so it's quick to search + char *commands; +} apigroups['Z' - 'A' + 1]; // only A=0 to Z=25 (R: noprivs, W: allprivs) + +static struct IPACCESS *ipaccess = NULL; +static int ips = 0; + +struct io_data { + size_t siz; + char *ptr; + char *cur; + bool sock; + bool close; +}; + +struct io_list { + struct io_data *io_data; + struct io_list *prev; + struct io_list *next; +}; + +static struct io_list *io_head = NULL; + +#define SOCKBUFALLOCSIZ 65536 + +#define io_new(init) _io_new(init, false) +#define sock_io_new() _io_new(SOCKBUFALLOCSIZ, true) + +#define ALLOC_SBITEMS 2 +#define LIMIT_SBITEMS 0 + +typedef struct sbitem { + char *buf; + size_t siz; + size_t tot; +} SBITEM; + +// Size to grow tot if exceeded +#define SBEXTEND 4096 + +#define DATASB(_item) ((SBITEM *)(_item->data)) + +static K_LIST *strbufs; + +static void io_reinit(struct io_data *io_data) +{ + io_data->cur = io_data->ptr; + *(io_data->ptr) = '\0'; + io_data->close = false; +} + +static struct io_data *_io_new(size_t initial, bool socket_buf) +{ + struct io_data *io_data; + struct io_list *io_list; + + io_data = malloc(sizeof(*io_data)); + io_data->ptr = malloc(initial); + io_data->siz = initial; + io_data->sock = socket_buf; + io_reinit(io_data); + + io_list = malloc(sizeof(*io_list)); + + io_list->io_data = io_data; + + if (io_head) { + io_list->next = io_head; + io_list->prev = io_head->prev; + io_list->next->prev = io_list; + io_list->prev->next = io_list; + } else { + io_list->prev = io_list; + io_list->next = io_list; + io_head = io_list; + } + + return io_data; +} + +static bool io_add(struct io_data *io_data, char *buf) +{ + size_t len, dif, tot; + + len = strlen(buf); + dif = io_data->cur - io_data->ptr; + // send will always have enough space to add the JSON + tot = len + 1 + dif + sizeof(JSON_CLOSE) + sizeof(JSON_END); + + if (tot > io_data->siz) { + size_t new = io_data->siz + (2 * SOCKBUFALLOCSIZ); + + if (new < tot) + new = (2 + (size_t)((float)tot / (float)SOCKBUFALLOCSIZ)) * SOCKBUFALLOCSIZ; + + io_data->ptr = realloc(io_data->ptr, new); + io_data->cur = io_data->ptr + dif; + io_data->siz = new; + } + + memcpy(io_data->cur, buf, len + 1); + io_data->cur += len; + + return true; +} + +static bool io_put(struct io_data *io_data, char *buf) +{ + io_reinit(io_data); + return io_add(io_data, buf); +} + +static void io_close(struct io_data *io_data) +{ + io_data->close = true; +} + +static void io_free() +{ + struct io_list *io_list, *io_next; + + if (io_head) { + io_list = io_head; + do { + io_next = io_list->next; + + free(io_list->io_data->ptr); + free(io_list->io_data); + free(io_list); + + io_list = io_next; + } while (io_list != io_head); + + io_head = NULL; + } +} + +// This is only called when expected to be needed (rarely) +// i.e. strings outside of the codes control (input from the user) +static char *escape_string(char *str, bool isjson) +{ + char *buf, *ptr; + int count; + + count = 0; + for (ptr = str; *ptr; ptr++) { + switch (*ptr) { + case ',': + case '|': + case '=': + if (!isjson) + count++; + break; + case '"': + if (isjson) + count++; + break; + case '\\': + count++; + break; + } + } + + if (count == 0) + return str; + + buf = malloc(strlen(str) + count + 1); + if (unlikely(!buf)) { + quithere(1, "Failed to malloc escape buf %d", + (int)(strlen(str) + count + 1)); + } + + ptr = buf; + while (*str) + switch (*str) { + case ',': + case '|': + case '=': + if (!isjson) + *(ptr++) = '\\'; + *(ptr++) = *(str++); + break; + case '"': + if (isjson) + *(ptr++) = '\\'; + *(ptr++) = *(str++); + break; + case '\\': + *(ptr++) = '\\'; + *(ptr++) = *(str++); + break; + default: + *(ptr++) = *(str++); + break; + } + + *ptr = '\0'; + + return buf; +} + +static struct api_data *api_add_extra(struct api_data *root, struct api_data *extra) +{ + struct api_data *tmp; + + if (root) { + if (extra) { + // extra tail + tmp = extra->prev; + + // extra prev = root tail + extra->prev = root->prev; + + // root tail next = extra + root->prev->next = extra; + + // extra tail next = root + tmp->next = root; + + // root prev = extra tail + root->prev = tmp; + } + } else + root = extra; + + return root; +} + +static struct api_data *api_add_data_full(struct api_data *root, char *name, enum api_data_type type, void *data, bool copy_data) +{ + struct api_data *api_data; + + api_data = (struct api_data *)malloc(sizeof(struct api_data)); + + api_data->name = strdup(name); + api_data->type = type; + + if (root == NULL) { + root = api_data; + root->prev = root; + root->next = root; + } else { + api_data->prev = root->prev; + root->prev = api_data; + api_data->next = root; + api_data->prev->next = api_data; + } + + api_data->data_was_malloc = copy_data; + + // Avoid crashing on bad data + if (data == NULL) { + api_data->type = type = API_CONST; + data = (void *)NULLSTR; + api_data->data_was_malloc = copy_data = false; + } + + if (!copy_data) + api_data->data = data; + else + switch(type) { + case API_ESCAPE: + case API_STRING: + case API_CONST: + api_data->data = (void *)malloc(strlen((char *)data) + 1); + strcpy((char*)(api_data->data), (char *)data); + break; + case API_UINT8: + /* Most OSs won't really alloc less than 4 */ + api_data->data = malloc(4); + *(uint8_t *)api_data->data = *(uint8_t *)data; + break; + case API_INT16: + /* Most OSs won't really alloc less than 4 */ + api_data->data = malloc(4); + *(int16_t *)api_data->data = *(int16_t *)data; + break; + case API_UINT16: + /* Most OSs won't really alloc less than 4 */ + api_data->data = malloc(4); + *(uint16_t *)api_data->data = *(uint16_t *)data; + break; + case API_INT: + api_data->data = (void *)malloc(sizeof(int)); + *((int *)(api_data->data)) = *((int *)data); + break; + case API_UINT: + api_data->data = (void *)malloc(sizeof(unsigned int)); + *((unsigned int *)(api_data->data)) = *((unsigned int *)data); + break; + case API_UINT32: + api_data->data = (void *)malloc(sizeof(uint32_t)); + *((uint32_t *)(api_data->data)) = *((uint32_t *)data); + break; + case API_HEX32: + api_data->data = (void *)malloc(sizeof(uint32_t)); + *((uint32_t *)(api_data->data)) = *((uint32_t *)data); + break; + case API_UINT64: + api_data->data = (void *)malloc(sizeof(uint64_t)); + *((uint64_t *)(api_data->data)) = *((uint64_t *)data); + break; + case API_INT64: + api_data->data = (void *)malloc(sizeof(int64_t)); + *((int64_t *)(api_data->data)) = *((int64_t *)data); + break; + case API_DOUBLE: + case API_ELAPSED: + case API_MHS: + case API_MHTOTAL: + case API_UTILITY: + case API_FREQ: + case API_HS: + case API_DIFF: + case API_PERCENT: + api_data->data = (void *)malloc(sizeof(double)); + *((double *)(api_data->data)) = *((double *)data); + break; + case API_BOOL: + api_data->data = (void *)malloc(sizeof(bool)); + *((bool *)(api_data->data)) = *((bool *)data); + break; + case API_TIMEVAL: + api_data->data = (void *)malloc(sizeof(struct timeval)); + memcpy(api_data->data, data, sizeof(struct timeval)); + break; + case API_TIME: + api_data->data = (void *)malloc(sizeof(time_t)); + *(time_t *)(api_data->data) = *((time_t *)data); + break; + case API_VOLTS: + case API_TEMP: + case API_AVG: + api_data->data = (void *)malloc(sizeof(float)); + *((float *)(api_data->data)) = *((float *)data); + break; + default: + applog(LOG_ERR, "API: unknown1 data type %d ignored", type); + api_data->type = API_STRING; + api_data->data_was_malloc = false; + api_data->data = (void *)UNKNOWN; + break; + } + + return root; +} + +struct api_data *api_add_escape(struct api_data *root, char *name, char *data, bool copy_data) +{ + return api_add_data_full(root, name, API_ESCAPE, (void *)data, copy_data); +} + +struct api_data *api_add_string(struct api_data *root, char *name, char *data, bool copy_data) +{ + return api_add_data_full(root, name, API_STRING, (void *)data, copy_data); +} + +struct api_data *api_add_const(struct api_data *root, char *name, const char *data, bool copy_data) +{ + return api_add_data_full(root, name, API_CONST, (void *)data, copy_data); +} + +struct api_data *api_add_uint8(struct api_data *root, char *name, uint8_t *data, bool copy_data) +{ + return api_add_data_full(root, name, API_UINT8, (void *)data, copy_data); +} + +struct api_data *api_add_int16(struct api_data *root, char *name, uint16_t *data, bool copy_data) +{ + return api_add_data_full(root, name, API_INT16, (void *)data, copy_data); +} + +struct api_data *api_add_uint16(struct api_data *root, char *name, uint16_t *data, bool copy_data) +{ + return api_add_data_full(root, name, API_UINT16, (void *)data, copy_data); +} + +struct api_data *api_add_int(struct api_data *root, char *name, int *data, bool copy_data) +{ + return api_add_data_full(root, name, API_INT, (void *)data, copy_data); +} + +struct api_data *api_add_uint(struct api_data *root, char *name, unsigned int *data, bool copy_data) +{ + return api_add_data_full(root, name, API_UINT, (void *)data, copy_data); +} + +struct api_data *api_add_uint32(struct api_data *root, char *name, uint32_t *data, bool copy_data) +{ + return api_add_data_full(root, name, API_UINT32, (void *)data, copy_data); +} + +struct api_data *api_add_hex32(struct api_data *root, char *name, uint32_t *data, bool copy_data) +{ + return api_add_data_full(root, name, API_HEX32, (void *)data, copy_data); +} + +struct api_data *api_add_uint64(struct api_data *root, char *name, uint64_t *data, bool copy_data) +{ + return api_add_data_full(root, name, API_UINT64, (void *)data, copy_data); +} + +struct api_data *api_add_int64(struct api_data *root, char *name, int64_t *data, bool copy_data) +{ + return api_add_data_full(root, name, API_INT64, (void *)data, copy_data); +} + +struct api_data *api_add_double(struct api_data *root, char *name, double *data, bool copy_data) +{ + return api_add_data_full(root, name, API_DOUBLE, (void *)data, copy_data); +} + +struct api_data *api_add_elapsed(struct api_data *root, char *name, double *data, bool copy_data) +{ + return api_add_data_full(root, name, API_ELAPSED, (void *)data, copy_data); +} + +struct api_data *api_add_bool(struct api_data *root, char *name, bool *data, bool copy_data) +{ + return api_add_data_full(root, name, API_BOOL, (void *)data, copy_data); +} + +struct api_data *api_add_timeval(struct api_data *root, char *name, struct timeval *data, bool copy_data) +{ + return api_add_data_full(root, name, API_TIMEVAL, (void *)data, copy_data); +} + +struct api_data *api_add_time(struct api_data *root, char *name, time_t *data, bool copy_data) +{ + return api_add_data_full(root, name, API_TIME, (void *)data, copy_data); +} + +struct api_data *api_add_mhs(struct api_data *root, char *name, double *data, bool copy_data) +{ + return api_add_data_full(root, name, API_MHS, (void *)data, copy_data); +} + +struct api_data *api_add_mhtotal(struct api_data *root, char *name, double *data, bool copy_data) +{ + return api_add_data_full(root, name, API_MHTOTAL, (void *)data, copy_data); +} + +struct api_data *api_add_temp(struct api_data *root, char *name, float *data, bool copy_data) +{ + return api_add_data_full(root, name, API_TEMP, (void *)data, copy_data); +} + +struct api_data *api_add_utility(struct api_data *root, char *name, double *data, bool copy_data) +{ + return api_add_data_full(root, name, API_UTILITY, (void *)data, copy_data); +} + +struct api_data *api_add_freq(struct api_data *root, char *name, double *data, bool copy_data) +{ + return api_add_data_full(root, name, API_FREQ, (void *)data, copy_data); +} + +struct api_data *api_add_volts(struct api_data *root, char *name, float *data, bool copy_data) +{ + return api_add_data_full(root, name, API_VOLTS, (void *)data, copy_data); +} + +struct api_data *api_add_hs(struct api_data *root, char *name, double *data, bool copy_data) +{ + return api_add_data_full(root, name, API_HS, (void *)data, copy_data); +} + +struct api_data *api_add_diff(struct api_data *root, char *name, double *data, bool copy_data) +{ + return api_add_data_full(root, name, API_DIFF, (void *)data, copy_data); +} + +struct api_data *api_add_percent(struct api_data *root, char *name, double *data, bool copy_data) +{ + return api_add_data_full(root, name, API_PERCENT, (void *)data, copy_data); +} + +struct api_data *api_add_avg(struct api_data *root, char *name, float *data, bool copy_data) +{ + return api_add_data_full(root, name, API_AVG, (void *)data, copy_data); +} + +static void add_item_buf(K_ITEM *item, const char *str) +{ + size_t old_siz, new_siz, siz, ext; + char *buf; + + buf = DATASB(item)->buf; + siz = (size_t)strlen(str); + + old_siz = DATASB(item)->siz; + new_siz = old_siz + siz + 1; // include '\0' + if (DATASB(item)->tot < new_siz) { + ext = (siz + 1) + SBEXTEND - ((siz + 1) % SBEXTEND); + DATASB(item)->buf = buf = realloc(DATASB(item)->buf, DATASB(item)->tot + ext); + if (!buf) { + quithere(1, "OOM buf siz=%d tot=%d ext=%d", + (int)siz, (int)(DATASB(item)->tot), (int)ext); + } + DATASB(item)->tot += ext; + } + memcpy(buf + old_siz, str, siz + 1); + DATASB(item)->siz += siz; +} + +static struct api_data *print_data(struct io_data *io_data, struct api_data *root, bool isjson, bool precom) +{ + // N.B. strings don't use this buffer so 64 is enough (for now) + char buf[64]; + struct api_data *tmp; + bool done, first = true; + char *original, *escape; + K_ITEM *item; + + K_WLOCK(strbufs); + item = k_unlink_head(strbufs); + K_WUNLOCK(strbufs); + + DATASB(item)->siz = 0; + + if (precom) + add_item_buf(item, COMMA); + + if (isjson) + add_item_buf(item, JSON0); + + while (root) { + if (!first) + add_item_buf(item, COMMA); + else + first = false; + + if (isjson) + add_item_buf(item, JSON1); + + add_item_buf(item, root->name); + + if (isjson) + add_item_buf(item, JSON1); + + if (isjson) + add_item_buf(item, ":"); + else + add_item_buf(item, "="); + + first = false; + + done = false; + switch(root->type) { + case API_STRING: + case API_CONST: + if (isjson) + add_item_buf(item, JSON1); + add_item_buf(item, (char *)(root->data)); + if (isjson) + add_item_buf(item, JSON1); + done = true; + break; + case API_ESCAPE: + original = (char *)(root->data); + escape = escape_string((char *)(root->data), isjson); + if (isjson) + add_item_buf(item, JSON1); + add_item_buf(item, escape); + if (isjson) + add_item_buf(item, JSON1); + if (escape != original) + free(escape); + done = true; + break; + case API_UINT8: + snprintf(buf, sizeof(buf), "%u", *(uint8_t *)root->data); + break; + case API_INT16: + snprintf(buf, sizeof(buf), "%d", *(int16_t *)root->data); + break; + case API_UINT16: + snprintf(buf, sizeof(buf), "%u", *(uint16_t *)root->data); + break; + case API_INT: + snprintf(buf, sizeof(buf), "%d", *((int *)(root->data))); + break; + case API_UINT: + snprintf(buf, sizeof(buf), "%u", *((unsigned int *)(root->data))); + break; + case API_UINT32: + snprintf(buf, sizeof(buf), "%"PRIu32, *((uint32_t *)(root->data))); + break; + case API_HEX32: + if (isjson) + add_item_buf(item, JSON1); + snprintf(buf, sizeof(buf), "0x%08x", *((uint32_t *)(root->data))); + add_item_buf(item, buf); + if (isjson) + add_item_buf(item, JSON1); + done = true; + break; + case API_UINT64: + snprintf(buf, sizeof(buf), "%"PRIu64, *((uint64_t *)(root->data))); + break; + case API_INT64: + snprintf(buf, sizeof(buf), "%"PRId64, *((int64_t *)(root->data))); + break; + case API_TIME: + snprintf(buf, sizeof(buf), "%lu", *((unsigned long *)(root->data))); + break; + case API_DOUBLE: + snprintf(buf, sizeof(buf), "%f", *((double *)(root->data))); + break; + case API_ELAPSED: + snprintf(buf, sizeof(buf), "%.0f", *((double *)(root->data))); + break; + case API_UTILITY: + case API_FREQ: + case API_MHS: + snprintf(buf, sizeof(buf), "%.2f", *((double *)(root->data))); + break; + case API_VOLTS: + case API_AVG: + snprintf(buf, sizeof(buf), "%.3f", *((float *)(root->data))); + break; + case API_MHTOTAL: + snprintf(buf, sizeof(buf), "%.4f", *((double *)(root->data))); + break; + case API_HS: + snprintf(buf, sizeof(buf), "%.15f", *((double *)(root->data))); + break; + case API_DIFF: + snprintf(buf, sizeof(buf), "%.8f", *((double *)(root->data))); + break; + case API_BOOL: + snprintf(buf, sizeof(buf), "%s", *((bool *)(root->data)) ? TRUESTR : FALSESTR); + break; + case API_TIMEVAL: + snprintf(buf, sizeof(buf), "%ld.%06ld", + (long)((struct timeval *)(root->data))->tv_sec, + (long)((struct timeval *)(root->data))->tv_usec); + break; + case API_TEMP: + snprintf(buf, sizeof(buf), "%.2f", *((float *)(root->data))); + break; + case API_PERCENT: + snprintf(buf, sizeof(buf), "%.4f", *((double *)(root->data)) * 100.0); + break; + default: + applog(LOG_ERR, "API: unknown2 data type %d ignored", root->type); + if (isjson) + add_item_buf(item, JSON1); + add_item_buf(item, UNKNOWN); + if (isjson) + add_item_buf(item, JSON1); + done = true; + break; + } + + if (!done) + add_item_buf(item, buf); + + free(root->name); + if (root->data_was_malloc) + free(root->data); + + if (root->next == root) { + free(root); + root = NULL; + } else { + tmp = root; + root = tmp->next; + root->prev = tmp->prev; + root->prev->next = root; + free(tmp); + } + } + + if (isjson) + add_item_buf(item, JSON5); + else + add_item_buf(item, SEPSTR); + + io_add(io_data, DATASB(item)->buf); + + K_WLOCK(strbufs); + k_add_head(strbufs, item); + K_WUNLOCK(strbufs); + + return root; +} + +#define DRIVER_COUNT_DRV(X) if (devices[i]->drv->drv_id == DRIVER_##X) \ + count++; + +#ifdef HAVE_AN_ASIC +static int numascs(void) +{ + int count = 0; + int i; + + rd_lock(&devices_lock); + for (i = 0; i < total_devices; i++) { + ASIC_PARSE_COMMANDS(DRIVER_COUNT_DRV) + } + rd_unlock(&devices_lock); + return count; +} + +static int ascdevice(int ascid) +{ + int count = 0; + int i; + + rd_lock(&devices_lock); + for (i = 0; i < total_devices; i++) { + ASIC_PARSE_COMMANDS(DRIVER_COUNT_DRV) + if (count == (ascid + 1)) + goto foundit; + } + + rd_unlock(&devices_lock); + return -1; + +foundit: + + rd_unlock(&devices_lock); + return i; +} +#endif + +#ifdef HAVE_AN_FPGA +static int numpgas(void) +{ + int count = 0; + int i; + + rd_lock(&devices_lock); + for (i = 0; i < total_devices; i++) { + FPGA_PARSE_COMMANDS(DRIVER_COUNT_DRV) + } + rd_unlock(&devices_lock); + return count; +} + +static int pgadevice(int pgaid) +{ + int count = 0; + int i; + + rd_lock(&devices_lock); + for (i = 0; i < total_devices; i++) { + FPGA_PARSE_COMMANDS(DRIVER_COUNT_DRV) + if (count == (pgaid + 1)) + goto foundit; + } + + rd_unlock(&devices_lock); + return -1; + +foundit: + + rd_unlock(&devices_lock); + return i; +} +#endif + +// All replies (except BYE and RESTART) start with a message +// thus for JSON, message() inserts JSON_START at the front +// and send_result() adds JSON_END at the end +static void message(struct io_data *io_data, int messageid, int paramid, char *param2, bool isjson) +{ + struct api_data *root = NULL; + char buf[TMPBUFSIZ]; + char severity[2]; +#ifdef HAVE_AN_ASIC + int asc; +#endif +#ifdef HAVE_AN_FPGA + int pga; +#endif + int i; + + if (isjson) + io_add(io_data, JSON_START JSON_STATUS); + + for (i = 0; codes[i].severity != SEVERITY_FAIL; i++) { + if (codes[i].code == messageid) { + switch (codes[i].severity) { + case SEVERITY_WARN: + severity[0] = 'W'; + break; + case SEVERITY_INFO: + severity[0] = 'I'; + break; + case SEVERITY_SUCC: + severity[0] = 'S'; + break; + case SEVERITY_ERR: + default: + severity[0] = 'E'; + break; + } + severity[1] = '\0'; + + switch(codes[i].params) { + case PARAM_PGA: + case PARAM_ASC: + case PARAM_PID: + case PARAM_INT: + sprintf(buf, codes[i].description, paramid); + break; + case PARAM_POOL: + sprintf(buf, codes[i].description, paramid, pools[paramid]->rpc_url); + break; +#ifdef HAVE_AN_FPGA + case PARAM_PGAMAX: + pga = numpgas(); + sprintf(buf, codes[i].description, paramid, pga - 1); + break; +#endif +#ifdef HAVE_AN_ASIC + case PARAM_ASCMAX: + asc = numascs(); + sprintf(buf, codes[i].description, paramid, asc - 1); + break; +#endif + case PARAM_PMAX: + sprintf(buf, codes[i].description, total_pools); + break; + case PARAM_POOLMAX: + sprintf(buf, codes[i].description, paramid, total_pools - 1); + break; + case PARAM_DMAX: +#ifdef HAVE_AN_ASIC + asc = numascs(); +#endif +#ifdef HAVE_AN_FPGA + pga = numpgas(); +#endif + + sprintf(buf, codes[i].description +#ifdef HAVE_AN_ASIC + , asc +#endif +#ifdef HAVE_AN_FPGA + , pga +#endif + ); + break; + case PARAM_CMD: + sprintf(buf, codes[i].description, JSON_COMMAND); + break; + case PARAM_STR: + sprintf(buf, codes[i].description, param2); + break; + case PARAM_BOTH: + sprintf(buf, codes[i].description, paramid, param2); + break; + case PARAM_BOOL: + sprintf(buf, codes[i].description, paramid ? TRUESTR : FALSESTR); + break; + case PARAM_SET: + sprintf(buf, codes[i].description, param2, paramid); + break; + case PARAM_NONE: + default: + strcpy(buf, codes[i].description); + } + + root = api_add_string(root, _STATUS, severity, false); + root = api_add_time(root, "When", &when, false); + root = api_add_int(root, "Code", &messageid, false); + root = api_add_escape(root, "Msg", buf, false); + root = api_add_escape(root, "Description", opt_api_description, false); + + root = print_data(io_data, root, isjson, false); + if (isjson) + io_add(io_data, JSON_CLOSE); + return; + } + } + + root = api_add_string(root, _STATUS, "F", false); + root = api_add_time(root, "When", &when, false); + int id = -1; + root = api_add_int(root, "Code", &id, false); + sprintf(buf, "%d", messageid); + root = api_add_escape(root, "Msg", buf, false); + root = api_add_escape(root, "Description", opt_api_description, false); + + root = print_data(io_data, root, isjson, false); + if (isjson) + io_add(io_data, JSON_CLOSE); +} + +#if LOCK_TRACKING + +#define LOCK_FMT_FFL " - called from %s %s():%d" + +#define LOCKMSG(fmt, ...) fprintf(stderr, "APILOCK: " fmt "\n", ##__VA_ARGS__) +#define LOCKMSGMORE(fmt, ...) fprintf(stderr, " " fmt "\n", ##__VA_ARGS__) +#define LOCKMSGFFL(fmt, ...) fprintf(stderr, "APILOCK: " fmt LOCK_FMT_FFL "\n", ##__VA_ARGS__, file, func, linenum) +#define LOCKMSGFLUSH() fflush(stderr) + +typedef struct lockstat { + uint64_t lock_id; + const char *file; + const char *func; + int linenum; + struct timeval tv; +} LOCKSTAT; + +typedef struct lockline { + struct lockline *prev; + struct lockstat *stat; + struct lockline *next; +} LOCKLINE; + +typedef struct lockinfo { + void *lock; + enum cglock_typ typ; + const char *file; + const char *func; + int linenum; + uint64_t gets; + uint64_t gots; + uint64_t tries; + uint64_t dids; + uint64_t didnts; // should be tries - dids + uint64_t unlocks; + LOCKSTAT lastgot; + LOCKLINE *lockgets; + LOCKLINE *locktries; +} LOCKINFO; + +typedef struct locklist { + LOCKINFO *info; + struct locklist *next; +} LOCKLIST; + +static uint64_t lock_id = 1; + +static LOCKLIST *lockhead; + +static void lockmsgnow() +{ + struct timeval now; + struct tm *tm; + time_t dt; + + cgtime(&now); + + dt = now.tv_sec; + tm = localtime(&dt); + + LOCKMSG("%d-%02d-%02d %02d:%02d:%02d", + tm->tm_year + 1900, + tm->tm_mon + 1, + tm->tm_mday, + tm->tm_hour, + tm->tm_min, + tm->tm_sec); +} + +static LOCKLIST *newlock(void *lock, enum cglock_typ typ, const char *file, const char *func, const int linenum) +{ + LOCKLIST *list; + + list = calloc(1, sizeof(*list)); + if (!list) + quithere(1, "OOM list"); + list->info = calloc(1, sizeof(*(list->info))); + if (!list->info) + quithere(1, "OOM info"); + list->next = lockhead; + lockhead = list; + + list->info->lock = lock; + list->info->typ = typ; + list->info->file = file; + list->info->func = func; + list->info->linenum = linenum; + + return list; +} + +static LOCKINFO *findlock(void *lock, enum cglock_typ typ, const char *file, const char *func, const int linenum) +{ + LOCKLIST *look; + + look = lockhead; + while (look) { + if (look->info->lock == lock) + break; + look = look->next; + } + + if (!look) + look = newlock(lock, typ, file, func, linenum); + + return look->info; +} + +static void addgettry(LOCKINFO *info, uint64_t id, const char *file, const char *func, const int linenum, bool get) +{ + LOCKSTAT *stat; + LOCKLINE *line; + + stat = calloc(1, sizeof(*stat)); + if (!stat) + quithere(1, "OOM stat"); + line = calloc(1, sizeof(*line)); + if (!line) + quithere(1, "OOM line"); + + if (get) + info->gets++; + else + info->tries++; + + stat->lock_id = id; + stat->file = file; + stat->func = func; + stat->linenum = linenum; + cgtime(&stat->tv); + + line->stat = stat; + + if (get) { + line->next = info->lockgets; + if (info->lockgets) + info->lockgets->prev = line; + info->lockgets = line; + } else { + line->next = info->locktries; + if (info->locktries) + info->locktries->prev = line; + info->locktries = line; + } +} + +static void markgotdid(LOCKINFO *info, uint64_t id, const char *file, const char *func, const int linenum, bool got, int ret) +{ + LOCKLINE *line; + + if (got) + info->gots++; + else { + if (ret == 0) + info->dids++; + else + info->didnts++; + } + + if (got || ret == 0) { + info->lastgot.lock_id = id; + info->lastgot.file = file; + info->lastgot.func = func; + info->lastgot.linenum = linenum; + cgtime(&info->lastgot.tv); + } + + if (got) + line = info->lockgets; + else + line = info->locktries; + while (line) { + if (line->stat->lock_id == id) + break; + line = line->next; + } + + if (!line) { + lockmsgnow(); + LOCKMSGFFL("ERROR attempt to mark a lock as '%s' that wasn't '%s' id=%"PRIu64, + got ? "got" : "did/didnt", got ? "get" : "try", id); + } + + // Unlink it + if (line->prev) + line->prev->next = line->next; + if (line->next) + line->next->prev = line->prev; + + if (got) { + if (info->lockgets == line) + info->lockgets = line->next; + } else { + if (info->locktries == line) + info->locktries = line->next; + } + + free(line->stat); + free(line); +} + +// Yes this uses locks also ... ;/ +static void locklock() +{ + if (unlikely(pthread_mutex_lock(&lockstat_lock))) + quithere(1, "WTF MUTEX ERROR ON LOCK! errno=%d", errno); +} + +static void lockunlock() +{ + if (unlikely(pthread_mutex_unlock(&lockstat_lock))) + quithere(1, "WTF MUTEX ERROR ON UNLOCK! errno=%d", errno); +} + +uint64_t api_getlock(void *lock, const char *file, const char *func, const int linenum) +{ + LOCKINFO *info; + uint64_t id; + + locklock(); + + info = findlock(lock, CGLOCK_UNKNOWN, file, func, linenum); + id = lock_id++; + addgettry(info, id, file, func, linenum, true); + + lockunlock(); + + return id; +} + +void api_gotlock(uint64_t id, void *lock, const char *file, const char *func, const int linenum) +{ + LOCKINFO *info; + + locklock(); + + info = findlock(lock, CGLOCK_UNKNOWN, file, func, linenum); + markgotdid(info, id, file, func, linenum, true, 0); + + lockunlock(); +} + +uint64_t api_trylock(void *lock, const char *file, const char *func, const int linenum) +{ + LOCKINFO *info; + uint64_t id; + + locklock(); + + info = findlock(lock, CGLOCK_UNKNOWN, file, func, linenum); + id = lock_id++; + addgettry(info, id, file, func, linenum, false); + + lockunlock(); + + return id; +} + +void api_didlock(uint64_t id, int ret, void *lock, const char *file, const char *func, const int linenum) +{ + LOCKINFO *info; + + locklock(); + + info = findlock(lock, CGLOCK_UNKNOWN, file, func, linenum); + markgotdid(info, id, file, func, linenum, false, ret); + + lockunlock(); +} + +void api_gunlock(void *lock, const char *file, const char *func, const int linenum) +{ + LOCKINFO *info; + + locklock(); + + info = findlock(lock, CGLOCK_UNKNOWN, file, func, linenum); + info->unlocks++; + + lockunlock(); +} + +void api_initlock(void *lock, enum cglock_typ typ, const char *file, const char *func, const int linenum) +{ + locklock(); + + findlock(lock, typ, file, func, linenum); + + lockunlock(); +} + +void dsp_det(char *msg, LOCKSTAT *stat) +{ + struct tm *tm; + time_t dt; + + dt = stat->tv.tv_sec; + tm = localtime(&dt); + + LOCKMSGMORE("%s id=%"PRIu64" by %s %s():%d at %d-%02d-%02d %02d:%02d:%02d", + msg, + stat->lock_id, + stat->file, + stat->func, + stat->linenum, + tm->tm_year + 1900, + tm->tm_mon + 1, + tm->tm_mday, + tm->tm_hour, + tm->tm_min, + tm->tm_sec); +} + +void dsp_lock(LOCKINFO *info) +{ + LOCKLINE *line; + char *status; + + LOCKMSG("Lock %p created by %s %s():%d", + info->lock, + info->file, + info->func, + info->linenum); + LOCKMSGMORE("gets:%"PRIu64" gots:%"PRIu64" tries:%"PRIu64 + " dids:%"PRIu64" didnts:%"PRIu64" unlocks:%"PRIu64, + info->gets, + info->gots, + info->tries, + info->dids, + info->didnts, + info->unlocks); + + if (info->gots > 0 || info->dids > 0) { + if (info->unlocks < info->gots + info->dids) + status = "Last got/did still HELD"; + else + status = "Last got/did (idle)"; + + dsp_det(status, &(info->lastgot)); + } else + LOCKMSGMORE("... unused ..."); + + if (info->lockgets) { + LOCKMSGMORE("BLOCKED gets (%"PRIu64")", info->gets - info->gots); + line = info->lockgets; + while (line) { + dsp_det("", line->stat); + line = line->next; + } + } else + LOCKMSGMORE("no blocked gets"); + + if (info->locktries) { + LOCKMSGMORE("BLOCKED tries (%"PRIu64")", info->tries - info->dids - info->didnts); + line = info->lockgets; + while (line) { + dsp_det("", line->stat); + line = line->next; + } + } else + LOCKMSGMORE("no blocked tries"); +} + +void show_locks() +{ + LOCKLIST *list; + + locklock(); + + lockmsgnow(); + + list = lockhead; + if (!list) + LOCKMSG("no locks?!?\n"); + else { + while (list) { + dsp_lock(list->info); + list = list->next; + } + } + + LOCKMSGFLUSH(); + + lockunlock(); +} +#endif + +static void lockstats(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group) +{ +#if LOCK_TRACKING + show_locks(); + message(io_data, MSG_LOCKOK, 0, NULL, isjson); +#else + message(io_data, MSG_LOCKDIS, 0, NULL, isjson); +#endif +} + +static void apiversion(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group) +{ + struct api_data *root = NULL; + bool io_open; + + message(io_data, MSG_VERSION, 0, NULL, isjson); + io_open = io_add(io_data, isjson ? COMSTR JSON_VERSION : _VERSION COMSTR); + + root = api_add_string(root, "BMMiner", VERSION, false); + root = api_add_const(root, "API", APIVERSION, false); + root = api_add_string(root, "Miner", g_miner_version, false); + root = api_add_string(root, "CompileTime", g_miner_compiletime, false); + root = api_add_string(root, "Type", g_miner_type, false); + + root = print_data(io_data, root, isjson, false); + if (isjson && io_open) + io_close(io_data); +} + +static void minerconfig(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group) +{ + struct api_data *root = NULL; + bool io_open; + int asccount = 0; + int pgacount = 0; + +#ifdef HAVE_AN_ASIC + asccount = numascs(); +#endif + +#ifdef HAVE_AN_FPGA + pgacount = numpgas(); +#endif + + message(io_data, MSG_MINECONFIG, 0, NULL, isjson); + io_open = io_add(io_data, isjson ? COMSTR JSON_MINECONFIG : _MINECONFIG COMSTR); + + root = api_add_int(root, "ASC Count", &asccount, false); + root = api_add_int(root, "PGA Count", &pgacount, false); + root = api_add_int(root, "Pool Count", &total_pools, false); + root = api_add_const(root, "Strategy", strategies[pool_strategy].s, false); + root = api_add_int(root, "Log Interval", &opt_log_interval, false); + root = api_add_const(root, "Device Code", DEVICECODE, false); + root = api_add_const(root, "OS", OSINFO, false); + root = api_add_bool(root, "Failover-Only", &opt_fail_only, false); + root = api_add_int(root, "ScanTime", &opt_scantime, false); + root = api_add_int(root, "Queue", &opt_queue, false); + root = api_add_int(root, "Expiry", &opt_expiry, false); +#ifdef USE_USBUTILS + if (hotplug_time == 0) + root = api_add_const(root, "Hotplug", DISABLED, false); + else + root = api_add_int(root, "Hotplug", &hotplug_time, false); +#else + root = api_add_const(root, "Hotplug", NONE, false); +#endif + + root = print_data(io_data, root, isjson, false); + if (isjson && io_open) + io_close(io_data); +} + +static const char *status2str(enum alive status) +{ + switch (status) { + case LIFE_WELL: + return ALIVE; + case LIFE_SICK: + return SICK; + case LIFE_DEAD: + return DEAD; + case LIFE_NOSTART: + return NOSTART; + case LIFE_INIT: + return INIT; + default: + return UNKNOWN; + } +} + +#ifdef HAVE_AN_ASIC +static void ascstatus(struct io_data *io_data, int asc, bool isjson, bool precom) +{ + struct api_data *root = NULL; + char *enabled; + char *status; + int numasc = numascs(); + + if (numasc > 0 && asc >= 0 && asc < numasc) { + int dev = ascdevice(asc); + if (dev < 0) // Should never happen + return; + + struct cgpu_info *cgpu = get_devices(dev); + float temp = cgpu->temp; + double dev_runtime; + + dev_runtime = cgpu_runtime(cgpu); + + cgpu->utility = cgpu->accepted / dev_runtime * 60; + + if (cgpu->deven != DEV_DISABLED) + enabled = (char *)YES; + else + enabled = (char *)NO; + + status = (char *)status2str(cgpu->status); + + root = api_add_int(root, "ASC", &asc, false); + root = api_add_string(root, "Name", cgpu->drv->name, false); + root = api_add_int(root, "ID", &(cgpu->device_id), false); + root = api_add_string(root, "Enabled", enabled, false); + root = api_add_string(root, "Status", status, false); + root = api_add_temp(root, "Temperature", &temp, false); + double mhs = cgpu->total_mhashes / dev_runtime; + root = api_add_mhs(root, "MHS av", &mhs, false); + char mhsname[27]; + sprintf(mhsname, "MHS %ds", opt_log_interval); + root = api_add_mhs(root, mhsname, &(cgpu->rolling), false); + root = api_add_int(root, "Accepted", &(cgpu->accepted), false); + root = api_add_int(root, "Rejected", &(cgpu->rejected), false); + root = api_add_int(root, "Hardware Errors", &(cgpu->hw_errors), false); + root = api_add_utility(root, "Utility", &(cgpu->utility), false); + int last_share_pool = cgpu->last_share_pool_time > 0 ? + cgpu->last_share_pool : -1; + root = api_add_int(root, "Last Share Pool", &last_share_pool, false); + root = api_add_time(root, "Last Share Time", &(cgpu->last_share_pool_time), false); + root = api_add_mhtotal(root, "Total MH", &(cgpu->total_mhashes), false); + root = api_add_int64(root, "Diff1 Work", &(cgpu->diff1), false); + root = api_add_diff(root, "Difficulty Accepted", &(cgpu->diff_accepted), false); + root = api_add_diff(root, "Difficulty Rejected", &(cgpu->diff_rejected), false); + root = api_add_diff(root, "Last Share Difficulty", &(cgpu->last_share_diff), false); +#ifdef USE_USBUTILS + root = api_add_bool(root, "No Device", &(cgpu->usbinfo.nodev), false); +#endif + root = api_add_time(root, "Last Valid Work", &(cgpu->last_device_valid_work), false); + double hwp = (cgpu->hw_errors + cgpu->diff1) ? + (double)(cgpu->hw_errors) / (double)(cgpu->hw_errors + cgpu->diff1) : 0; + root = api_add_percent(root, "Device Hardware%", &hwp, false); + double rejp = cgpu->diff1 ? + (double)(cgpu->diff_rejected) / (double)(cgpu->diff1) : 0; + root = api_add_percent(root, "Device Rejected%", &rejp, false); + root = api_add_elapsed(root, "Device Elapsed", &(dev_runtime), false); + + root = print_data(io_data, root, isjson, precom); + } +} +#endif + +#ifdef HAVE_AN_FPGA +static void pgastatus(struct io_data *io_data, int pga, bool isjson, bool precom) +{ + struct api_data *root = NULL; + char *enabled; + char *status; + int numpga = numpgas(); + + if (numpga > 0 && pga >= 0 && pga < numpga) { + int dev = pgadevice(pga); + if (dev < 0) // Should never happen + return; + + struct cgpu_info *cgpu = get_devices(dev); + double frequency = 0; + float temp = cgpu->temp; + struct timeval now; + double dev_runtime; + + if (cgpu->dev_start_tv.tv_sec == 0) + dev_runtime = total_secs; + else { + cgtime(&now); + dev_runtime = tdiff(&now, &(cgpu->dev_start_tv)); + } + + if (dev_runtime < 1.0) + dev_runtime = 1.0; + +#ifdef USE_MODMINER + if (cgpu->drv->drv_id == DRIVER_modminer) + frequency = cgpu->clock; +#endif + + cgpu->utility = cgpu->accepted / dev_runtime * 60; + + if (cgpu->deven != DEV_DISABLED) + enabled = (char *)YES; + else + enabled = (char *)NO; + + status = (char *)status2str(cgpu->status); + + root = api_add_int(root, "PGA", &pga, false); + root = api_add_string(root, "Name", cgpu->drv->name, false); + root = api_add_int(root, "ID", &(cgpu->device_id), false); + root = api_add_string(root, "Enabled", enabled, false); + root = api_add_string(root, "Status", status, false); + root = api_add_temp(root, "Temperature", &temp, false); + double mhs = cgpu->total_mhashes / dev_runtime; + root = api_add_mhs(root, "MHS av", &mhs, false); + char mhsname[27]; + sprintf(mhsname, "MHS %ds", opt_log_interval); + root = api_add_mhs(root, mhsname, &(cgpu->rolling), false); + root = api_add_int(root, "Accepted", &(cgpu->accepted), false); + root = api_add_int(root, "Rejected", &(cgpu->rejected), false); + root = api_add_int(root, "Hardware Errors", &(cgpu->hw_errors), false); + root = api_add_utility(root, "Utility", &(cgpu->utility), false); + int last_share_pool = cgpu->last_share_pool_time > 0 ? + cgpu->last_share_pool : -1; + root = api_add_int(root, "Last Share Pool", &last_share_pool, false); + root = api_add_time(root, "Last Share Time", &(cgpu->last_share_pool_time), false); + root = api_add_mhtotal(root, "Total MH", &(cgpu->total_mhashes), false); + root = api_add_freq(root, "Frequency", &frequency, false); + root = api_add_int64(root, "Diff1 Work", &(cgpu->diff1), false); + root = api_add_diff(root, "Difficulty Accepted", &(cgpu->diff_accepted), false); + root = api_add_diff(root, "Difficulty Rejected", &(cgpu->diff_rejected), false); + root = api_add_diff(root, "Last Share Difficulty", &(cgpu->last_share_diff), false); +#ifdef USE_USBUTILS + root = api_add_bool(root, "No Device", &(cgpu->usbinfo.nodev), false); +#endif + root = api_add_time(root, "Last Valid Work", &(cgpu->last_device_valid_work), false); + double hwp = (cgpu->hw_errors + cgpu->diff1) ? + (double)(cgpu->hw_errors) / (double)(cgpu->hw_errors + cgpu->diff1) : 0; + root = api_add_percent(root, "Device Hardware%", &hwp, false); + double rejp = cgpu->diff1 ? + (double)(cgpu->diff_rejected) / (double)(cgpu->diff1) : 0; + root = api_add_percent(root, "Device Rejected%", &rejp, false); + root = api_add_elapsed(root, "Device Elapsed", &(dev_runtime), false); + + root = print_data(io_data, root, isjson, precom); + } +} +#endif + +static void devstatus(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group) +{ + bool io_open = false; + int devcount = 0; + int numasc = 0; + int numpga = 0; + int i; + +#ifdef HAVE_AN_ASIC + numasc = numascs(); +#endif + +#ifdef HAVE_AN_FPGA + numpga = numpgas(); +#endif + + if (numpga == 0 && numasc == 0) { + message(io_data, MSG_NODEVS, 0, NULL, isjson); + return; + } + + + message(io_data, MSG_DEVS, 0, NULL, isjson); + if (isjson) + io_open = io_add(io_data, COMSTR JSON_DEVS); + +#ifdef HAVE_AN_ASIC + if (numasc > 0) { + for (i = 0; i < numasc; i++) { + ascstatus(io_data, i, isjson, isjson && devcount > 0); + + devcount++; + } + } +#endif + +#ifdef HAVE_AN_FPGA + if (numpga > 0) { + for (i = 0; i < numpga; i++) { + pgastatus(io_data, i, isjson, isjson && devcount > 0); + + devcount++; + } + } +#endif + + if (isjson && io_open) + io_close(io_data); +} + +static void edevstatus(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group) +{ + bool io_open = false; + int devcount = 0; + int numasc = 0; + int numpga = 0; + int i; +#ifdef USE_USBUTILS + time_t howoldsec = 0; +#endif + +#ifdef HAVE_AN_ASIC + numasc = numascs(); +#endif + +#ifdef HAVE_AN_FPGA + numpga = numpgas(); +#endif + + if (numpga == 0 && numasc == 0) { + message(io_data, MSG_NODEVS, 0, NULL, isjson); + return; + } + +#ifdef USE_USBUTILS + if (param && *param) + howoldsec = (time_t)atoi(param); +#endif + + message(io_data, MSG_DEVS, 0, NULL, isjson); + if (isjson) + io_open = io_add(io_data, COMSTR JSON_DEVS); + +#ifdef HAVE_AN_ASIC + if (numasc > 0) { + for (i = 0; i < numasc; i++) { +#ifdef USE_USBUTILS + int dev = ascdevice(i); + if (dev < 0) // Should never happen + continue; + + struct cgpu_info *cgpu = get_devices(dev); + if (!cgpu) + continue; + if (cgpu->blacklisted) + continue; + if (cgpu->usbinfo.nodev) { + if (howoldsec <= 0) + continue; + if ((when - cgpu->usbinfo.last_nodev.tv_sec) >= howoldsec) + continue; + } +#endif + + ascstatus(io_data, i, isjson, isjson && devcount > 0); + + devcount++; + } + } +#endif + +#ifdef HAVE_AN_FPGA + if (numpga > 0) { + for (i = 0; i < numpga; i++) { +#ifdef USE_USBUTILS + int dev = pgadevice(i); + if (dev < 0) // Should never happen + continue; + + struct cgpu_info *cgpu = get_devices(dev); + if (!cgpu) + continue; + if (cgpu->blacklisted) + continue; + if (cgpu->usbinfo.nodev) { + if (howoldsec <= 0) + continue; + if ((when - cgpu->usbinfo.last_nodev.tv_sec) >= howoldsec) + continue; + } +#endif + + pgastatus(io_data, i, isjson, isjson && devcount > 0); + + devcount++; + } + } +#endif + + if (isjson && io_open) + io_close(io_data); +} + +#ifdef HAVE_AN_FPGA +static void pgadev(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group) +{ + bool io_open = false; + int numpga = numpgas(); + int id; + + if (numpga == 0) { + message(io_data, MSG_PGANON, 0, NULL, isjson); + return; + } + + if (param == NULL || *param == '\0') { + message(io_data, MSG_MISID, 0, NULL, isjson); + return; + } + + id = atoi(param); + if (id < 0 || id >= numpga) { + message(io_data, MSG_INVPGA, id, NULL, isjson); + return; + } + + message(io_data, MSG_PGADEV, id, NULL, isjson); + + if (isjson) + io_open = io_add(io_data, COMSTR JSON_PGA); + + pgastatus(io_data, id, isjson, false); + + if (isjson && io_open) + io_close(io_data); +} + +static void pgaenable(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group) +{ + struct cgpu_info *cgpu; + int numpga = numpgas(); + struct thr_info *thr; + int pga; + int id; + int i; + + if (numpga == 0) { + message(io_data, MSG_PGANON, 0, NULL, isjson); + return; + } + + if (param == NULL || *param == '\0') { + message(io_data, MSG_MISID, 0, NULL, isjson); + return; + } + + id = atoi(param); + if (id < 0 || id >= numpga) { + message(io_data, MSG_INVPGA, id, NULL, isjson); + return; + } + + int dev = pgadevice(id); + if (dev < 0) { // Should never happen + message(io_data, MSG_INVPGA, id, NULL, isjson); + return; + } + + cgpu = get_devices(dev); + + applog(LOG_DEBUG, "API: request to pgaenable pgaid %d device %d %s%u", + id, dev, cgpu->drv->name, cgpu->device_id); + + if (cgpu->deven != DEV_DISABLED) { + message(io_data, MSG_PGALRENA, id, NULL, isjson); + return; + } + +#if 0 /* A DISABLED device wont change status FIXME: should disabling make it WELL? */ + if (cgpu->status != LIFE_WELL) { + message(io_data, MSG_PGAUNW, id, NULL, isjson); + return; + } +#endif + +#ifdef USE_USBUTILS + if (cgpu->usbinfo.nodev) { + message(io_data, MSG_PGAUSBNODEV, id, NULL, isjson); + return; + } +#endif + + for (i = 0; i < mining_threads; i++) { + thr = get_thread(i); + pga = thr->cgpu->cgminer_id; + if (pga == dev) { + cgpu->deven = DEV_ENABLED; + applog(LOG_DEBUG, "API: Pushing sem post to thread %d", thr->id); + cgsem_post(&thr->sem); + } + } + + message(io_data, MSG_PGAENA, id, NULL, isjson); +} + +static void pgadisable(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group) +{ + struct cgpu_info *cgpu; + int numpga = numpgas(); + int id; + + if (numpga == 0) { + message(io_data, MSG_PGANON, 0, NULL, isjson); + return; + } + + if (param == NULL || *param == '\0') { + message(io_data, MSG_MISID, 0, NULL, isjson); + return; + } + + id = atoi(param); + if (id < 0 || id >= numpga) { + message(io_data, MSG_INVPGA, id, NULL, isjson); + return; + } + + int dev = pgadevice(id); + if (dev < 0) { // Should never happen + message(io_data, MSG_INVPGA, id, NULL, isjson); + return; + } + + cgpu = get_devices(dev); + + applog(LOG_DEBUG, "API: request to pgadisable pgaid %d device %d %s%u", + id, dev, cgpu->drv->name, cgpu->device_id); + + if (cgpu->deven == DEV_DISABLED) { + message(io_data, MSG_PGALRDIS, id, NULL, isjson); + return; + } + + cgpu->deven = DEV_DISABLED; + + message(io_data, MSG_PGADIS, id, NULL, isjson); +} + +static void pgaidentify(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group) +{ + struct cgpu_info *cgpu; + struct device_drv *drv; + int numpga = numpgas(); + int id; + + if (numpga == 0) { + message(io_data, MSG_PGANON, 0, NULL, isjson); + return; + } + + if (param == NULL || *param == '\0') { + message(io_data, MSG_MISID, 0, NULL, isjson); + return; + } + + id = atoi(param); + if (id < 0 || id >= numpga) { + message(io_data, MSG_INVPGA, id, NULL, isjson); + return; + } + + int dev = pgadevice(id); + if (dev < 0) { // Should never happen + message(io_data, MSG_INVPGA, id, NULL, isjson); + return; + } + + cgpu = get_devices(dev); + drv = cgpu->drv; + + if (!drv->identify_device) + message(io_data, MSG_PGANOID, id, NULL, isjson); + else { + drv->identify_device(cgpu); + message(io_data, MSG_PGAIDENT, id, NULL, isjson); + } +} +#endif + +static void poolstatus(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group) +{ + struct api_data *root = NULL; + bool io_open = false; + char *status, *lp; + int i; + int hour = 0; + int minute = 0; + int second = 0; + + char lasttime[256] = {0}; + long timediff = 0; + + if (total_pools == 0) { + message(io_data, MSG_NOPOOL, 0, NULL, isjson); + return; + } + + message(io_data, MSG_POOL, 0, NULL, isjson); + + if (isjson) + io_open = io_add(io_data, COMSTR JSON_POOLS); + + for (i = 0; i < total_pools; i++) { + struct pool *pool = pools[i]; + + if (pool->removed) + continue; + + switch (pool->enabled) { + case POOL_DISABLED: + status = (char *)DISABLED; + break; + case POOL_REJECTING: + status = (char *)REJECTING; + break; + case POOL_ENABLED: + if (pool->idle) + status = (char *)DEAD; + else + status = (char *)ALIVE; + break; + default: + status = (char *)UNKNOWN; + break; + } + + if (pool->hdr_path) + lp = (char *)YES; + else + lp = (char *)NO; + + if(pool->last_share_time <= 0) { + strcpy(lasttime, "0"); + } else { + timediff = time(NULL) - pool->last_share_time; + if(timediff < 0) + timediff = 0; + + hour = timediff / 3600; + minute = (timediff % 3600) / 60; + second = (timediff % 3600) % 60; + sprintf(lasttime, "%d:%02d:%02d", hour, minute, second); + } + + root = api_add_int(root, "POOL", &i, false); + root = api_add_escape(root, "URL", pool->rpc_url, false); + root = api_add_string(root, "Status", status, false); + root = api_add_int(root, "Priority", &(pool->prio), false); + root = api_add_int(root, "Quota", &pool->quota, false); + root = api_add_string(root, "Long Poll", lp, false); + root = api_add_uint(root, "Getworks", &(pool->getwork_requested), false); + root = api_add_int64(root, "Accepted", &(pool->accepted), false); + root = api_add_int64(root, "Rejected", &(pool->rejected), false); + //root = api_add_int(root, "Works", &pool->works, false); + root = api_add_uint(root, "Discarded", &(pool->discarded_work), false); + root = api_add_uint(root, "Stale", &(pool->stale_shares), false); + root = api_add_uint(root, "Get Failures", &(pool->getfail_occasions), false); + root = api_add_uint(root, "Remote Failures", &(pool->remotefail_occasions), false); + root = api_add_escape(root, "User", pool->rpc_user, false); + //root = api_add_time(root, "Last Share Time", &(pool->last_share_time), false); + root = api_add_string(root, "Last Share Time", lasttime, false); + root = api_add_string(root, "Diff", pool->diff, false); + root = api_add_int64(root, "Diff1 Shares", &(pool->diff1), false); + if (pool->rpc_proxy) { + root = api_add_const(root, "Proxy Type", proxytype(pool->rpc_proxytype), false); + root = api_add_escape(root, "Proxy", pool->rpc_proxy, false); + } else { + root = api_add_const(root, "Proxy Type", BLANK, false); + root = api_add_const(root, "Proxy", BLANK, false); + } + root = api_add_diff(root, "Difficulty Accepted", &(pool->diff_accepted), false); + root = api_add_diff(root, "Difficulty Rejected", &(pool->diff_rejected), false); + root = api_add_diff(root, "Difficulty Stale", &(pool->diff_stale), false); + root = api_add_diff(root, "Last Share Difficulty", &(pool->last_share_diff), false); + root = api_add_bool(root, "Has Stratum", &(pool->has_stratum), false); + root = api_add_bool(root, "Stratum Active", &(pool->stratum_active), false); + if (pool->stratum_active) + root = api_add_escape(root, "Stratum URL", pool->stratum_url, false); + else + root = api_add_const(root, "Stratum URL", BLANK, false); + root = api_add_bool(root, "Has GBT", &(pool->has_gbt), false); + root = api_add_uint64(root, "Best Share", &(pool->best_diff), true); + double rejp = (pool->diff_accepted + pool->diff_rejected + pool->diff_stale) ? + (double)(pool->diff_rejected) / (double)(pool->diff_accepted + pool->diff_rejected + pool->diff_stale) : 0; + root = api_add_percent(root, "Pool Rejected%", &rejp, false); + double stalep = (pool->diff_accepted + pool->diff_rejected + pool->diff_stale) ? + (double)(pool->diff_stale) / (double)(pool->diff_accepted + pool->diff_rejected + pool->diff_stale) : 0; + root = api_add_percent(root, "Pool Stale%", &stalep, false); + + root = print_data(io_data, root, isjson, isjson && (i > 0)); + } + + if (isjson && io_open) + io_close(io_data); +} + +static void lcddisplay(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group) +{ + struct api_data *root = NULL; + bool io_open = false; + char *status, *lp; + double ghs; + + char szindex[32] = {0}; + char szfan[32] = {0}; + char sztemp[32] = {0}; + char szpool[32] = {0}; + char szuser[32] = {0}; + + struct pool *pool = current_pool(); + + message(io_data, MSG_POOL, 0, NULL, isjson); + + if (isjson) + io_open = io_add(io_data, COMSTR JSON_POOLS); + + ghs = total_mhashes_done / 1000 / total_secs; + + strcpy(szindex, "0"); + root = api_add_string(root, "LCD", szindex, false); + + root = api_add_mhs(root, "GHS5s", &(g_displayed_rolling), false); + root = api_add_mhs(root, "GHSavg", &(ghs), false); + + sprintf(szfan, "%d", g_max_fan); + root = api_add_string(root, "fan", szfan, false); + sprintf(sztemp, "%d", g_max_temp); + root = api_add_string(root, "temp", sztemp, false); + + if(pool == NULL) { + strcpy(szpool, "no"); + strcpy(szuser, "no"); + root = api_add_string(root, "pool", szpool, false); + root = api_add_string(root, "user", szuser, false); + } else { + root = api_add_string(root, "pool", pool->rpc_url, false); + root = api_add_string(root, "user", pool->rpc_user, false); + } + + root = print_data(io_data, root, isjson, isjson); + + if (isjson && io_open) + io_close(io_data); +} + +static void summary(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group) +{ + struct api_data *root = NULL; + bool io_open; + double utility, ghs, work_utility; + + message(io_data, MSG_SUMM, 0, NULL, isjson); + io_open = io_add(io_data, isjson ? COMSTR JSON_SUMMARY : _SUMMARY COMSTR); + + // stop hashmeter() changing some while copying + mutex_lock(&hash_lock); +#ifdef USE_BITMAIN_C5 + total_diff1 = total_diff_accepted + total_diff_rejected + total_diff_stale; +#endif + + utility = total_accepted / ( total_secs ? total_secs : 1 ) * 60; + ghs = total_mhashes_done / 1000 / total_secs; + work_utility = total_diff1 / ( total_secs ? total_secs : 1 ) * 60; + + root = api_add_elapsed(root, "Elapsed", &(total_secs), true); +#ifndef USE_BITMAIN_C5 + root = api_add_mhs(root, "GHS 5s", &(g_displayed_rolling), false); +#else + root = api_add_string(root, "GHS 5s", displayed_hash_rate, false); +#endif + root = api_add_mhs(root, "GHS av", &(ghs), false); + root = api_add_uint(root, "Found Blocks", &(found_blocks), true); + root = api_add_int64(root, "Getworks", &(total_getworks), true); + root = api_add_int64(root, "Accepted", &(total_accepted), true); + root = api_add_int64(root, "Rejected", &(total_rejected), true); + root = api_add_int(root, "Hardware Errors", &(hw_errors), true); + root = api_add_utility(root, "Utility", &(utility), false); + root = api_add_int64(root, "Discarded", &(total_discarded), true); + root = api_add_int64(root, "Stale", &(total_stale), true); + root = api_add_uint(root, "Get Failures", &(total_go), true); + root = api_add_uint(root, "Local Work", &(local_work), true); + root = api_add_uint(root, "Remote Failures", &(total_ro), true); + root = api_add_uint(root, "Network Blocks", &(new_blocks), true); + root = api_add_mhtotal(root, "Total MH", &(total_mhashes_done), true); + root = api_add_utility(root, "Work Utility", &(work_utility), false); + root = api_add_diff(root, "Difficulty Accepted", &(total_diff_accepted), true); + root = api_add_diff(root, "Difficulty Rejected", &(total_diff_rejected), true); + root = api_add_diff(root, "Difficulty Stale", &(total_diff_stale), true); + root = api_add_uint64(root, "Best Share", &(best_diff), true); + double hwp = (hw_errors + total_diff1) ? + (double)(hw_errors) / (double)(hw_errors + total_diff1) : 0; + root = api_add_percent(root, "Device Hardware%", &hwp, false); + double rejp = total_diff1 ? + (double)(total_diff_rejected) / (double)(total_diff1) : 0; + root = api_add_percent(root, "Device Rejected%", &rejp, false); + double prejp = (total_diff_accepted + total_diff_rejected + total_diff_stale) ? + (double)(total_diff_rejected) / (double)(total_diff_accepted + total_diff_rejected + total_diff_stale) : 0; + root = api_add_percent(root, "Pool Rejected%", &prejp, false); + double stalep = (total_diff_accepted + total_diff_rejected + total_diff_stale) ? + (double)(total_diff_stale) / (double)(total_diff_accepted + total_diff_rejected + total_diff_stale) : 0; + root = api_add_percent(root, "Pool Stale%", &stalep, false); + root = api_add_time(root, "Last getwork", &last_getwork, false); + + mutex_unlock(&hash_lock); + + root = print_data(io_data, root, isjson, false); + if (isjson && io_open) + io_close(io_data); +} + +static void pgacount(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group) +{ + struct api_data *root = NULL; + bool io_open; + int count = 0; + +#ifdef HAVE_AN_FPGA + count = numpgas(); +#endif + + message(io_data, MSG_NUMPGA, 0, NULL, isjson); + io_open = io_add(io_data, isjson ? COMSTR JSON_PGAS : _PGAS COMSTR); + + root = api_add_int(root, "Count", &count, false); + + root = print_data(io_data, root, isjson, false); + if (isjson && io_open) + io_close(io_data); +} + +static void switchpool(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group) +{ + struct pool *pool; + int id; + + if (total_pools == 0) { + message(io_data, MSG_NOPOOL, 0, NULL, isjson); + return; + } + + if (param == NULL || *param == '\0') { + message(io_data, MSG_MISPID, 0, NULL, isjson); + return; + } + + id = atoi(param); + cg_rlock(&control_lock); + if (id < 0 || id >= total_pools) { + cg_runlock(&control_lock); + message(io_data, MSG_INVPID, id, NULL, isjson); + return; + } + + pool = pools[id]; + pool->enabled = POOL_ENABLED; + cg_runlock(&control_lock); + switch_pools(pool); + + message(io_data, MSG_SWITCHP, id, NULL, isjson); +} + +static void copyadvanceafter(char ch, char **param, char **buf) +{ +#define src_p (*param) +#define dst_b (*buf) + + while (*src_p && *src_p != ch) { + if (*src_p == '\\' && *(src_p+1) != '\0') + src_p++; + + *(dst_b++) = *(src_p++); + } + if (*src_p) + src_p++; + + *(dst_b++) = '\0'; +} + +static bool pooldetails(char *param, char **url, char **user, char **pass) +{ + char *ptr, *buf; + + ptr = buf = malloc(strlen(param)+1); + if (unlikely(!buf)) + quit(1, "Failed to malloc pooldetails buf"); + + *url = buf; + + // copy url + copyadvanceafter(',', ¶m, &buf); + + if (!(*param)) // missing user + goto exitsama; + + *user = buf; + + // copy user + copyadvanceafter(',', ¶m, &buf); + + if (!*param) // missing pass + goto exitsama; + + *pass = buf; + + // copy pass + copyadvanceafter(',', ¶m, &buf); + + return true; + +exitsama: + free(ptr); + return false; +} + +static void addpool(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group) +{ + char *url, *user, *pass; + struct pool *pool; + char *ptr; + + if (param == NULL || *param == '\0') { + message(io_data, MSG_MISPDP, 0, NULL, isjson); + return; + } + + if (!pooldetails(param, &url, &user, &pass)) { + ptr = escape_string(param, isjson); + message(io_data, MSG_INVPDP, 0, ptr, isjson); + if (ptr != param) + free(ptr); + ptr = NULL; + return; + } + + pool = add_pool(); + detect_stratum(pool, url); + add_pool_details(pool, true, url, user, pass); + + ptr = escape_string(url, isjson); + message(io_data, MSG_ADDPOOL, pool->pool_no, ptr, isjson); + if (ptr != url) + free(ptr); + ptr = NULL; +} + +static void enablepool(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group) +{ + struct pool *pool; + int id; + + if (total_pools == 0) { + message(io_data, MSG_NOPOOL, 0, NULL, isjson); + return; + } + + if (param == NULL || *param == '\0') { + message(io_data, MSG_MISPID, 0, NULL, isjson); + return; + } + + id = atoi(param); + if (id < 0 || id >= total_pools) { + message(io_data, MSG_INVPID, id, NULL, isjson); + return; + } + + pool = pools[id]; + if (pool->enabled == POOL_ENABLED) { + message(io_data, MSG_ALRENAP, id, NULL, isjson); + return; + } + + pool->enabled = POOL_ENABLED; + if (pool->prio < current_pool()->prio) + switch_pools(pool); + + message(io_data, MSG_ENAPOOL, id, NULL, isjson); +} + +static void poolpriority(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group) +{ + char *ptr, *next; + int i, pr, prio = 0; + + // TODO: all cgminer code needs a mutex added everywhere for change + // access to total_pools and also parts of the pools[] array, + // just copying total_pools here wont solve that + + if (total_pools == 0) { + message(io_data, MSG_NOPOOL, 0, NULL, isjson); + return; + } + + if (param == NULL || *param == '\0') { + message(io_data, MSG_MISPID, 0, NULL, isjson); + return; + } + + bool pools_changed[total_pools]; + int new_prio[total_pools]; + for (i = 0; i < total_pools; ++i) + pools_changed[i] = false; + + next = param; + while (next && *next) { + ptr = next; + next = strchr(ptr, ','); + if (next) + *(next++) = '\0'; + + i = atoi(ptr); + if (i < 0 || i >= total_pools) { + message(io_data, MSG_INVPID, i, NULL, isjson); + return; + } + + if (pools_changed[i]) { + message(io_data, MSG_DUPPID, i, NULL, isjson); + return; + } + + pools_changed[i] = true; + new_prio[i] = prio++; + } + + // Only change them if no errors + for (i = 0; i < total_pools; i++) { + if (pools_changed[i]) + pools[i]->prio = new_prio[i]; + } + + // In priority order, cycle through the unchanged pools and append them + for (pr = 0; pr < total_pools; pr++) + for (i = 0; i < total_pools; i++) { + if (!pools_changed[i] && pools[i]->prio == pr) { + pools[i]->prio = prio++; + pools_changed[i] = true; + break; + } + } + + if (current_pool()->prio) + switch_pools(NULL); + + message(io_data, MSG_POOLPRIO, 0, NULL, isjson); +} + +static void poolquota(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group) +{ + struct pool *pool; + int quota, id; + char *comma; + + if (total_pools == 0) { + message(io_data, MSG_NOPOOL, 0, NULL, isjson); + return; + } + + if (param == NULL || *param == '\0') { + message(io_data, MSG_MISPID, 0, NULL, isjson); + return; + } + + comma = strchr(param, ','); + if (!comma) { + message(io_data, MSG_CONVAL, 0, param, isjson); + return; + } + + *(comma++) = '\0'; + + id = atoi(param); + if (id < 0 || id >= total_pools) { + message(io_data, MSG_INVPID, id, NULL, isjson); + return; + } + pool = pools[id]; + + quota = atoi(comma); + if (quota < 0) { + message(io_data, MSG_INVNEG, quota, pool->rpc_url, isjson); + return; + } + + pool->quota = quota; + adjust_quota_gcd(); + message(io_data, MSG_SETQUOTA, quota, pool->rpc_url, isjson); +} + +static void disablepool(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group) +{ + struct pool *pool; + int id; + + if (total_pools == 0) { + message(io_data, MSG_NOPOOL, 0, NULL, isjson); + return; + } + + if (param == NULL || *param == '\0') { + message(io_data, MSG_MISPID, 0, NULL, isjson); + return; + } + + id = atoi(param); + if (id < 0 || id >= total_pools) { + message(io_data, MSG_INVPID, id, NULL, isjson); + return; + } + + pool = pools[id]; + if (pool->enabled == POOL_DISABLED) { + message(io_data, MSG_ALRDISP, id, NULL, isjson); + return; + } + + if (enabled_pools <= 1) { + message(io_data, MSG_DISLASTP, id, NULL, isjson); + return; + } + + pool->enabled = POOL_DISABLED; + if (pool == current_pool()) + switch_pools(NULL); + + message(io_data, MSG_DISPOOL, id, NULL, isjson); +} + +static void removepool(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group) +{ + struct pool *pool; + char *rpc_url; + bool dofree = false; + int id; + + if (total_pools == 0) { + message(io_data, MSG_NOPOOL, 0, NULL, isjson); + return; + } + + if (param == NULL || *param == '\0') { + message(io_data, MSG_MISPID, 0, NULL, isjson); + return; + } + + id = atoi(param); + if (id < 0 || id >= total_pools) { + message(io_data, MSG_INVPID, id, NULL, isjson); + return; + } + + if (total_pools <= 1) { + message(io_data, MSG_REMLASTP, id, NULL, isjson); + return; + } + + pool = pools[id]; + if (pool == current_pool()) + switch_pools(NULL); + + if (pool == current_pool()) { + message(io_data, MSG_ACTPOOL, id, NULL, isjson); + return; + } + + pool->enabled = POOL_DISABLED; + rpc_url = escape_string(pool->rpc_url, isjson); + if (rpc_url != pool->rpc_url) + dofree = true; + + remove_pool(pool); + + message(io_data, MSG_REMPOOL, id, rpc_url, isjson); + + if (dofree) + free(rpc_url); + rpc_url = NULL; +} + +void doquit(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group) +{ + if (isjson) + io_put(io_data, JSON_ACTION JSON_BYE); + else + io_put(io_data, _BYE); + + bye = true; + do_a_quit = true; +} + +void dorestart(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group) +{ + if (isjson) + io_put(io_data, JSON_ACTION JSON_RESTART); + else + io_put(io_data, _RESTART); + + bye = true; + do_a_restart = true; +} + +void privileged(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group) +{ + message(io_data, MSG_ACCOK, 0, NULL, isjson); +} + +void notifystatus(struct io_data *io_data, int device, struct cgpu_info *cgpu, bool isjson, __maybe_unused char group) +{ + struct api_data *root = NULL; + char *reason; + + if (cgpu->device_last_not_well == 0) + reason = REASON_NONE; + else + switch(cgpu->device_not_well_reason) { + case REASON_THREAD_FAIL_INIT: + reason = REASON_THREAD_FAIL_INIT_STR; + break; + case REASON_THREAD_ZERO_HASH: + reason = REASON_THREAD_ZERO_HASH_STR; + break; + case REASON_THREAD_FAIL_QUEUE: + reason = REASON_THREAD_FAIL_QUEUE_STR; + break; + case REASON_DEV_SICK_IDLE_60: + reason = REASON_DEV_SICK_IDLE_60_STR; + break; + case REASON_DEV_DEAD_IDLE_600: + reason = REASON_DEV_DEAD_IDLE_600_STR; + break; + case REASON_DEV_NOSTART: + reason = REASON_DEV_NOSTART_STR; + break; + case REASON_DEV_OVER_HEAT: + reason = REASON_DEV_OVER_HEAT_STR; + break; + case REASON_DEV_THERMAL_CUTOFF: + reason = REASON_DEV_THERMAL_CUTOFF_STR; + break; + case REASON_DEV_COMMS_ERROR: + reason = REASON_DEV_COMMS_ERROR_STR; + break; + default: + reason = REASON_UNKNOWN_STR; + break; + } + + // ALL counters (and only counters) must start the name with a '*' + // Simplifies future external support for identifying new counters + root = api_add_int(root, "NOTIFY", &device, false); + root = api_add_string(root, "Name", cgpu->drv->name, false); + root = api_add_int(root, "ID", &(cgpu->device_id), false); + root = api_add_time(root, "Last Well", &(cgpu->device_last_well), false); + root = api_add_time(root, "Last Not Well", &(cgpu->device_last_not_well), false); + root = api_add_string(root, "Reason Not Well", reason, false); + root = api_add_int(root, "*Thread Fail Init", &(cgpu->thread_fail_init_count), false); + root = api_add_int(root, "*Thread Zero Hash", &(cgpu->thread_zero_hash_count), false); + root = api_add_int(root, "*Thread Fail Queue", &(cgpu->thread_fail_queue_count), false); + root = api_add_int(root, "*Dev Sick Idle 60s", &(cgpu->dev_sick_idle_60_count), false); + root = api_add_int(root, "*Dev Dead Idle 600s", &(cgpu->dev_dead_idle_600_count), false); + root = api_add_int(root, "*Dev Nostart", &(cgpu->dev_nostart_count), false); + root = api_add_int(root, "*Dev Over Heat", &(cgpu->dev_over_heat_count), false); + root = api_add_int(root, "*Dev Thermal Cutoff", &(cgpu->dev_thermal_cutoff_count), false); + root = api_add_int(root, "*Dev Comms Error", &(cgpu->dev_comms_error_count), false); + root = api_add_int(root, "*Dev Throttle", &(cgpu->dev_throttle_count), false); + + root = print_data(io_data, root, isjson, isjson && (device > 0)); +} + +static void notify(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, char group) +{ + struct cgpu_info *cgpu; + bool io_open = false; + int i; + + if (total_devices == 0) { + message(io_data, MSG_NODEVS, 0, NULL, isjson); + return; + } + + message(io_data, MSG_NOTIFY, 0, NULL, isjson); + + if (isjson) + io_open = io_add(io_data, COMSTR JSON_NOTIFY); + + for (i = 0; i < total_devices; i++) { + cgpu = get_devices(i); + notifystatus(io_data, i, cgpu, isjson, group); + } + + if (isjson && io_open) + io_close(io_data); +} + +static void devdetails(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group) +{ + struct api_data *root = NULL; + bool io_open = false; + struct cgpu_info *cgpu; + int i; + + if (total_devices == 0) { + message(io_data, MSG_NODEVS, 0, NULL, isjson); + return; + } + + message(io_data, MSG_DEVDETAILS, 0, NULL, isjson); + + if (isjson) + io_open = io_add(io_data, COMSTR JSON_DEVDETAILS); + + for (i = 0; i < total_devices; i++) { + cgpu = get_devices(i); + + root = api_add_int(root, "DEVDETAILS", &i, false); + root = api_add_string(root, "Name", cgpu->drv->name, false); + root = api_add_int(root, "ID", &(cgpu->device_id), false); + root = api_add_string(root, "Driver", cgpu->drv->dname, false); + root = api_add_const(root, "Kernel", cgpu->kname ? : BLANK, false); + root = api_add_const(root, "Model", cgpu->name ? : BLANK, false); + root = api_add_const(root, "Device Path", cgpu->device_path ? : BLANK, false); + + root = print_data(io_data, root, isjson, isjson && (i > 0)); + } + + if (isjson && io_open) + io_close(io_data); +} + +void dosave(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group) +{ + char filename[PATH_MAX]; + FILE *fcfg; + char *ptr; + + if (param == NULL || *param == '\0') { + default_save_file(filename); + param = filename; + } + + fcfg = fopen(param, "w"); + if (!fcfg) { + ptr = escape_string(param, isjson); + message(io_data, MSG_BADFN, 0, ptr, isjson); + if (ptr != param) + free(ptr); + ptr = NULL; + return; + } + + write_config(fcfg); + fclose(fcfg); + + ptr = escape_string(param, isjson); + message(io_data, MSG_SAVED, 0, ptr, isjson); + if (ptr != param) + free(ptr); + ptr = NULL; +} + +static int itemstats(struct io_data *io_data, int i, char *id, struct cgminer_stats *stats, struct cgminer_pool_stats *pool_stats, struct api_data *extra, struct cgpu_info *cgpu, bool isjson) +{ + struct api_data *root = NULL; + double ghs; + + ghs = total_mhashes_done / 1000 / total_secs; + + root = api_add_int(root, "STATS", &i, false); + root = api_add_string(root, "ID", id, false); + root = api_add_elapsed(root, "Elapsed", &(total_secs), false); + root = api_add_uint32(root, "Calls", &(stats->getwork_calls), false); + root = api_add_timeval(root, "Wait", &(stats->getwork_wait), false); + root = api_add_timeval(root, "Max", &(stats->getwork_wait_max), false); + root = api_add_timeval(root, "Min", &(stats->getwork_wait_min), false); + root = api_add_mhs(root, "GHS 5s", &(g_displayed_rolling), false); + root = api_add_mhs(root, "GHS av", &(ghs), false); + + /* + if (pool_stats) { + root = api_add_uint32(root, "Pool Calls", &(pool_stats->getwork_calls), false); + root = api_add_uint32(root, "Pool Attempts", &(pool_stats->getwork_attempts), false); + root = api_add_timeval(root, "Pool Wait", &(pool_stats->getwork_wait), false); + root = api_add_timeval(root, "Pool Max", &(pool_stats->getwork_wait_max), false); + root = api_add_timeval(root, "Pool Min", &(pool_stats->getwork_wait_min), false); + root = api_add_double(root, "Pool Av", &(pool_stats->getwork_wait_rolling), false); + root = api_add_bool(root, "Work Had Roll Time", &(pool_stats->hadrolltime), false); + root = api_add_bool(root, "Work Can Roll", &(pool_stats->canroll), false); + root = api_add_bool(root, "Work Had Expire", &(pool_stats->hadexpire), false); + root = api_add_uint32(root, "Work Roll Time", &(pool_stats->rolltime), false); + root = api_add_diff(root, "Work Diff", &(pool_stats->last_diff), false); + root = api_add_diff(root, "Min Diff", &(pool_stats->min_diff), false); + root = api_add_diff(root, "Max Diff", &(pool_stats->max_diff), false); + root = api_add_uint32(root, "Min Diff Count", &(pool_stats->min_diff_count), false); + root = api_add_uint32(root, "Max Diff Count", &(pool_stats->max_diff_count), false); + root = api_add_uint64(root, "Times Sent", &(pool_stats->times_sent), false); + root = api_add_uint64(root, "Bytes Sent", &(pool_stats->bytes_sent), false); + root = api_add_uint64(root, "Times Recv", &(pool_stats->times_received), false); + root = api_add_uint64(root, "Bytes Recv", &(pool_stats->bytes_received), false); + root = api_add_uint64(root, "Net Bytes Sent", &(pool_stats->net_bytes_sent), false); + root = api_add_uint64(root, "Net Bytes Recv", &(pool_stats->net_bytes_received), false); + }*/ + + if (extra) + root = api_add_extra(root, extra); + + if (cgpu) { +#ifdef USE_USBUTILS + char details[256]; + + if (cgpu->usbinfo.pipe_count) + snprintf(details, sizeof(details), + "%"PRIu64" %"PRIu64"/%"PRIu64"/%"PRIu64" %lu", + cgpu->usbinfo.pipe_count, + cgpu->usbinfo.clear_err_count, + cgpu->usbinfo.retry_err_count, + cgpu->usbinfo.clear_fail_count, + (unsigned long)(cgpu->usbinfo.last_pipe)); + else + strcpy(details, "0"); + + root = api_add_string(root, "USB Pipe", details, true); + + /* + snprintf(details, sizeof(details), + "r%"PRIu64" %.6f w%"PRIu64" %.6f", + cgpu->usbinfo.read_delay_count, + cgpu->usbinfo.total_read_delay, + cgpu->usbinfo.write_delay_count, + cgpu->usbinfo.total_write_delay); + + root = api_add_string(root, "USB Delay", details, true); + + if (cgpu->usbinfo.usb_tmo[0].count == 0 && + cgpu->usbinfo.usb_tmo[1].count == 0 && + cgpu->usbinfo.usb_tmo[2].count == 0) { + snprintf(details, sizeof(details), + "%"PRIu64" 0", cgpu->usbinfo.tmo_count); + } else { + snprintf(details, sizeof(details), + "%"PRIu64" %d=%d/%d/%d/%"PRIu64"/%"PRIu64 + " %d=%d/%d/%d/%"PRIu64"/%"PRIu64 + " %d=%d/%d/%d/%"PRIu64"/%"PRIu64" ", + cgpu->usbinfo.tmo_count, + USB_TMO_0, cgpu->usbinfo.usb_tmo[0].count, + cgpu->usbinfo.usb_tmo[0].min_tmo, + cgpu->usbinfo.usb_tmo[0].max_tmo, + cgpu->usbinfo.usb_tmo[0].total_over, + cgpu->usbinfo.usb_tmo[0].total_tmo, + USB_TMO_1, cgpu->usbinfo.usb_tmo[1].count, + cgpu->usbinfo.usb_tmo[1].min_tmo, + cgpu->usbinfo.usb_tmo[1].max_tmo, + cgpu->usbinfo.usb_tmo[1].total_over, + cgpu->usbinfo.usb_tmo[1].total_tmo, + USB_TMO_2, cgpu->usbinfo.usb_tmo[2].count, + cgpu->usbinfo.usb_tmo[2].min_tmo, + cgpu->usbinfo.usb_tmo[2].max_tmo, + cgpu->usbinfo.usb_tmo[2].total_over, + cgpu->usbinfo.usb_tmo[2].total_tmo); + } + + root = api_add_string(root, "USB tmo", details, true);*/ +#endif + } + + root = print_data(io_data, root, isjson, isjson && (i > 0)); + + return ++i; +} + +static void minerstats(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group) +{ + struct api_data *root = NULL; + struct cgpu_info *cgpu; + bool io_open = false; + struct api_data *extra; + char id[20]; + int i, j; + + message(io_data, MSG_MINESTATS, 0, NULL, isjson); + + if (isjson) + io_open = io_add(io_data, COMSTR JSON_MINESTATS); + + root = api_add_string(root, "BMMiner", VERSION, false); + root = api_add_string(root, "Miner", g_miner_version, false); + root = api_add_string(root, "CompileTime", g_miner_compiletime, false); + root = api_add_string(root, "Type", g_miner_type, false); + root = print_data(io_data, root, isjson, false); + + i = 0; + for (j = 0; j < total_devices; j++) { + cgpu = get_devices(j); + + if (cgpu && cgpu->drv) { + if (cgpu->drv->get_api_stats) + extra = cgpu->drv->get_api_stats(cgpu); + else + extra = NULL; + + sprintf(id, "%s%d", cgpu->drv->name, cgpu->device_id); + i = itemstats(io_data, i, id, &(cgpu->cgminer_stats), NULL, extra, cgpu, isjson); + } + } + + if (isjson && io_open) + io_close(io_data); +} + +static void minerestats(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group) +{ + struct cgpu_info *cgpu; + bool io_open = false; + struct api_data *extra; + char id[20]; + int i, j; +#ifdef USE_USBUTILS + time_t howoldsec = 0; + + if (param && *param) + howoldsec = (time_t)atoi(param); +#endif + + message(io_data, MSG_MINESTATS, 0, NULL, isjson); + if (isjson) + io_open = io_add(io_data, COMSTR JSON_MINESTATS); + + i = 0; + for (j = 0; j < total_devices; j++) { + cgpu = get_devices(j); + if (!cgpu) + continue; +#ifdef USE_USBUTILS + if (cgpu->blacklisted) + continue; + if (cgpu->usbinfo.nodev) { + if (howoldsec <= 0) + continue; + if ((when - cgpu->usbinfo.last_nodev.tv_sec) >= howoldsec) + continue; + } +#endif + if (cgpu->drv) { + if (cgpu->drv->get_api_stats) + extra = cgpu->drv->get_api_stats(cgpu); + else + extra = NULL; + + sprintf(id, "%s%d", cgpu->drv->name, cgpu->device_id); + i = itemstats(io_data, i, id, &(cgpu->cgminer_stats), NULL, extra, cgpu, isjson); + } + } + + if (isjson && io_open) + io_close(io_data); +} + +static void failoveronly(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group) +{ + if (param == NULL || *param == '\0') { + message(io_data, MSG_MISBOOL, 0, NULL, isjson); + return; + } + + *param = tolower(*param); + + if (*param != 't' && *param != 'f') { + message(io_data, MSG_INVBOOL, 0, NULL, isjson); + return; + } + + bool tf = (*param == 't'); + + opt_fail_only = tf; + + message(io_data, MSG_FOO, tf, NULL, isjson); +} + +static void minecoin(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group) +{ + struct api_data *root = NULL; + bool io_open; + + message(io_data, MSG_MINECOIN, 0, NULL, isjson); + io_open = io_add(io_data, isjson ? COMSTR JSON_MINECOIN : _MINECOIN COMSTR); + + root = api_add_const(root, "Hash Method", SHA256STR, false); + + cg_rlock(&ch_lock); + root = api_add_timeval(root, "Current Block Time", &block_timeval, true); + root = api_add_string(root, "Current Block Hash", current_hash, true); + cg_runlock(&ch_lock); + + root = api_add_bool(root, "LP", &have_longpoll, false); + root = api_add_diff(root, "Network Difficulty", ¤t_diff, true); + + root = print_data(io_data, root, isjson, false); + if (isjson && io_open) + io_close(io_data); +} + +static void debugstate(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group) +{ + struct api_data *root = NULL; + bool io_open; + + if (param == NULL) + param = (char *)BLANK; + else + *param = tolower(*param); + + switch(*param) { + case 's': + opt_realquiet = true; + break; + case 'q': + opt_quiet ^= true; + break; + case 'v': + opt_log_output ^= true; + if (opt_log_output) + opt_quiet = false; + break; + case 'd': + opt_debug ^= true; + opt_log_output = opt_debug; + if (opt_debug) + opt_quiet = false; + break; + case 'r': + opt_protocol ^= true; + if (opt_protocol) + opt_quiet = false; + break; + case 'p': + want_per_device_stats ^= true; + opt_log_output = want_per_device_stats; + break; + case 'n': + opt_log_output = false; + opt_debug = false; + opt_quiet = false; + opt_protocol = false; + want_per_device_stats = false; + opt_worktime = false; + break; + case 'w': + opt_worktime ^= true; + break; +#ifdef _MEMORY_DEBUG + case 'y': + cgmemspeedup(); + break; + case 'z': + cgmemrpt(); + break; +#endif + default: + // anything else just reports the settings + break; + } + + message(io_data, MSG_DEBUGSET, 0, NULL, isjson); + io_open = io_add(io_data, isjson ? COMSTR JSON_DEBUGSET : _DEBUGSET COMSTR); + + root = api_add_bool(root, "Silent", &opt_realquiet, false); + root = api_add_bool(root, "Quiet", &opt_quiet, false); + root = api_add_bool(root, "Verbose", &opt_log_output, false); + root = api_add_bool(root, "Debug", &opt_debug, false); + root = api_add_bool(root, "RPCProto", &opt_protocol, false); + root = api_add_bool(root, "PerDevice", &want_per_device_stats, false); + root = api_add_bool(root, "WorkTime", &opt_worktime, false); + + root = print_data(io_data, root, isjson, false); + if (isjson && io_open) + io_close(io_data); +} + +static void setconfig(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group) +{ + char *comma; + int value; + + if (param == NULL || *param == '\0') { + message(io_data, MSG_CONPAR, 0, NULL, isjson); + return; + } + + comma = strchr(param, ','); + if (!comma) { + message(io_data, MSG_CONVAL, 0, param, isjson); + return; + } + + *(comma++) = '\0'; + value = atoi(comma); + if (value < 0 || value > 9999) { + message(io_data, MSG_INVNUM, value, param, isjson); + return; + } + + if (strcasecmp(param, "queue") == 0) + opt_queue = value; + else if (strcasecmp(param, "scantime") == 0) + opt_scantime = value; + else if (strcasecmp(param, "expiry") == 0) + opt_expiry = value; + else { + message(io_data, MSG_UNKCON, 0, param, isjson); + return; + } + + message(io_data, MSG_SETCONFIG, value, param, isjson); +} + +static void usbstats(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group) +{ + struct api_data *root = NULL; + +#ifdef USE_USBUTILS + bool io_open = false; + int count = 0; + + root = api_usb_stats(&count); +#endif + + if (!root) { + message(io_data, MSG_NOUSTA, 0, NULL, isjson); + return; + } + +#ifdef USE_USBUTILS + message(io_data, MSG_USBSTA, 0, NULL, isjson); + + if (isjson) + io_open = io_add(io_data, COMSTR JSON_USBSTATS); + + root = print_data(io_data, root, isjson, false); + + while (42) { + root = api_usb_stats(&count); + if (!root) + break; + + root = print_data(io_data, root, isjson, isjson); + } + + if (isjson && io_open) + io_close(io_data); +#endif +} + +#ifdef HAVE_AN_FPGA +static void pgaset(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group) +{ + struct cgpu_info *cgpu; + struct device_drv *drv; + char buf[TMPBUFSIZ]; + int numpga = numpgas(); + + if (numpga == 0) { + message(io_data, MSG_PGANON, 0, NULL, isjson); + return; + } + + if (param == NULL || *param == '\0') { + message(io_data, MSG_MISID, 0, NULL, isjson); + return; + } + + char *opt = strchr(param, ','); + if (opt) + *(opt++) = '\0'; + if (!opt || !*opt) { + message(io_data, MSG_MISPGAOPT, 0, NULL, isjson); + return; + } + + int id = atoi(param); + if (id < 0 || id >= numpga) { + message(io_data, MSG_INVPGA, id, NULL, isjson); + return; + } + + int dev = pgadevice(id); + if (dev < 0) { // Should never happen + message(io_data, MSG_INVPGA, id, NULL, isjson); + return; + } + + cgpu = get_devices(dev); + drv = cgpu->drv; + + char *set = strchr(opt, ','); + if (set) + *(set++) = '\0'; + + if (!drv->set_device) + message(io_data, MSG_PGANOSET, id, NULL, isjson); + else { + char *ret = drv->set_device(cgpu, opt, set, buf); + if (ret) { + if (strcasecmp(opt, "help") == 0) + message(io_data, MSG_PGAHELP, id, ret, isjson); + else + message(io_data, MSG_PGASETERR, id, ret, isjson); + } else + message(io_data, MSG_PGASETOK, id, NULL, isjson); + } +} +#endif + +static void dozero(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group) +{ + if (param == NULL || *param == '\0') { + message(io_data, MSG_ZERMIS, 0, NULL, isjson); + return; + } + + char *sum = strchr(param, ','); + if (sum) + *(sum++) = '\0'; + if (!sum || !*sum) { + message(io_data, MSG_MISBOOL, 0, NULL, isjson); + return; + } + + bool all = false; + bool bs = false; + if (strcasecmp(param, "all") == 0) + all = true; + else if (strcasecmp(param, "bestshare") == 0) + bs = true; + + if (all == false && bs == false) { + message(io_data, MSG_ZERINV, 0, param, isjson); + return; + } + + *sum = tolower(*sum); + if (*sum != 't' && *sum != 'f') { + message(io_data, MSG_INVBOOL, 0, NULL, isjson); + return; + } + + bool dosum = (*sum == 't'); + if (dosum) + print_summary(); + + if (all) + zero_stats(); + if (bs) + zero_bestshare(); + + if (dosum) + message(io_data, MSG_ZERSUM, 0, all ? "All" : "BestShare", isjson); + else + message(io_data, MSG_ZERNOSUM, 0, all ? "All" : "BestShare", isjson); +} + +static void dohotplug(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group) +{ +#ifdef USE_USBUTILS + int value; + + if (param == NULL || *param == '\0') { + message(io_data, MSG_MISHPLG, 0, NULL, isjson); + return; + } + + value = atoi(param); + if (value < 0 || value > 9999) { + message(io_data, MSG_INVHPLG, 0, param, isjson); + return; + } + + hotplug_time = value; + + if (value) + message(io_data, MSG_HOTPLUG, value, NULL, isjson); + else + message(io_data, MSG_DISHPLG, 0, NULL, isjson); +#else + message(io_data, MSG_NOHPLG, 0, NULL, isjson); + return; +#endif +} + +#ifdef HAVE_AN_ASIC +static void ascdev(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group) +{ + bool io_open = false; + int numasc = numascs(); + int id; + + if (numasc == 0) { + message(io_data, MSG_ASCNON, 0, NULL, isjson); + return; + } + + if (param == NULL || *param == '\0') { + message(io_data, MSG_MISID, 0, NULL, isjson); + return; + } + + id = atoi(param); + if (id < 0 || id >= numasc) { + message(io_data, MSG_INVASC, id, NULL, isjson); + return; + } + + message(io_data, MSG_ASCDEV, id, NULL, isjson); + + if (isjson) + io_open = io_add(io_data, COMSTR JSON_ASC); + + ascstatus(io_data, id, isjson, false); + + if (isjson && io_open) + io_close(io_data); +} + +static void ascenable(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group) +{ + struct cgpu_info *cgpu; + int numasc = numascs(); + struct thr_info *thr; + int asc; + int id; + int i; + + if (numasc == 0) { + message(io_data, MSG_ASCNON, 0, NULL, isjson); + return; + } + + if (param == NULL || *param == '\0') { + message(io_data, MSG_MISID, 0, NULL, isjson); + return; + } + + id = atoi(param); + if (id < 0 || id >= numasc) { + message(io_data, MSG_INVASC, id, NULL, isjson); + return; + } + + int dev = ascdevice(id); + if (dev < 0) { // Should never happen + message(io_data, MSG_INVASC, id, NULL, isjson); + return; + } + + cgpu = get_devices(dev); + + applog(LOG_DEBUG, "API: request to ascenable ascid %d device %d %s%u", + id, dev, cgpu->drv->name, cgpu->device_id); + + if (cgpu->deven != DEV_DISABLED) { + message(io_data, MSG_ASCLRENA, id, NULL, isjson); + return; + } + +#if 0 /* A DISABLED device wont change status FIXME: should disabling make it WELL? */ + if (cgpu->status != LIFE_WELL) { + message(io_data, MSG_ASCUNW, id, NULL, isjson); + return; + } +#endif + +#ifdef USE_USBUTILS + if (cgpu->usbinfo.nodev) { + message(io_data, MSG_ASCUSBNODEV, id, NULL, isjson); + return; + } +#endif + + for (i = 0; i < mining_threads; i++) { + thr = get_thread(i); + asc = thr->cgpu->cgminer_id; + if (asc == dev) { + cgpu->deven = DEV_ENABLED; + applog(LOG_DEBUG, "API: Pushing sem post to thread %d", thr->id); + cgsem_post(&thr->sem); + } + } + + message(io_data, MSG_ASCENA, id, NULL, isjson); +} + +static void ascdisable(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group) +{ + struct cgpu_info *cgpu; + int numasc = numascs(); + int id; + + if (numasc == 0) { + message(io_data, MSG_ASCNON, 0, NULL, isjson); + return; + } + + if (param == NULL || *param == '\0') { + message(io_data, MSG_MISID, 0, NULL, isjson); + return; + } + + id = atoi(param); + if (id < 0 || id >= numasc) { + message(io_data, MSG_INVASC, id, NULL, isjson); + return; + } + + int dev = ascdevice(id); + if (dev < 0) { // Should never happen + message(io_data, MSG_INVASC, id, NULL, isjson); + return; + } + + cgpu = get_devices(dev); + + applog(LOG_DEBUG, "API: request to ascdisable ascid %d device %d %s%u", + id, dev, cgpu->drv->name, cgpu->device_id); + + if (cgpu->deven == DEV_DISABLED) { + message(io_data, MSG_ASCLRDIS, id, NULL, isjson); + return; + } + + cgpu->deven = DEV_DISABLED; + + message(io_data, MSG_ASCDIS, id, NULL, isjson); +} + +static void ascidentify(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group) +{ + struct cgpu_info *cgpu; + struct device_drv *drv; + int numasc = numascs(); + int id; + + if (numasc == 0) { + message(io_data, MSG_ASCNON, 0, NULL, isjson); + return; + } + + if (param == NULL || *param == '\0') { + message(io_data, MSG_MISID, 0, NULL, isjson); + return; + } + + id = atoi(param); + if (id < 0 || id >= numasc) { + message(io_data, MSG_INVASC, id, NULL, isjson); + return; + } + + int dev = ascdevice(id); + if (dev < 0) { // Should never happen + message(io_data, MSG_INVASC, id, NULL, isjson); + return; + } + + cgpu = get_devices(dev); + drv = cgpu->drv; + + if (!drv->identify_device) + message(io_data, MSG_ASCNOID, id, NULL, isjson); + else { + drv->identify_device(cgpu); + message(io_data, MSG_ASCIDENT, id, NULL, isjson); + } +} +#endif + +static void asccount(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group) +{ + struct api_data *root = NULL; + bool io_open; + int count = 0; + +#ifdef HAVE_AN_ASIC + count = numascs(); +#endif + + message(io_data, MSG_NUMASC, 0, NULL, isjson); + io_open = io_add(io_data, isjson ? COMSTR JSON_ASCS : _ASCS COMSTR); + + root = api_add_int(root, "Count", &count, false); + + root = print_data(io_data, root, isjson, false); + if (isjson && io_open) + io_close(io_data); +} + +#ifdef HAVE_AN_ASIC +static void ascset(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group) +{ + struct cgpu_info *cgpu; + struct device_drv *drv; + char buf[TMPBUFSIZ]; + int numasc = numascs(); + + if (numasc == 0) { + message(io_data, MSG_ASCNON, 0, NULL, isjson); + return; + } + + if (param == NULL || *param == '\0') { + message(io_data, MSG_MISID, 0, NULL, isjson); + return; + } + + char *opt = strchr(param, ','); + if (opt) + *(opt++) = '\0'; + if (!opt || !*opt) { + message(io_data, MSG_MISASCOPT, 0, NULL, isjson); + return; + } + + int id = atoi(param); + if (id < 0 || id >= numasc) { + message(io_data, MSG_INVASC, id, NULL, isjson); + return; + } + + int dev = ascdevice(id); + if (dev < 0) { // Should never happen + message(io_data, MSG_INVASC, id, NULL, isjson); + return; + } + + cgpu = get_devices(dev); + drv = cgpu->drv; + + char *set = strchr(opt, ','); + if (set) + *(set++) = '\0'; + + if (!drv->set_device) + message(io_data, MSG_ASCNOSET, id, NULL, isjson); + else { + char *ret = drv->set_device(cgpu, opt, set, buf); + if (ret) { + if (strcasecmp(opt, "help") == 0) + message(io_data, MSG_ASCHELP, id, ret, isjson); + else + message(io_data, MSG_ASCSETERR, id, ret, isjson); + } else + message(io_data, MSG_ASCSETOK, id, NULL, isjson); + } +} +#endif + +static void lcddata(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group) +{ + struct api_data *root = NULL; + struct cgpu_info *cgpu; + bool io_open; + double ghs = 0.0, last_share_diff = 0.0; + float temp = 0.0; + time_t last_share_time = 0; + time_t last_device_valid_work = 0; + struct pool *pool = NULL; + char *rpc_url = "none", *rpc_user = ""; + int i; + + message(io_data, MSG_LCD, 0, NULL, isjson); + io_open = io_add(io_data, isjson ? COMSTR JSON_LCD : _LCD COMSTR); + + // stop hashmeter() changing some while copying + mutex_lock(&hash_lock); + + root = api_add_elapsed(root, "Elapsed", &(total_secs), true); + ghs = total_mhashes_done / total_secs / 1000.0; + root = api_add_mhs(root, "GHS av", &ghs, true); + ghs = rolling5 / 1000.0; + root = api_add_mhs(root, "GHS 5m", &ghs, true); + ghs = total_rolling / 1000.0; + root = api_add_mhs(root, "GHS 5s", &ghs, true); + + mutex_unlock(&hash_lock); + + temp = 0; + last_device_valid_work = 0; + for (i = 0; i < total_devices; i++) { + cgpu = get_devices(i); + if (last_device_valid_work == 0 || + last_device_valid_work < cgpu->last_device_valid_work) + last_device_valid_work = cgpu->last_device_valid_work; + if (temp < cgpu->temp) + temp = cgpu->temp; + } + + last_share_time = 0; + last_share_diff = 0; + for (i = 0; i < total_pools; i++) { + pool = pools[i]; + + if (pool->removed) + continue; + + if (last_share_time == 0 || last_share_time < pool->last_share_time) { + last_share_time = pool->last_share_time; + last_share_diff = pool->last_share_diff; + } + } + pool = current_pool(); + if (pool) { + rpc_url = pool->rpc_url; + rpc_user = pool->rpc_user; + } + + root = api_add_temp(root, "Temperature", &temp, false); + root = api_add_diff(root, "Last Share Difficulty", &last_share_diff, false); + root = api_add_time(root, "Last Share Time", &last_share_time, false); + root = api_add_uint64(root, "Best Share", &best_diff, true); + root = api_add_time(root, "Last Valid Work", &last_device_valid_work, false); + root = api_add_uint(root, "Found Blocks", &found_blocks, true); + root = api_add_escape(root, "Current Pool", rpc_url, true); + root = api_add_escape(root, "User", rpc_user, true); + + root = print_data(io_data, root, isjson, false); + if (isjson && io_open) + io_close(io_data); +} + +static void checkcommand(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, char group); + +struct CMDS { + char *name; + void (*func)(struct io_data *, SOCKETTYPE, char *, bool, char); + bool iswritemode; + bool joinable; +} cmds[] = { + { "version", apiversion, false, true }, + { "config", minerconfig, false, true }, + { "devs", devstatus, false, true }, + { "edevs", edevstatus, false, true }, + { "pools", poolstatus, false, true }, + { "summary", summary, false, true }, +#ifdef HAVE_AN_FPGA + { "pga", pgadev, false, false }, + { "pgaenable", pgaenable, true, false }, + { "pgadisable", pgadisable, true, false }, + { "pgaidentify", pgaidentify, true, false }, +#endif + { "pgacount", pgacount, false, true }, + { "switchpool", switchpool, true, false }, + { "addpool", addpool, true, false }, + { "poolpriority", poolpriority, true, false }, + { "poolquota", poolquota, true, false }, + { "enablepool", enablepool, true, false }, + { "disablepool", disablepool, true, false }, + { "removepool", removepool, true, false }, + { "save", dosave, true, false }, + { "quit", doquit, true, false }, + { "privileged", privileged, true, false }, + { "notify", notify, false, true }, + { "devdetails", devdetails, false, true }, + { "restart", dorestart, true, false }, + { "stats", minerstats, false, true }, + { "estats", minerestats, false, true }, + { "check", checkcommand, false, false }, + { "failover-only", failoveronly, true, false }, + { "coin", minecoin, false, true }, + { "debug", debugstate, true, false }, + { "setconfig", setconfig, true, false }, + { "usbstats", usbstats, false, true }, +#ifdef HAVE_AN_FPGA + { "pgaset", pgaset, true, false }, +#endif + { "zero", dozero, true, false }, + { "hotplug", dohotplug, true, false }, +#ifdef HAVE_AN_ASIC + { "asc", ascdev, false, false }, + { "ascenable", ascenable, true, false }, + { "ascdisable", ascdisable, true, false }, + { "ascidentify", ascidentify, true, false }, + { "ascset", ascset, true, false }, +#endif + { "asccount", asccount, false, true }, + { "lcd", lcddisplay, false, true }, + { "lockstats", lockstats, true, true }, + { NULL, NULL, false, false } +}; + +static void checkcommand(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, char group) +{ + struct api_data *root = NULL; + bool io_open; + char cmdbuf[100]; + bool found, access; + int i; + + if (param == NULL || *param == '\0') { + message(io_data, MSG_MISCHK, 0, NULL, isjson); + return; + } + + found = false; + access = false; + for (i = 0; cmds[i].name != NULL; i++) { + if (strcmp(cmds[i].name, param) == 0) { + found = true; + + sprintf(cmdbuf, "|%s|", param); + if (ISPRIVGROUP(group) || strstr(COMMANDS(group), cmdbuf)) + access = true; + + break; + } + } + + message(io_data, MSG_CHECK, 0, NULL, isjson); + io_open = io_add(io_data, isjson ? COMSTR JSON_CHECK : _CHECK COMSTR); + + root = api_add_const(root, "Exists", found ? YES : NO, false); + root = api_add_const(root, "Access", access ? YES : NO, false); + + root = print_data(io_data, root, isjson, false); + if (isjson && io_open) + io_close(io_data); +} + +static void head_join(struct io_data *io_data, char *cmdptr, bool isjson, bool *firstjoin) +{ + char *ptr; + + if (*firstjoin) { + if (isjson) + io_add(io_data, JSON0); + *firstjoin = false; + } else { + if (isjson) + io_add(io_data, JSON_BETWEEN_JOIN); + } + + // External supplied string + ptr = escape_string(cmdptr, isjson); + + if (isjson) { + io_add(io_data, JSON1); + io_add(io_data, ptr); + io_add(io_data, JSON2); + } else { + io_add(io_data, JOIN_CMD); + io_add(io_data, ptr); + io_add(io_data, BETWEEN_JOIN); + } + + if (ptr != cmdptr) + free(ptr); +} + +static void tail_join(struct io_data *io_data, bool isjson) +{ + if (io_data->close) { + io_add(io_data, JSON_CLOSE); + io_data->close = false; + } + + if (isjson) { + io_add(io_data, JSON_END); + io_add(io_data, JSON3); + } +} + +static void send_result(struct io_data *io_data, SOCKETTYPE c, bool isjson) +{ + int count, sendc, res, tosend, len, n; + char *buf = io_data->ptr; + + strcpy(buf, io_data->ptr); + + if (io_data->close) + strcat(buf, JSON_CLOSE); + + if (isjson) + strcat(buf, JSON_END); + + len = strlen(buf); + tosend = len+1; + + applog(LOG_DEBUG, "API: send reply: (%d) '%.10s%s'", tosend, buf, len > 10 ? "..." : BLANK); + + count = sendc = 0; + while (count < 5 && tosend > 0) { + // allow 50ms per attempt + struct timeval timeout = {0, 50000}; + fd_set wd; + + FD_ZERO(&wd); + FD_SET(c, &wd); + if ((res = select(c + 1, NULL, &wd, NULL, &timeout)) < 1) { + applog(LOG_WARNING, "API: send select failed (%d)", res); + return; + } + + n = send(c, buf, tosend, 0); + sendc++; + + if (SOCKETFAIL(n)) { + count++; + if (sock_blocks()) + continue; + + applog(LOG_WARNING, "API: send (%d:%d) failed: %s", len+1, (len+1 - tosend), SOCKERRMSG); + + return; + } else { + if (sendc <= 1) { + if (n == tosend) + applog(LOG_DEBUG, "API: sent all of %d first go", tosend); + else + applog(LOG_DEBUG, "API: sent %d of %d first go", n, tosend); + } else { + if (n == tosend) + applog(LOG_DEBUG, "API: sent all of remaining %d (sendc=%d)", tosend, sendc); + else + applog(LOG_DEBUG, "API: sent %d of remaining %d (sendc=%d)", n, tosend, sendc); + } + + tosend -= n; + buf += n; + + if (n == 0) + count++; + } + } +} + +static void tidyup(__maybe_unused void *arg) +{ + mutex_lock(&quit_restart_lock); + + SOCKETTYPE *apisock = (SOCKETTYPE *)arg; + + bye = true; + + if (*apisock != INVSOCK) { + shutdown(*apisock, SHUT_RDWR); + CLOSESOCKET(*apisock); + *apisock = INVSOCK; + } + + if (ipaccess != NULL) { + free(ipaccess); + ipaccess = NULL; + } + + io_free(); + + mutex_unlock(&quit_restart_lock); +} + +/* + * Interpret --api-groups G:cmd1:cmd2:cmd3,P:cmd4,*,... + */ +static void setup_groups() +{ + char *api_groups = opt_api_groups ? opt_api_groups : (char *)BLANK; + char *buf, *ptr, *next, *colon; + char group; + char commands[TMPBUFSIZ]; + char cmdbuf[100]; + char *cmd; + bool addstar, did; + int i; + + buf = malloc(strlen(api_groups) + 1); + if (unlikely(!buf)) + quit(1, "Failed to malloc ipgroups buf"); + + strcpy(buf, api_groups); + + next = buf; + // for each group defined + while (next && *next) { + ptr = next; + next = strchr(ptr, ','); + if (next) + *(next++) = '\0'; + + // Validate the group + if (*(ptr+1) != ':') { + colon = strchr(ptr, ':'); + if (colon) + *colon = '\0'; + quit(1, "API invalid group name '%s'", ptr); + } + + group = GROUP(*ptr); + if (!VALIDGROUP(group)) + quit(1, "API invalid group name '%c'", *ptr); + + if (group == PRIVGROUP) + quit(1, "API group name can't be '%c'", PRIVGROUP); + + if (group == NOPRIVGROUP) + quit(1, "API group name can't be '%c'", NOPRIVGROUP); + + if (apigroups[GROUPOFFSET(group)].commands != NULL) + quit(1, "API duplicate group name '%c'", *ptr); + + ptr += 2; + + // Validate the command list (and handle '*') + cmd = &(commands[0]); + *(cmd++) = SEPARATOR; + *cmd = '\0'; + addstar = false; + while (ptr && *ptr) { + colon = strchr(ptr, ':'); + if (colon) + *(colon++) = '\0'; + + if (strcmp(ptr, "*") == 0) + addstar = true; + else { + did = false; + for (i = 0; cmds[i].name != NULL; i++) { + if (strcasecmp(ptr, cmds[i].name) == 0) { + did = true; + break; + } + } + if (did) { + // skip duplicates + sprintf(cmdbuf, "|%s|", cmds[i].name); + if (strstr(commands, cmdbuf) == NULL) { + strcpy(cmd, cmds[i].name); + cmd += strlen(cmds[i].name); + *(cmd++) = SEPARATOR; + *cmd = '\0'; + } + } else { + quit(1, "API unknown command '%s' in group '%c'", ptr, group); + } + } + + ptr = colon; + } + + // * = allow all non-iswritemode commands + if (addstar) { + for (i = 0; cmds[i].name != NULL; i++) { + if (cmds[i].iswritemode == false) { + // skip duplicates + sprintf(cmdbuf, "|%s|", cmds[i].name); + if (strstr(commands, cmdbuf) == NULL) { + strcpy(cmd, cmds[i].name); + cmd += strlen(cmds[i].name); + *(cmd++) = SEPARATOR; + *cmd = '\0'; + } + } + } + } + + ptr = apigroups[GROUPOFFSET(group)].commands = malloc(strlen(commands) + 1); + if (unlikely(!ptr)) + quit(1, "Failed to malloc group commands buf"); + + strcpy(ptr, commands); + } + + // Now define R (NOPRIVGROUP) as all non-iswritemode commands + cmd = &(commands[0]); + *(cmd++) = SEPARATOR; + *cmd = '\0'; + for (i = 0; cmds[i].name != NULL; i++) { + if (cmds[i].iswritemode == false) { + strcpy(cmd, cmds[i].name); + cmd += strlen(cmds[i].name); + *(cmd++) = SEPARATOR; + *cmd = '\0'; + } + } + + ptr = apigroups[GROUPOFFSET(NOPRIVGROUP)].commands = malloc(strlen(commands) + 1); + if (unlikely(!ptr)) + quit(1, "Failed to malloc noprivgroup commands buf"); + + strcpy(ptr, commands); + + // W (PRIVGROUP) is handled as a special case since it simply means all commands + + free(buf); + return; +} + +/* + * Interpret [W:]IP[/Prefix][,[R|W:]IP2[/Prefix2][,...]] --api-allow option + * ipv6 address should be enclosed with a pair of square brackets and the prefix left outside + * special case of 0/0 allows /0 (means all IP addresses) + */ +#define ALLIP "0/0" +/* + * N.B. IP4 addresses are by Definition 32bit big endian on all platforms + */ +static void setup_ipaccess() +{ + char *buf, *ptr, *comma, *slash, *end; + int ipcount, mask, i, shift; + bool ipv6 = false; + char group; + char tmp[30]; + + buf = malloc(strlen(opt_api_allow) + 1); + if (unlikely(!buf)) + quit(1, "Failed to malloc ipaccess buf"); + + strcpy(buf, opt_api_allow); + + ipcount = 1; + ptr = buf; + while (*ptr) + if (*(ptr++) == ',') + ipcount++; + + // possibly more than needed, but never less + ipaccess = calloc(ipcount, sizeof(struct IPACCESS)); + if (unlikely(!ipaccess)) + quit(1, "Failed to calloc ipaccess"); + + ips = 0; + ptr = buf; + while (ptr && *ptr) { + while (*ptr == ' ' || *ptr == '\t') + ptr++; + + if (*ptr == ',') { + ptr++; + continue; + } + + comma = strchr(ptr, ','); + if (comma) + *(comma++) = '\0'; + + group = NOPRIVGROUP; + + if (isalpha(*ptr) && *(ptr+1) == ':') { + if (DEFINEDGROUP(*ptr)) + group = GROUP(*ptr); + + ptr += 2; + } + + ipaccess[ips].group = group; + + if (strcmp(ptr, ALLIP) == 0) { + for (i = 0; i < 16; i++) { + ipaccess[ips].ip.s6_addr[i] = 0; + ipaccess[ips].mask.s6_addr[i] = 0; + } + } + else { + end = strchr(ptr, '/'); + if (!end) { + for (i = 0; i < 16; i++) + ipaccess[ips].mask.s6_addr[i] = 0xff; + end = ptr + strlen(ptr); + } + slash = end--; + if (*ptr == '[' && *end == ']') { + *(ptr++) = '\0'; + *(end--) = '\0'; + ipv6 = true; + } + else + ipv6 = false; + if (*slash) { + *(slash++) = '\0'; + mask = atoi(slash); + if (mask < 1 || (mask += ipv6 ? 0 : 96) > 128 ) + goto popipo; // skip invalid/zero + + for (i = 0; i < 16; i++) + ipaccess[ips].mask.s6_addr[i] = 0; + + i = 0; + shift = 7; + while (mask-- > 0) { + ipaccess[ips].mask.s6_addr[i] |= 1 << shift; + if (shift-- == 0) { + i++; + shift = 7; + } + } + } + + for (i = 0; i < 16; i++) + ipaccess[ips].ip.s6_addr[i] = 0; // missing default to '[::]' + if (ipv6) { + if (INET_PTON(AF_INET6, ptr, &(ipaccess[ips].ip)) != 1) + goto popipo; + } + else { + // v4 mapped v6 address, such as "::ffff:255.255.255.255" + sprintf(tmp, "::ffff:%s", ptr); + if (INET_PTON(AF_INET6, tmp, &(ipaccess[ips].ip)) != 1) + goto popipo; + } + for (i = 0; i < 16; i++) + ipaccess[ips].ip.s6_addr[i] &= ipaccess[ips].mask.s6_addr[i]; + } + + ips++; +popipo: + ptr = comma; + } + + free(buf); +} + +static void *quit_thread(__maybe_unused void *userdata) +{ + // allow thread creator to finish whatever it's doing + mutex_lock(&quit_restart_lock); + mutex_unlock(&quit_restart_lock); + + if (opt_debug) + applog(LOG_DEBUG, "API: killing bmminer"); + + kill_work(); + + return NULL; +} + +static void *restart_thread(__maybe_unused void *userdata) +{ + // allow thread creator to finish whatever it's doing + mutex_lock(&quit_restart_lock); + mutex_unlock(&quit_restart_lock); + + if (opt_debug) + applog(LOG_DEBUG, "API: restarting bmminer"); + + app_restart(); + + return NULL; +} + +static bool check_connect(struct sockaddr_storage *cli, char **connectaddr, char *group) +{ + bool addrok = false; + int i, j; + bool match; + char tmp[30]; + struct in6_addr client_ip; + + *connectaddr = (char *)malloc(INET6_ADDRSTRLEN); + getnameinfo((struct sockaddr *)cli, sizeof(*cli), + *connectaddr, INET6_ADDRSTRLEN, NULL, 0, NI_NUMERICHOST); + + // v4 mapped v6 address, such as "::ffff:255.255.255.255" + if (cli->ss_family == AF_INET) { + sprintf(tmp, "::ffff:%s", *connectaddr); + INET_PTON(AF_INET6, tmp, &client_ip); + } + else + INET_PTON(AF_INET6, *connectaddr, &client_ip); + + *group = NOPRIVGROUP; + if (opt_api_allow) { + for (i = 0; i < ips; i++) { + match = true; + for (j = 0; j < 16; j++) + if ((client_ip.s6_addr[j] & ipaccess[i].mask.s6_addr[j]) + != ipaccess[i].ip.s6_addr[j]) { + match = false; + break; + } + if (match) { + addrok = true; + *group = ipaccess[i].group; + break; + } + } + } else { + if (opt_api_network) + addrok = true; + else + addrok = (strcmp(*connectaddr, localaddr) == 0) + || IN6_IS_ADDR_LOOPBACK(&client_ip); + } + + return addrok; +} + +static void mcast() +{ + struct sockaddr_storage came_from; + time_t bindstart; + char *binderror; + SOCKETTYPE mcast_sock = INVSOCK; + SOCKETTYPE reply_sock = INVSOCK; + socklen_t came_from_siz; + char *connectaddr; + ssize_t rep; + int bound; + int count; + int reply_port; + bool addrok; + char group; + + char port_s[10], came_from_port[10]; + struct addrinfo hints, *res, *host, *client; + + char expect[] = "bmminer-"; // first 8 bytes constant + char *expect_code; + size_t expect_code_len; + char buf[1024]; + char replybuf[1024]; + + sprintf(port_s, "%d", opt_api_mcast_port); + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + if (getaddrinfo(opt_api_mcast_addr, port_s, &hints, &res) != 0) + quit(1, "Invalid API Multicast Address"); + host = res; + while (host != NULL) { + mcast_sock = socket(res->ai_family, SOCK_DGRAM, 0); + if (mcast_sock > 0) + break; + host = host->ai_next; + } + if (mcast_sock == INVSOCK) { + freeaddrinfo(res); + quit(1, "API mcast could not open socket"); + } + + int optval = 1; + if (SOCKETFAIL(setsockopt(mcast_sock, SOL_SOCKET, SO_REUSEADDR, (void *)(&optval), sizeof(optval)))) { + applog(LOG_ERR, "API mcast setsockopt SO_REUSEADDR failed (%s)%s", SOCKERRMSG, MUNAVAILABLE); + goto die; + } + + // try for more than 1 minute ... in case the old one hasn't completely gone yet + bound = 0; + bindstart = time(NULL); + while (bound == 0) { + if (SOCKETFAIL(bind(mcast_sock, host->ai_addr, host->ai_addrlen))) { + binderror = SOCKERRMSG; + if ((time(NULL) - bindstart) > 61) + break; + else + cgsleep_ms(30000); + } else + bound = 1; + } + + if (bound == 0) { + applog(LOG_ERR, "API mcast bind to port %d failed (%s)%s", opt_api_mcast_port, binderror, MUNAVAILABLE); + goto die; + } + + switch (host->ai_family) { + case AF_INET: { + struct ip_mreq grp; + memset(&grp, 0, sizeof(grp)); + grp.imr_multiaddr.s_addr = ((struct sockaddr_in *)(host->ai_addr))->sin_addr.s_addr; + grp.imr_interface.s_addr = INADDR_ANY; + + if (SOCKETFAIL(setsockopt(mcast_sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, + (void *)(&grp), sizeof(grp)))) { + applog(LOG_ERR, "API mcast join failed (%s)%s", SOCKERRMSG, MUNAVAILABLE); + goto die; + } + break; + } + case AF_INET6: { + struct ipv6_mreq grp; + memcpy(&grp.ipv6mr_multiaddr, &(((struct sockaddr_in6 *)(host->ai_addr))->sin6_addr), + sizeof(struct in6_addr)); + grp.ipv6mr_interface= 0; + + if (SOCKETFAIL(setsockopt(mcast_sock, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, + (void *)(&grp), sizeof(grp)))) { + applog(LOG_ERR, "API mcast join failed (%s)%s", SOCKERRMSG, MUNAVAILABLE); + goto die; + } + break; + } + default: + break; + } + freeaddrinfo(res); + + expect_code_len = sizeof(expect) + strlen(opt_api_mcast_code); + expect_code = malloc(expect_code_len+1); + if (!expect_code) + quit(1, "Failed to malloc mcast expect_code"); + snprintf(expect_code, expect_code_len+1, "%s%s-", expect, opt_api_mcast_code); + + count = 0; + while (80085) { + cgsleep_ms(1000); + + count++; + came_from_siz = sizeof(came_from); + if (SOCKETFAIL(rep = recvfrom(mcast_sock, buf, sizeof(buf) - 1, + 0, (struct sockaddr *)(&came_from), &came_from_siz))) { + applog(LOG_DEBUG, "API mcast failed count=%d (%s) (%d)", + count, SOCKERRMSG, (int)mcast_sock); + continue; + } + + addrok = check_connect(&came_from, &connectaddr, &group); + applog(LOG_DEBUG, "API mcast from %s - %s", + connectaddr, addrok ? "Accepted" : "Ignored"); + if (!addrok) + continue; + + buf[rep] = '\0'; + if (rep > 0 && buf[rep-1] == '\n') + buf[--rep] = '\0'; + + getnameinfo((struct sockaddr *)(&came_from), came_from_siz, + NULL, 0, came_from_port, sizeof(came_from_port), NI_NUMERICHOST); + + applog(LOG_DEBUG, "API mcast request rep=%d (%s) from [%s]:%s", + (int)rep, buf, connectaddr, came_from_port); + + if ((size_t)rep > expect_code_len && memcmp(buf, expect_code, expect_code_len) == 0) { + reply_port = atoi(&buf[expect_code_len]); + if (reply_port < 1 || reply_port > 65535) { + applog(LOG_DEBUG, "API mcast request ignored - invalid port (%s)", + &buf[expect_code_len]); + } else { + applog(LOG_DEBUG, "API mcast request OK port %s=%d", + &buf[expect_code_len], reply_port); + + if (getaddrinfo(connectaddr, &buf[expect_code_len], &hints, &res) != 0) { + applog(LOG_ERR, "Invalid client address %s", connectaddr); + continue; + } + client = res; + while (client) { + reply_sock = socket(res->ai_family, SOCK_DGRAM, 0); + if (mcast_sock > 0) + break; + client = client->ai_next; + } + if (reply_sock == INVSOCK) { + freeaddrinfo(res); + applog(LOG_ERR, "API mcast could not open socket to client %s", connectaddr); + continue; + } + + snprintf(replybuf, sizeof(replybuf), + "cgm-" API_MCAST_CODE "-%d-%s", + opt_api_port, opt_api_mcast_des); + + rep = sendto(reply_sock, replybuf, strlen(replybuf)+1, + 0, client->ai_addr, client->ai_addrlen); + freeaddrinfo(res); + if (SOCKETFAIL(rep)) { + applog(LOG_DEBUG, "API mcast send reply failed (%s) (%d)", + SOCKERRMSG, (int)reply_sock); + } else { + applog(LOG_DEBUG, "API mcast send reply (%s) succeeded (%d) (%d)", + replybuf, (int)rep, (int)reply_sock); + } + + CLOSESOCKET(reply_sock); + } + } else + applog(LOG_DEBUG, "API mcast request was no good"); + } + +die: + + CLOSESOCKET(mcast_sock); +} + +static void *mcast_thread(void *userdata) +{ + struct thr_info *mythr = userdata; + + pthread_detach(pthread_self()); + pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); + + RenameThread("APIMcast"); + + mcast(); + + PTH(mythr) = 0L; + + return NULL; +} + +void mcast_init() +{ + struct thr_info *thr; + + thr = calloc(1, sizeof(*thr)); + if (!thr) + quit(1, "Failed to calloc mcast thr"); + + if (thr_info_create(thr, NULL, mcast_thread, thr)) + quit(1, "API mcast thread create failed"); +} + +void api(int api_thr_id) +{ + struct io_data *io_data; + struct thr_info bye_thr; + char buf[TMPBUFSIZ]; + char param_buf[TMPBUFSIZ]; + SOCKETTYPE c; + int n, bound; + char *connectaddr; + char *binderror; + time_t bindstart; + short int port = opt_api_port; + char port_s[10]; + struct sockaddr_storage cli; + socklen_t clisiz; + char cmdbuf[100]; + char *cmd = NULL; + char *param; + bool addrok; + char group; + json_error_t json_err; + json_t *json_config = NULL; + json_t *json_val; + bool isjson; + bool did, isjoin = false, firstjoin; + int i; + struct addrinfo hints, *res, *host; + + SOCKETTYPE *apisock; + + apisock = malloc(sizeof(*apisock)); + *apisock = INVSOCK; + + if (!opt_api_listen) { + applog(LOG_DEBUG, "API not running%s", UNAVAILABLE); + free(apisock); + return; + } + + io_data = sock_io_new(); + + mutex_init(&quit_restart_lock); + + pthread_cleanup_push(tidyup, (void *)apisock); + my_thr_id = api_thr_id; + + setup_groups(); + + if (opt_api_allow) { + setup_ipaccess(); + + if (ips == 0) { + applog(LOG_WARNING, "API not running (no valid IPs specified)%s", UNAVAILABLE); + free(apisock); + return; + } + } + + /* This should be done before curl in needed + * to ensure curl has already called WSAStartup() in windows */ + cgsleep_ms(opt_log_interval*1000); + + sprintf(port_s, "%d", port); + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_PASSIVE; + hints.ai_family = AF_UNSPEC; + if (getaddrinfo(opt_api_host, port_s, &hints, &res) != 0) { + applog(LOG_ERR, "API failed to resolve %s", opt_api_host); + free(apisock); + return; + } + host = res; + while (host) { + *apisock = socket(res->ai_family, SOCK_STREAM, 0); + if (*apisock > 0) + break; + host = host->ai_next; + } + if (*apisock == INVSOCK) { + applog(LOG_ERR, "API initialisation failed (%s)%s", SOCKERRMSG, UNAVAILABLE); + freeaddrinfo(res); + free(apisock); + return; + } + +#ifndef WIN32 + // On linux with SO_REUSEADDR, bind will get the port if the previous + // socket is closed (even if it is still in TIME_WAIT) but fail if + // another program has it open - which is what we want + int optval = 1; + // If it doesn't work, we don't really care - just show a debug message + if (SOCKETFAIL(setsockopt(*apisock, SOL_SOCKET, SO_REUSEADDR, (void *)(&optval), sizeof(optval)))) + applog(LOG_DEBUG, "API setsockopt SO_REUSEADDR failed (ignored): %s", SOCKERRMSG); +#else + // On windows a 2nd program can bind to a port>1024 already in use unless + // SO_EXCLUSIVEADDRUSE is used - however then the bind to a closed port + // in TIME_WAIT will fail until the timeout - so we leave the options alone +#endif + + // try for more than 1 minute ... in case the old one hasn't completely gone yet + bound = 0; + bindstart = time(NULL); + while (bound == 0) { + if (SOCKETFAIL(bind(*apisock, host->ai_addr, host->ai_addrlen))) { + binderror = SOCKERRMSG; + if ((time(NULL) - bindstart) > 61) + break; + else { + applog(LOG_WARNING, "API bind to port %d failed - trying again in 30sec", port); + cgsleep_ms(30000); + } + } else + bound = 1; + } + freeaddrinfo(res); + + if (bound == 0) { + applog(LOG_ERR, "API bind to port %d failed (%s)%s", port, binderror, UNAVAILABLE); + free(apisock); + return; + } + + if (SOCKETFAIL(listen(*apisock, QUEUE))) { + applog(LOG_ERR, "API3 initialisation failed (%s)%s", SOCKERRMSG, UNAVAILABLE); + CLOSESOCKET(*apisock); + free(apisock); + return; + } + + if (opt_api_allow) + applog(LOG_WARNING, "API running in IP access mode on port %d (%d)", port, (int)*apisock); + else { + if (opt_api_network) + applog(LOG_WARNING, "API running in UNRESTRICTED read access mode on port %d (%d)", port, (int)*apisock); + else + applog(LOG_WARNING, "API running in local read access mode on port %d (%d)", port, (int)*apisock); + } + + if (opt_api_mcast) + mcast_init(); + + strbufs = k_new_list("StrBufs", sizeof(SBITEM), ALLOC_SBITEMS, LIMIT_SBITEMS, false); + + while (!bye) { + clisiz = sizeof(cli); + if (SOCKETFAIL(c = accept(*apisock, (struct sockaddr *)(&cli), &clisiz))) { + applog(LOG_ERR, "API failed (%s)%s (%d)", SOCKERRMSG, UNAVAILABLE, (int)*apisock); + goto die; + } + + addrok = check_connect((struct sockaddr_storage *)&cli, &connectaddr, &group); + applog(LOG_DEBUG, "API: connection from %s - %s", + connectaddr, addrok ? "Accepted" : "Ignored"); + + if (addrok) { + n = recv(c, &buf[0], TMPBUFSIZ-1, 0); + if (SOCKETFAIL(n)) + buf[0] = '\0'; + else + buf[n] = '\0'; + + if (opt_debug) { + if (SOCKETFAIL(n)) + applog(LOG_DEBUG, "API: recv failed: %s", SOCKERRMSG); + else + applog(LOG_DEBUG, "API: recv command: (%d) '%s'", n, buf); + } + + if (!SOCKETFAIL(n)) { + // the time of the request in now + when = time(NULL); + io_reinit(io_data); + + did = false; + + if (*buf != ISJSON) { + isjson = false; + + param = strchr(buf, SEPARATOR); + if (param != NULL) + *(param++) = '\0'; + + cmd = buf; + } + else { + isjson = true; + + param = NULL; + + json_config = json_loadb(buf, n, 0, &json_err); + + if (!json_is_object(json_config)) { + message(io_data, MSG_INVJSON, 0, NULL, isjson); + send_result(io_data, c, isjson); + did = true; + } else { + json_val = json_object_get(json_config, JSON_COMMAND); + if (json_val == NULL) { + message(io_data, MSG_MISCMD, 0, NULL, isjson); + send_result(io_data, c, isjson); + did = true; + } else { + if (!json_is_string(json_val)) { + message(io_data, MSG_INVCMD, 0, NULL, isjson); + send_result(io_data, c, isjson); + did = true; + } else { + cmd = (char *)json_string_value(json_val); + json_val = json_object_get(json_config, JSON_PARAMETER); + if (json_is_string(json_val)) + param = (char *)json_string_value(json_val); + else if (json_is_integer(json_val)) { + sprintf(param_buf, "%d", (int)json_integer_value(json_val)); + param = param_buf; + } else if (json_is_real(json_val)) { + sprintf(param_buf, "%f", (double)json_real_value(json_val)); + param = param_buf; + } + } + } + } + } + + if (!did) { + char *cmdptr, *cmdsbuf = NULL; + + if (strchr(cmd, CMDJOIN)) { + firstjoin = isjoin = true; + // cmd + leading+tailing '|' + '\0' + cmdsbuf = malloc(strlen(cmd) + 3); + if (!cmdsbuf) + quithere(1, "OOM cmdsbuf"); + strcpy(cmdsbuf, "|"); + param = NULL; + } else + firstjoin = isjoin = false; + + cmdptr = cmd; + do { + did = false; + if (isjoin) { + cmd = strchr(cmdptr, CMDJOIN); + if (cmd) + *(cmd++) = '\0'; + if (!*cmdptr) + goto inochi; + } + + for (i = 0; cmds[i].name != NULL; i++) { + if (strcmp(cmdptr, cmds[i].name) == 0) { + sprintf(cmdbuf, "|%s|", cmdptr); + if (isjoin) { + if (strstr(cmdsbuf, cmdbuf)) { + did = true; + break; + } + strcat(cmdsbuf, cmdptr); + strcat(cmdsbuf, "|"); + head_join(io_data, cmdptr, isjson, &firstjoin); + if (!cmds[i].joinable) { + message(io_data, MSG_ACCDENY, 0, cmds[i].name, isjson); + did = true; + tail_join(io_data, isjson); + break; + } + } + if (ISPRIVGROUP(group) || strstr(COMMANDS(group), cmdbuf)) + (cmds[i].func)(io_data, c, param, isjson, group); + else { + message(io_data, MSG_ACCDENY, 0, cmds[i].name, isjson); + applog(LOG_DEBUG, "API: access denied to '%s' for '%s' command", connectaddr, cmds[i].name); + } + + did = true; + if (!isjoin) + send_result(io_data, c, isjson); + else + tail_join(io_data, isjson); + break; + } + } + + if (!did) { + if (isjoin) + head_join(io_data, cmdptr, isjson, &firstjoin); + message(io_data, MSG_INVCMD, 0, NULL, isjson); + if (isjoin) + tail_join(io_data, isjson); + else + send_result(io_data, c, isjson); + } +inochi: + if (isjoin) + cmdptr = cmd; + } while (isjoin && cmdptr); + } + + if (isjoin) + send_result(io_data, c, isjson); + + if (isjson && json_is_object(json_config)) + json_decref(json_config); + } + } + CLOSESOCKET(c); + } +die: + /* Blank line fix for older compilers since pthread_cleanup_pop is a + * macro that gets confused by a label existing immediately before it + */ + ; + pthread_cleanup_pop(true); + + free(apisock); + + if (opt_debug) + applog(LOG_DEBUG, "API: terminating due to: %s", + do_a_quit ? "QUIT" : (do_a_restart ? "RESTART" : (bye ? "BYE" : "UNKNOWN!"))); + + mutex_lock(&quit_restart_lock); + + if (do_a_restart) { + if (thr_info_create(&bye_thr, NULL, restart_thread, &bye_thr)) { + mutex_unlock(&quit_restart_lock); + quit(1, "API failed to initiate a restart - aborting"); + } + pthread_detach(bye_thr.pth); + } else if (do_a_quit) { + if (thr_info_create(&bye_thr, NULL, quit_thread, &bye_thr)) { + mutex_unlock(&quit_restart_lock); + quit(1, "API failed to initiate a clean quit - aborting"); + } + pthread_detach(bye_thr.pth); + } + + mutex_unlock(&quit_restart_lock); +} diff --git a/arg-nonnull.h b/arg-nonnull.h new file mode 100644 index 0000000..6c2f1e8 --- /dev/null +++ b/arg-nonnull.h @@ -0,0 +1,26 @@ +/* A C macro for declaring that specific arguments must not be NULL. + Copyright (C) 2009-2011 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +/* _GL_ARG_NONNULL((n,...,m)) tells the compiler and static analyzer tools + that the values passed as arguments n, ..., m must be non-NULL pointers. + n = 1 stands for the first argument, n = 2 for the second argument etc. */ +#ifndef _GL_ARG_NONNULL +# if (__GNUC__ == 3 && __GNUC_MINOR__ >= 3) || __GNUC__ > 3 +# define _GL_ARG_NONNULL(params) __attribute__ ((__nonnull__ params)) +# else +# define _GL_ARG_NONNULL(params) +# endif +#endif diff --git a/autogen.sh b/autogen.sh new file mode 100644 index 0000000..cae99ba --- /dev/null +++ b/autogen.sh @@ -0,0 +1,11 @@ +#!/bin/sh +bs_dir=$(cd "$(dirname "$0")"; pwd) + +#Some versions of libtoolize don't like there being no ltmain.sh file already +touch "${bs_dir}"/ltmain.sh +autoreconf -fi "${bs_dir}" + +if test -n "$1" && test -z "$NOCONFIGURE" ; then + echo 'Configuring...' + "$bs_dir"/configure "$@" +fi diff --git a/bench_block.h b/bench_block.h new file mode 100644 index 0000000..729a95e --- /dev/null +++ b/bench_block.h @@ -0,0 +1,170 @@ +#ifndef __BENCH_BLOCK_H__ +#define __BENCH_BLOCK_H__ + +/* This contains 32 carefully chosen work items, 16 of which return diff >= 32 + * at nonces spaced ~ 0x10000000 apart and 16 < diff 32. */ + +const char bench_hidiffs[16][324] = { +// 0002108b diff 131 +"000000029c6bf469abe4ad37605c097a860cff3cf5c1ef4377618f74000000000000000082b1514e7b6565941e5824f084292164ec5f97e7ea20c494bd96e524d478977b536dd2261900896c8b100200" +"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000" +"64e4e3becc01064d808269b330f40f4de82dc92e894d635025daa3e2e2c410b4", + +// 1003dacf diff 37 +"00000002e790c23987181950eeb144591c3ac4d06c0705f2801d097600000000000000009ebbce2f5f0d6cc0aca284ecb1059c856ef2f7f42e7edd403d246754ee4c905a536dd2a91900896ccfda0310" +"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000" +"4a78daf1b5eb3397af1c00dbd9b06659cdc04183c8baaf5be1dbf32f79e00459", + +// 200e57b4 diff 3866 +"000000023e91fce7300a792bfbaa0c76e1aa5f9b546c1db582aee4ff0000000000000000f04650a8e748d2e6fde86a8a920b285f3e22398f583700236958323ef9ea8321536dcf431900896cb4570e20" +"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000" +"0a1d654ae2b06f219ccf4601933fab408de1c3b7c8c9c85e03231d4aaf5a26cd", + +// 300f71e2 diff 335 +"000000023e91fce7300a792bfbaa0c76e1aa5f9b546c1db582aee4ff000000000000000074b39134c2930d2f2e7339f9d502c776c44d6ee599f7efebec6c9bbd04787aae536dce561900896ce2710f30" +"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000" +"94e60c1180022f337232ab3d298f838304b6008ab237cf7e1717f1933407e592", + +// 400548ed diff 2670 +"000000023e91fce7300a792bfbaa0c76e1aa5f9b546c1db582aee4ff0000000000000000c5b821fb0b26d63b00cc26e7ac4d6cfd1d3fc109b0db188e7e792e3d18342919536dce501900896ced480540" +"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000" +"a290eac61642949c00d17f7cd5980abedb8647fc5df9955dcfe4d56a50a0c564", + +// 5001f760 diff 60 +"00000002e790c23987181950eeb144591c3ac4d06c0705f2801d097600000000000000006e9d94bf5a0ab7b202d39e1200af96074e4f641f4e55e3e9e3aee72aa00a70e9536dd2ae1900896c60f70150" +"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000" +"1477ca8536702eacbd65a6a162cfe90d62016a14ffe58d52b7dd4c3628a27e5b", + +// 600c9816 diff 35 +"00000002194bb5b4f8ac3392fbd66f3dd3e9dcdb22370e380837fe44000000000000000003bbb250f2dc23717e8192c0b8bec6a175cd059e4089d325006eaee3446254c9536dd39e1900896c16980c60" +"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000" +"68db599d6b7a55fd61d4244a3dfa465055ead6b5c0a37c7a3d4555b58e99065e", + +// 70092d5f diff 114 +"000000023e91fce7300a792bfbaa0c76e1aa5f9b546c1db582aee4ff000000000000000072e17babd4089b204797cebda7dc6e277950eab1b2908991ae1d72335f82d204536dcf441900896c5f2d0970" +"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000" +"c7d601ce3b01e569a49508d541bbcba9b3c8394b1834523ef1e5cb2c60bd34a3", + +// 800eeaa8 diff 159 +"000000029c6bf469abe4ad37605c097a860cff3cf5c1ef4377618f74000000000000000022388b6f022144db134af1bc8e61b385ca37cae038c1d165ae98c496b3b41e8b536dd2101900896ca8ea0e80" +"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000" +"410761e97e67b494fd547cfe9ffbb36893da7aec75c6b51b8d5f38f87b5d63cf", + +// 900f600d diff 144 +"000000029c6bf469abe4ad37605c097a860cff3cf5c1ef4377618f7400000000000000000e1f0cfdf5ad8248fc4520f3bb0b2040226430348cddeff5ca9181beeb78870d536dd2161900896c0d600f90" +"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000" +"ad1a8d354a7e8b13ec47f4c3d907d00945a61e86059f4943e42c1e52398eba5d", + +// a00210bf diff 1055 +"00000002194bb5b4f8ac3392fbd66f3dd3e9dcdb22370e380837fe4400000000000000002232a16d38cc0e13e4b16d917bff4c34727deb3b5c50e424fb8453ff9b2adcb4536dd4231900896cbf1002a0" +"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000" +"adc67d38f31f589b18b9d8e531b994ce5733c021a03d88d38611ee6b4c2710a5", + +// b004309d diff 43 +"00000002194bb5b4f8ac3392fbd66f3dd3e9dcdb22370e380837fe440000000000000000a2860471277b4a93fea2a8b6d8c281fab7bde3b78f2acd1bfdc89d464ed3bb3c536dd35e1900896c9d3004b0" +"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000" +"537686c611aae4397c7c04b2c190708453d00e8c9563525610c31ba46e80dbc2", + +// c00b7537 diff 64 +"00000002194bb5b4f8ac3392fbd66f3dd3e9dcdb22370e380837fe440000000000000000f370230607998fbbd10275c5890885fcd81b68018ba2373abf0f93a06d02ab28536dd33e1900896c37750bc0" +"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000" +"1fdda952da6abd70022a6e5f2b9dc5e1b66011128c3fa249f0b7439f00d5943e", + +// d0005bd5 diff 1539 +"00000002c0a2c91fc41254539a5b2a27be28de2a6187e2af3f129d6300000000000000005e45ffc512d5ca3bc4d2063dd3af1669c296ae126a5a2ef896d1e190cedf67b9536dd46b1900896cd55b00d0" +"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000" +"07094d6cbe76538a88612624fc5e655cc405cb8198dcad516b88dbac5bf8b906", + +// e00a7796 diff 41 +"00000002194bb5b4f8ac3392fbd66f3dd3e9dcdb22370e380837fe44000000000000000027c548815127c125147af91c356c293f0defbd2771f8dc3b1142b367528656db536dd37c1900896c96770ae0" +"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000" +"1bdbb3b1be7216872ea787627b03c389a527451f6dd832d8540874306f9c07c6", + +// f001f029 diff 77 +"00000002194bb5b4f8ac3392fbd66f3dd3e9dcdb22370e380837fe440000000000000000adef758770bb90c5b13769c5b61affb322b24c747573b38ebe2ee81748d0b557536dd4071900896c29f001f0" +"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000" +"1ac8eea63285353944e40eec54d2dd6cd0994b447429bb0ed0598d38f42da0e2" +}; + +const char bench_lodiffs[16][324] = { +// 000ed6b6 diff 2 +"00000002c01f502cb3e9fdb053230ec12a4954c1021a6b35862b5e29000000000000000084d1b83ae44057025e8c5b5756b44f04df5fffe4a7a30e5c12d12a97a7a4c2ea536dce431900896cb6d60e00" +"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000" +"d08f7e14c50dad77dc238b4db2901a0578e657b1954779ab9cd82a73829edf7f", + +// 1000818f diff 5 +"000000023bf53ef343a50f7599601f849c93ecce63530b0b449a44630000000000000000c1a174254a6593ffba987f68fe26e716e3c129a7f33a9c43ae7ecf90c8cd0d2c536dc4e61900896c8f810010" +"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000" +"6700aeedada2b3877900b58a183c42c40949956bb8b4a8d21481f8936b572922", + +// 20006be9 diff 7 +"00000002138cf4b61dff74e3c26b2d80045064e8ab4802521bab2cda000000000000000071eef64a7ef4e47cda16e96673197d36c7235a4aadd23c21a38ce53827d1f8bc536dc4d71900896ce96b0020" +"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000" +"0730cf7a6b8a85eb1cc017b109d23c392464f99aa8c020ea107c525b671adde0", + +// 300029f4 diff 2 +"000000023bf53ef343a50f7599601f849c93ecce63530b0b449a44630000000000000000fe2c6b926468565e524ab7c2f111035dcde7c60955842111930589eccb410f83536dc66b1900896cf4290030" +"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000" +"27dbb374a97f15c59587256662f36904d075d0e61f749618182711288ac617c7", + +// 40001d82 diff 2 +"000000023bf53ef343a50f7599601f849c93ecce63530b0b449a4463000000000000000003073385e05c29f0435a6001c8eca9c8d5602890aeff9d4d103d3383cf80dae5536dc57c1900896c821d0040" +"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000" +"7da3b97e82c0c3125a58dad8a0d1d0369244731f3b096e972484298d15b843d9", + +// 50003ce6 diff 1 +"000000029ca55e5f1bc0328c84f358fddadc13cb232599bc2ca9dbe10000000000000000b5b4d19c20a7fc2b174ff673c006edd2247c4b2336571864df93eb7ec0c8c276536dfe041900896ce63c0050" +"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000" +"1514bd586511e531e2b6277a6d112b171f9e008d56ef4a971e619acf22e75072", + +// 60004314 diff 2 +"000000023bf53ef343a50f7599601f849c93ecce63530b0b449a446300000000000000003e3030629ff4258056dc9efaf922bd173a65f65ee799b0c765097d3deeddef10536dc4d81900896c14430060" +"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000" +"dcb77a9c36d894d2dbc31437e5c2a1564e927937848ea2eb20b38638afc64b96", + +// 700041d4 diff 7 +"000000023bf53ef343a50f7599601f849c93ecce63530b0b449a446300000000000000005513c22bb99e9daa9936b0df5dce64d7737e3706be99e5098d112002492cf81b536dc5691900896cd4410070" +"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000" +"0d896267fda5dda0f85308e77f754c8b94b7b88e3cb315475cd9efd16401e3ce", + +// 80009d99 diff 1 +"00000002e155f07e652e4d671ca4db51bbde14d2b5ae34ee67ecc74400000000000000004af5cffd7e5a7087f1b484b526c7350c86d8389283509ca878502f792115e8dc536dc6ad1900896c999d0080" +"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000" +"339354568f506ac3cd69bb427b1af83a0473b87c16bf3b562a93d0a2ffc53e54", + +// 9000fb14 diff 4 +"000000029ca55e5f1bc0328c84f358fddadc13cb232599bc2ca9dbe100000000000000005925a624e5c84f96d2c34dce3b6a736addb891724b48a36320c7494435f9c915536dfe621900896c14fb0090" +"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000" +"8362009c07cf48249f481be6b79e67247cab1d20050cf11c276085b90732110c", + +// a000eb5e diff 2 +"00000002e155f07e652e4d671ca4db51bbde14d2b5ae34ee67ecc74400000000000000001e69f1d6507f4b7b50980930f7d8089834fbe65f0980b8592d53cdda08e50d24536dc7da1900896c5eeb00a0" +"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000" +"21e4f654d26ab8c9164ff311657a9f9c4cdc0e8a09334925f7c02138819d7e61", + +// b0002ec5 diff 2 +"000000023bf53ef343a50f7599601f849c93ecce63530b0b449a4463000000000000000064923b63f53c72c04ebe6c1c9140b6377132b6e50865814fe562291bd023d348536dc65a1900896cc52e00b0" +"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000" +"55db91a25401a89daf9ff7d7954bab722b894ba480fefaf1f0a95aaf5f600567", + +// c0001f6e diff 2 +"000000023bf53ef343a50f7599601f849c93ecce63530b0b449a44630000000000000000ee9817160e35d4410601c8dc741c1a810c485f3b40a0859be5f58f0bf6ef1694536dc6321900896c6e1f00c0" +"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000" +"ae215785178ff6350064060ebbb219a71716a10e88528fc4bb1cb5c8fdd0cf60", + +// d0005f26 diff 7 +"000000029ca55e5f1bc0328c84f358fddadc13cb232599bc2ca9dbe100000000000000001e514cf738455a54f004ec86edafcfd9fd2022017bb31c245340353911744fb7536dfe1f1900896c265f00d0" +"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000" +"dcafaa86defe850b057ae74f7218a79b0ede086a196f18f0e7c585eb88d1139a", + +// e0008993 diff 2 +"000000023bf53ef343a50f7599601f849c93ecce63530b0b449a446300000000000000005edbd53fcc64850b5334678199d769514818fbcc79861fc77e572bb4753b7fe2536dc5d91900896c938900e0" +"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000" +"5e653df5956ece518a78a5d11297431af94ce8ba91d80cfb2aa8c5b3095fa256", + +// f000709e diff 1 +"000000023bf53ef343a50f7599601f849c93ecce63530b0b449a44630000000000000000596fc4aa5da839ba267c36aa1a5b29d813747b2273dc03aa9e404c4da0238e2b536dc4cc1900896c9e7000f0" +"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000" +"0e23806a533bd956787eef52dd8edee456c60d6cecbb6175458ee53fc8c6c813" +}; +#endif /* __BENCH_BLOCK_H__ */ diff --git a/bitforce-firmware-flash.c b/bitforce-firmware-flash.c new file mode 100644 index 0000000..b4f6aca --- /dev/null +++ b/bitforce-firmware-flash.c @@ -0,0 +1,108 @@ +/* + * Copyright 2012 Luke Dashjr + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#define _BSD_SOURCE +#include +#include +#include + +#include +#include + +#define BFL_FILE_MAGIC "BFLDATA" +#define BFL_UPLOAD_MAGIC "NGH-STREAM" + +#define myassert(expr, n, ...) \ +do { \ + if (!(expr)) { \ + fprintf(stderr, __VA_ARGS__); \ + return n; \ + } \ +} while(0) + +#define ERRRESP(buf) buf, (buf[strlen(buf)-1] == '\n' ? "" : "\n") + +#define WAITFOROK(n, msg) \ +do { \ + myassert(fgets(buf, sizeof(buf), BFL), n, "Error reading response from " msg "\n"); \ + myassert(!strcmp(buf, "OK\n"), n, "Invalid response from " msg ": %s%s", ERRRESP(buf)); \ +} while(0) + +int main(int argc, char**argv) +{ + myassert(argc == 3, 1, "Usage: %s \n", argv[0]); + setbuf(stdout, NULL); + + // Check filename + char *FWname = basename(strdup(argv[2])); + size_t FWnameLen = strlen(FWname); + myassert(FWnameLen <= 255, 0x0f, "Firmware filename '%s' is too long\n", FWname); + uint8_t n8 = FWnameLen; + + // Open and check firmware file + FILE *FW = fopen(argv[2], "r"); + myassert(FW, 0x10, "Failed to open '%s' for reading\n", argv[2]); + char buf[0x20]; + myassert(1 == fread(buf, 7, 1, FW), 0x10, "Failed to read from '%s'\n", argv[2]); + myassert(!memcmp(buf, BFL_FILE_MAGIC, sizeof(BFL_FILE_MAGIC)-1), 0x11, "'%s' doesn't look like a BFL firmware\n", argv[2]); + myassert(!fseek(FW, 0, SEEK_END), 0x12, "Failed to find end of '%s'\n", argv[2]); + long FWlen = ftell(FW); + myassert(FWlen > 0, 0x12, "Couldn't get size of '%s'\n", argv[2]); + myassert(!fseek(FW, 7, SEEK_SET), 0x12, "Failed to rewind firmware file after getting size\n"); + FWlen -= 7; + printf("Firmware file looks OK :)\n"); + + // Open device + FILE *BFL = fopen(argv[1], "r+"); + myassert(BFL, 0x20, "Failed to open '%s' for read/write\n", argv[1]); + myassert(!setvbuf(BFL, NULL, _IOFBF, 1032), 0x21, "Failed to setup buffer for device"); + + // ZAX: Start firmware upload + printf("Starting firmware upload... "); + myassert(1 == fwrite("ZAX", 3, 1, BFL), 0x22, "Failed to issue ZAX command\n"); + WAITFOROK(0x22, "ZAX"); + + // Firmware upload header + myassert(1 == fwrite(BFL_UPLOAD_MAGIC, sizeof(BFL_UPLOAD_MAGIC)-1, 1, BFL), 0x23, "Failed to send firmware upload header (magic)\n"); + uint32_t n32 = htonl(FWlen - FWlen / 6); + myassert(1 == fwrite(&n32, sizeof(n32), 1, BFL), 0x23, "Failed to send firmware upload header (size)\n"); + myassert(1 == fwrite("\0\0", 2 , 1, BFL), 0x23, "Failed to send firmware upload header (padding 1)\n"); + myassert(1 == fwrite(&n8, sizeof(n8) , 1, BFL), 0x23, "Failed to send firmware upload header (filename length)\n"); + myassert(1 == fwrite(FWname, n8 , 1, BFL), 0x23, "Failed to send firmware upload header (filename)\n"); + myassert(1 == fwrite("\0>>>>>>>>", 9 , 1, BFL), 0x23, "Failed to send firmware upload header (padding 2)\n"); + WAITFOROK(0x23, "firmware upload header"); + printf("OK, sending...\n"); + + // Actual firmware upload + long i, j; + for (i = 0, j = 0; i < FWlen; ++i) { + myassert(1 == fread(&n8, sizeof(n8), 1, FW), 0x30, "Error reading data from firmware file\n"); + if (5 == i % 6) + continue; + n8 ^= 0x2f; + myassert(1 == fwrite(&n8, sizeof(n8), 1, BFL), 0x31, "Error sending data to device\n"); + if (!(++j % 0x400)) { + myassert(1 == fwrite(">>>>>>>>", 8, 1, BFL), 0x32, "Error sending block-finish to device\n"); + printf("\r%5.2f%% complete", (double)i * 100. / (double)FWlen); + WAITFOROK(0x32, "block-finish"); + } + } + printf("\r100%% complete :)\n"); + myassert(1 == fwrite(">>>>>>>>", 8, 1, BFL), 0x3f, "Error sending upload-finished to device\n"); + myassert(fgets(buf, sizeof(buf), BFL), 0x3f, "Error reading response from upload-finished\n"); + myassert(!strcmp(buf, "DONE\n"), 0x3f, "Invalid response from upload-finished: %s%s", ERRRESP(buf)); + + // ZBX: Finish programming + printf("Waiting for device... "); + myassert(1 == fwrite("ZBX", 3, 1, BFL), 0x40, "Failed to issue ZBX command\n"); + WAITFOROK(0x40, "ZBX"); + printf("All done! Try mining to test the flash succeeded.\n"); + + return 0; +} diff --git a/bitmain-readme.txt b/bitmain-readme.txt new file mode 100644 index 0000000..b3e6e5a --- /dev/null +++ b/bitmain-readme.txt @@ -0,0 +1,58 @@ +###################################################################################### +# # +# BitMain setup and build instructions (on mingw32/Windows): # +# # +###################################################################################### + +************************************************************************************** +* Build cgminer.exe * +************************************************************************************** +Run the MinGW MSYS shell +(Start Icon/keyboard key ==> All Programs ==> MinGW ==> MinGW Shell). +Change the working directory to your CGMiner project folder. +Example: cd cgminer-2.1.2 [Enter Key] if you are unsure then type "ls -la" +Another way is to type "cd cg" and then press the tab key; It will auto fill. +Type the lines below one at a time. Look for problems after each one before going on +to the next. + + adl.sh (optional - see below) + autoreconf -fvi + CFLAGS="-O2 -msse2" ./configure (additional config options, see below) + make + strip cgminer.exe <== only do this if you are not compiling for debugging + +For bitmain mode: + autoreconf -fvi + CFLAGS="-O2 -msse2" ./configure --enable-bmsc + make + +************************************************************************************** +* Some ./configure options * +************************************************************************************** +--enable-cpumining Build with cpu mining support(default disabled) +--disable-opencl Override detection and disable building with opencl +--disable-adl Override detection and disable building with adl +--enable-bitforce Compile support for BitForce FPGAs(default disabled) +--enable-icarus Compile support for Icarus Board(default disabled) +--enable-bitmain Compile support for BitMain Devices(default disabled) +--enable-modminer Compile support for ModMiner FPGAs(default disabled) +--enable-ztex Compile support for Ztex Board(default disabled) +--enable-scrypt Compile support for scrypt litecoin mining (default disabled) +--without-curses Compile support for curses TUI (default enabled) +--without-libudev Autodetect FPGAs using libudev (default enabled) +--enable-forcecombo Allow combinations of drivers not intended to be built together(default disabled) + +************************************************************************************** +* Run cgminer for bitmain mode * +************************************************************************************** +BitMain options: +--bitmain-options baud:miner_count:asic_count:timeout:frequency + +For example: +cgminer --bitmain-options 115200:24:10:30:300 -o http://stratum.btcguild.com:3333 -u xlc1985_1 -p abc123456 -D + +###################################################################################### +# # +# BitMain setup and build instructions (on mingw32/Windows) complete # +# # +###################################################################################### diff --git a/bitstreams/COPYING_fpgaminer b/bitstreams/COPYING_fpgaminer new file mode 100644 index 0000000..9db2c5f --- /dev/null +++ b/bitstreams/COPYING_fpgaminer @@ -0,0 +1,23 @@ +All the bitstream files included in this directory that follow the name pattern fpgaminer_*.ncd are: + +---- + +Copyright (c) 2011-2012 fpgaminer@bitcoin-mining.com + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +---- + +You can find the original sources at the Open Source FPGA Bitcoin Miner project GitHub repository: +https://github.com/progranism/Open-Source-FPGA-Bitcoin-Miner/tree/master/projects/X6000_ztex_comm4/hdl diff --git a/bitstreams/README b/bitstreams/README new file mode 100644 index 0000000..a00d01a --- /dev/null +++ b/bitstreams/README @@ -0,0 +1 @@ +You must put the file fpgaminer_top_fixed7_197MHz.ncd in here for modminer to work. \ No newline at end of file diff --git a/bitstreams/fpgaminer_top_fixed7_197MHz.ncd b/bitstreams/fpgaminer_top_fixed7_197MHz.ncd new file mode 100644 index 0000000000000000000000000000000000000000..1df4e1d051141b17951becdf0bb089c76f5c1954 GIT binary patch literal 3682603 zcmd44Ym6mHb{=*zZ)8vAZtr&9*_nQKPb9e#hYv5SyZR~Cs3Z5$Z(0OaZ9@&o+hdYN zv`uLVk}wF{vx>=^SIBD%P+l8qcmx{A64l0{{eyyj5Chm=XYRlTAva9JHedsSVbSnE z8%7(n^@AF#{GG^~Rrj9mndz#Vw~D7KGai{Y&WZCyM#hO(QgrM^`$y!xPv6@7jbD5J zuRs19zxtcs`|a=l#`iWK|IV-e%GK{(xPJ8)Z~xoh_#40cE5C62U;W-+zVnOU`AZM} z@_RSa```V}FFg3IU;WKH-+piHcP?GLcxmm@+C}=;>8n?M>(_th!sWHiU;DL7*Dld7 z)0Z!%xzf_uu`8Yky3CwARpvn!f07{lkqvyzpW8;iZJ=qDSvs_~Y&E^@M`;l+?QC z(Z%->{~?Yo91V^?CR*P*^S`bCm_VHR+E1_#l6b%L2%d~X%TeqR#mgme*uVbSKmP~N zh%dFj{wtkePHX-`7`YZf7V+?D5f7gh@$hL8kAdJfcHMht%San(6C^sR)xmB_!MMSUL;*WVENdqd>!4UxY$ME>3o`Flg;?+uZ^H$?v45czvU7mlHyNCxre^i2Ck?(B}!E&l5tQCxkvv2z{Oq`aB`@c|z#(gwW>+ zq0bXSpC^PqPrRhh#qxhbyl+m3_st3MzBwV@Hz&mVT*z9$8~Ck4JI1->T*z9$8~Ck4JI1->T*z9$8~Ck4JI z1->W6`|YH__oTr0q`>#2!1v@!_%5~=P6@nE3A|4UyiWWZc-sc70?+E?8BlPo*(8oJM zAMXf#yd(7Sj?l+DLLcu4eY_*|@s7~PJ3=4t2z|UG^zn|+$2&qF@4Tds#qysB{y!XR3d@l%m zF9>`u2z)QRgzw_>cTwPdQQ&=1;C)fxeNo_jQQ&=1;C)fxeNo_jQQ&=1;C)fxeNo_j zQQ&=1;C)fxeNo_jQQ&=1;C)fxeNo_jQQ&=1;C)fxeNo_jQQ&=1;C)fxeNo_jQQ*BU z@Ld=9t_ytE1-|P7-*tiSy1;i`;JYsHT^IPS3w+lFzUujK|(f$zG&cU|DSF7Ul1@VF%KxFqnnB=EQ-@VF%KxFqnnB=EQ-@VF%KxFqnn zB=EQ-@VF%KxFqnnB=EQ-@VF%KxFqnnB=EQ-@VF%KxFqnnB=ETO5*~}~`%B_^yCj~s zOX7LEB%Zg+0^iF5-^&8u%L3oa0^iF5-^&8u%L3oa0^iF5-^&8u%L3oa0^iF5-^&8u z%L3oa0^iF5-^&8u%L3oa0^iF5-^&8u%L3oa0^iG`KD{jPy)5v(EbzVj626P?&np7& zD+2E;0`DsV?<)fDD+2E;0`DsV?<)fDD+2E;0`DsV?<)fDD+2E;0`DsV?<)fDD+2E; z0`DsV?<)fDD+2E;0`DsV?<)fDD+2E;0`DsV?<)fDD+2GU0^h3w->U-Os{-Gv0^h3w z->U-Os{-Gv0^h3w->U-Os{-Gv0^h3w->U-Os{-Gv0^h3w->U-Os{-Gv0^h3w->U-O zs{-Gv0^h3w->U-Os{-Gv0^h4I;k)=eUlVv=6L?<}cwZBEUlVv=6L?<}cwZBEUlVv= z6L?<}cwZBEUlVv=6L?<}cwZBEUlVv=6L?<}cwZBEUlVv=6L?<}cwZBEUlVv=6L?<} zcwZBEUlVv=6L?=2cwQI!x-Rr}UFhq&(ARaLuj@iz*M+{W3w>P|`noRkbzSJ|y3p5k zp|9&gU)P1ct_yu#7y7#XlD-z7k6#e{-xK34?>(Wv_g<2Ju|MT6it9#%zbV4s7WKimMSbvX@qB(;{Qf)Q_umn}|Ble#cLbi_ z5qN$_;Q1Ya=S@++-4xHmO(D-sAMrYKK0MR~d@%F|6zo^FcrbW@b4o1#436nNee`n@IedrRo|meB7lq2F6Vzqf>b zZwdY068gO*-XFKb`{S0-|1F{aTSEW0g#K>{{ofM$za{j4OX&ZW(Elx=|64-;w}k$0 zy`=xe=lPbX4{wR*=azVWZi(mTmUw<{iSe7;0`J=b@7n_J+XC;~0`J=b@7n_J+XC;~ z0`J=b@7n_J+XC;~0`J=b@7n_J+XC;~0`J=b@7n_J+XC;~0`J=b@7n_J+XC;~;{A79 z;C)-*eOustTi|_L;C)Bndq?1VN8o!$;Cn~ldq?1VN8o!$;Cn~ldq?1VN8o!$;Cn~l zdq?1VN8o!$;Cn~ldq?1VN8o!$;Cn~ldq?1VN8o!$;Cn~ldspCbSKx71;Bi;raaZ7R zSKx71;Bi;raaZ7RSKx71;Bi;raaZ7RSKx71;Bi;raaZ7RSKx71;Bi;raaZ7RSKx71 z;Bi;rarY%W7W>ccis$XFc;4=c=k2a|-tLO}^`5}{p1}K_!26!S`<}r2p1}K_!26!S z`<}r2p1}K_!26!S`<}r2p1}K_!26!S`<}r2p1}K_!26!S`<}r2p1}K_!26!S`<}r2 zo~U2%3B2zKyzdFT?+Lu`3w-YjeD4c;^N~`fs?+2g*snVh@1>BTII++e5p}0W-D-#+?}{3Z2fe z@}8zRc=rkA>dx*7UbQ$87-&jxMEL7SK??08>tfi|^MT&{+}h%D*QVa(+v_d;*Gb6Y zCO%rSbN-}@T(Qd<;G-qh|H0DGv1`vnL-6;>;5cB~ErJKknAxFr1X*`J%#)_-@YuHx zo&z5HZR}Z&9-iCXMkTo0Jxj3k4{Qvk>0C1WRP0?7a|!I#>w945i6(Rpn%#>WHQ($Z z-w=f}&!`-z2r+3eP8UnSLfK#m4~B|GDGr9|0+(SQ`%*2W__~2dM`n9s!DV{n&WCdP zZO|bw{@L^ozi=|ALr)VADp*6Pm1 z)p$_Slrp+4aFvg;z*g!3l)s(q972^OSklpd>+iT4YQ9gkasqM*$#z6t@C43sScvK^ zAOt*wAYJ{9{|P$5*bDcm9{DFOYy5O}HRh}G+Lqi`UFkFKu*QSh+A*Y>EdCUZT}doz z@Po*`2Ss}5%QR?hdalLEeX2|x=BrGy#P%s!r3_iHbp4vzkk+ci`z!W+pRz%#1+GF? zSAS{ik+z&qk4ifs&ki`}wnUuC56X~-5FqFRe_)P7&s_E&1Q}g{jRzStTpj1GkDIYp zIOYoH=RUK1>pn;)?nkH*$^iTaHTeTF(}0X-{uQ@wQF$V8K$Iclnmi4SAp7mJv1-<# zH-wtCQ+0qdx24d!^#=G0J>%G~w$m4VX{dyDd5n;AFYq*k(w_EGCjy_?hdX;GZ#Tt~ zi(cE8#sE<1npb?oZq4NGSUAlkun_;+0aP@}>}EUvBgAjH2yWknuCAQF+@~MyJm2%Yu7$f&Iq(VqBag*!qkXz< zA9-*~E)J_E_reMBYBc2Al3}o5IiN5E7NZd1)l>V@ONR}G)<=P`PKIibZ(wf`|{|zA$%u`l(~tg;&hK<-RDv z+S&ViEbR-hPxYHwHoFnXnEZ@%B~Y;)y$Fp+w?O>g8THtx6RL}z|arl z_rG)`_zHzxJRLwEZL9F_1?ZW5@$QehOh0|d;U|x~@Cw~?Kk}iMeO<>j%0lIhkhtDq(E8L)cv(*f9t~7pQVm&*;q<1~qqG^Ly^*5b z6Zss;dCzdPKIiG>6as1FCeF9ez7DMBtU{ce@~zbQLWIV)7hbPUq#%471QJBx99Njn z0Oo9YK^rmmOYomoDtYy<`-n|^gMx1Az}I-(gDsQ+rPDz|Nm15iy;Uij*Oa4MRmbQb zaV&KojOWtG;m^rrnO{?hd~Z<^*O*?HQ0fON7#oz%Byc~lb3g0NJB}ktNaIOJVeR^L zSi=s@i`eKG(<-!PMkpK^?($$8aj<7%y@oN`*i_+TE?qg-?GUyu44!a`M<2B@_Y7KH*{wm?MALH1Ob5*z z3*5uP^hTwZ!}6?Mshdz;7Cgt)LtkX6eN>f65ar?2YfwO#G!7pfV!I%4>~e%|NGeJX zsm2AKirT}=(MQ-*dJ<+?W3u5S)+i?Ye&T*AmXOFS$94YKE~yDR9XPUxEjL9hYK5St zDy4BV98IS-(hVLH=)v|20P_Uy7O5g{L)3`unZ!{wA(Vh%IG{252ss)@|Kw>r zfJoNEV`T_1@S_&uQ5brIDXYKqCY5_Ubf3Bh$6cDUc{7fk3%i?Sn2zlJwih2a(A}}U zz4_wB`g$Aw^wVwXjy4W$X?!t}S@PVSURci(=_DXb#Ch@J`SUiswzf^((Z&(+CmZ{h z;1CAO1WWpb|Ksms?Cc1gbn6k`VnB`^`(%Ud?~RRP+Z@JB836U*0W85|w0+|SyN{nd ze)7>ro15#;*Pr7Dthm0&NwDz=CkM{pI4=QE*gkp`lD%LFHa9=|sDBAMtNV*wXO*Kr zbrpo1JDuX>T=Y=DefpH`$fr-AJU({p*|X=)n%x8)oCFUaI!pH9VMwq|&z=#vCvyAo z<4vBvx4oLYclwklv=X>lY5NIE-^wL$FG(kj^W~jjt;_SW-1}oOy6;fTuLt*vSV;Nh zn0n!QzXAVqlVd&1)92qkf9LD^GuIBP(Qx9|GWQ;|el<&=+DYhTz3g0C#-s6Lux9fi zhFza>^AKVn=jh@vxX8x}X;`^lBMlb$P-9_=26LS%bTDPZ8fqvbwpma_1J`g_i73g- zslu9nIg%UB{S(^lK%Jk3%#{Mi!DMW?BImnQ?s!+B38R=<%MhSI@jQBM43Ge=BHx_u zjQDjIm4Q!rq$5o^%$6dmxcos2wXS)9*Fd1QbF1xyB#j^SN1Su_)}5)DpZ&or^WDCi zd)G)*p`@ceK#r@lgkA5n4*O+B0i`*FoQcPs8%*tacFU5EXv7^tkiPbx{+DPshV7iI zulxbBXJJq3EF`VD-)#WYEmuuA^PhHEwm9pV-$nF#(n!s%*s-4D!DsG$)d}Ozl!T?4!i$=r zLZl--bNg|InkqX>(#EqiYj*8cSXp*)tc-#psH_SU0IuRJR?H|aW{t^EdoISsu1W-S zC70f?7@~VIP*H*wiwZPaE+$b)sbjpY{5aHv!}W*ix)rXIsdJYS&sQ-Zj&;Jh*J^7> zBcDq^sv-*X096C3FNB!_?Htq}!kq>8udnCijg#h7nHUYHnM!9$2M@{e(fS^40gqdwkPUZXJ3 z;5S}Pnp7R84c-jKM<*!4aalMw@#7(f;dZ=tTqoU5Xdj-a!CPoZ12@5Ci|nKccm#0j z!j___#)^L*o*J>-E8II-gSnrvwWyV{DcnI|?Wk-jgr^(^1{KT-+yDu9W+)s!_eUA2 z1jD|y@~Q-#zI74CI*gk-_Dap+nAmM<;}^QZ&>&Zhsz3HfY1{;p%=b_Z!Xndg4Ookj zo;Fdai)=`Bqo~M?=i)QQWS5pM0c}!gIIe6FH7M19ptR{=Fsaey8bH~$7}zwx*yI?) zlDJ`9PDXfxU^W(hi*oARU5WPTLq9VYIsiV!lW(DKiV7OnHg3b&AV7RcqnL`i=MvD0 zjEBsNs{-R+iC5e0;Qd?p*7B@Rp40uK(Ttq~*1JY}@t0FUPt2_4DieviI;0x8fPQsI z!1I<4N`epGIyM6pE;HSD@L+qJ=byEGuMp=LyxUH$8u{7P+pGbTXGb#91k6(#WGm-(G^|kB?kOIA=4S)#vc;Nit7E1K_Tp z*V>XH75lNmP;}*2YMKq=$PeRT%8m9C8qyx4N*+4giv17-B>)zzOqe) z8R}y_HZW=PQ8LDSDkO^eWP>r^PX*5Xk>aF0WS#&86l(*E_7}w}RGBTwT_(@s@~Q}G zrAp(JR$LHCmtYE$!Na-D*9(+>_zMO(2cd zhD=h=RAIpkmKDz2584w2Fw=FtHHcx2z-Z3`os1I?y}h*|mG^~yLQ{P#W|i2$Tx_Qf46c(J30D!JLgCXGgd8%I(ZcW zu$n1whe2tGQDM0X3iEf|o$i6eFM1V?&FKW5P^AxB|SlTq;}wv>S{{T)4MW;YZV{ zYV0T;*|KO*1C+jR8=jlv4>QYL2{j}h5Jm%!C&0qWz8(pa#B}I2H5Sn65g-VPj3%Z* zom9Hc1C$D0trDlGt#~MaY1%qHoufh(7-VW9U#Fp>M)PxkNkXU}`AvXwfF)z4M-`Vt z!%Xow^h)Th&1?}iM%!X*? ztWBGOYbI_4p$vi<4W~}`+)U!mUqs;OlQ4%Y$P zwJ;ar1+ODQ6CTIE_xchiC0PO#sJC1Cf_qgH~t-rRiQ)>5rnf-uP=bZaiP_4p@;-x6!+SDOs37bmPYM_L1*1D6ppJ=pSuwAH8w( z25Yn5-LH-wU4PykL=Q1bhb4d=iMd+Xkzq#ymZ6iN-`%g~taw=SejRfqWVk}sM+o09 z=;m_S_mO$`oYE13Y<&{cyv-|&c$U!f_22(}wqQU0n8R*`-OL35N7eE`I7OZz z*-8*#E!ROy(5)8RMs@Jy@n8L`m_WGA^VGV$wl1&4T3h3_j$M3zTIeL;672m|SZ} z0&6K%V9K^W8jPfLE5ZD`x@x1AIeQ~ceNa&~(b`X~wxku>u;q9-Dm7ZNp)wf`ND*lj zD<9=0TZZ9Fzg0#VlhuhI`HjC-g|${*4y#QWU#lsv0z#uPsmJ`oI-|gHDd?4a?la<5 z1i*P}VKZ6^eJ%+JN~nPga?Q5ZJ3D&|q2&T2v|LHq3aMbsi_Z`xft%r(Xve`hmSvhF zE*^c6M+$~PhqTA9A@HGIlWWlWf;+ekp|=gSnP3z-UJwKDQZP5i3}}N-dgw0rJd6svf z+uZX!17%_jZp7UBy3#zXRK~<93hSC*+KwPa*cFL3X2~<2LbO5#X@j0cJJD&1^sGfO98dr!MG>@# zEo|V5!hp0UJzpx8Wbb}H)Nn@JfdST76+Rt04o=S&`5&@rze{}=L{#{!)(ZSk%%FLG zDk!e-7&6#CE_xXmM0by)r(b$Y@WrL*(UJD&a5MJ7{cz@{U6?N*z6*17U$lWiMP6C8 z@d;MjV8Ku4J_qqN$7g;oLa--~q3iVZwr;$884OO)jp76B641u>dYzoI001ECH{YVcM0T2fjN=e2Yd$ zj{aQyxrr9ncZ@-V-M()rLcM|Bq!!|CDP%{Xv4eWHlDR2Lyf5h=S z;TgA2K|L_uNrENdRbwncYXk3IY9-hS&pc}&8MjQBC1^_+?>5p((x!(ob{30^$V%z+ zBp~$4fL1C&jTTIe8lC^@0wWa~J|BZ6Ia)i`!#q0{fdY5tlfmC~C)A4vlbc}Zpk<+x z1Y@ZUDezM&QWfl>mm9Ne6`NidcZneH$ftHegO`b=tRS2%H|*MI_?eVD_kYsJ(FFM_ zG!I2^{!%Ja1F7(`Yg&GOTFn?ksvVm5??mpyqvDB5xiLG$(rz!*rOpF8$&-2*7V*Rn zbFJ#U!MGid>&8wkS-<)iD_syoD6z}KAl8#8(v?xEpJoPaJnTJUz0hEgGn<03?Xu`~ zp5@(`a%}TiA!%)MM&IGTg?<1uGOeo6&fq?Q8Fh?1w~JrOtq6;=$PY5h^BLIgGAVX{ z*}|Zn*4)M}!?AI@GJ=hsu~JL1T>P;%c;K+9U1BnW#}MP!ab2o93<8yy8oSI*rUzg9NS%D3a|>20ruL!(*bisF+yy785pL{2oqc(-tv?pi2-0B@++!_4Q3ms zXmMv#w0cV~_V_w^eBwTUHs`(NpOZy(Ox)puvqIZ@(iBlSj6F<&)qas9%AH&IhFsKLE0Hy#-E^SWp zUaN8TFCu}6B`7c1yl$pZ72;jol%DdNc!GHi;bf@!)f9T|4xA7K60i_uukC}Fi(;6L z(u~G-q?-y;9KyP3!hxEOqPTI_@#1js!yraDaQ)XyWcsC_q7V{T46*+Ra4^ZVwxdB~ z&@s%rj?_%yGb|L0X|0c)ERHo&Jf#XiLED zlO7$RN2~|9-{f@R(wAEIQ5SYDpFn)QXX23}9wl0TjuBJrKJxf+yYoo56Uky@nH$c1 zH=en@-b&E!Yw+YryYoo56UidSz9kUD%7+l76K8)HBiUbWN3xH7zyKdR_Jbd=t;l{?FYU_Z3(2u#-*iQ?mEiH?jSbAtpY1yGQW2P!W_i{~A;8|B z^ozSA*4@bUj?32jZTcZ@btBlTu;x)FG2!~y3m!k-#0-5_^XBIKo*y%dcDI1O==*z4 zECKqaK1Tw!Xj!tE+IyFvopa9|c;BH`2hX4LPAr?7%=6*Hr%x9om2!guN7%c zIP;igtZAf7qOB$maTT?^df@m~0i2*BWxW{$OMsln5Y z(s3jxn`xpAkqC9Mdvsx=P?{Pv$x@}s_;G3`nbm7%eo4>Op1{Ecy(yaxn)_ z?~%>t+@51pMrBE@&|VE{g;~jg4ki!_vtZGI5Nb?huvkFItrQF-%|QB`1ov?XbMZs` zOl4TK-2S|_t@)-o|8ps>ggp-udTgBH(Zfpam<;p8CiYE^RwG%ek@s1%t(69N^-F*? zB^$zI*h)P;PL-ddYdJ}Mzf3$`>q=w)JVMzZ6brq>+qHIgLQ8pEqAD7gkkN?4p$7Q1 z%3;(gRVN0PjgB<5LXl1gdz8S^!ym5qMFnQyV&*~}4|AS_Y^o@Za#NUi1f$L5KB3Ri zn6c3obwiE6mO3mtcL{NVVlZwJ1uGFlb+Fx0;(5Y=7b>MOq+B6sJxFw7p`QU~AJlFX z#lAjhP>(bhF}xiT^kvnsBaEM-pRXnjT-dI_TcT9X4GZ-WPsqF+O?Y0qb!)mytVbbC z4Q4yFU0w?>!X+FRW*0}#l!XIZWlf1~F6`7`HqRh0c~;LTYD!FyE};QCEv+TIGlstH znL8&JdRm8BQx8$rqbaZD9w$6cJ+4A_VaZ}mw_n#KReCF_S0KS)m{0P+Zw8@1$*_Uy zu&9g7c+D8S&zXs^xEO1@Q7R%o^UD&Yp?5-eT7hZrO)$oAbunnh5shrD8(8<~-<+-W zuCxs*ZbbMt=v{(l7+~)AsBmk#EyzqK7L{ZZ&}0l@@N8mU7f7Rf6QTaeS?y*L)p6os z9y*qV1Ve*HTBEBeTnvXqm|DQ@#Q4U&B>ETP(UIMG>9aN6pS8BTf981IU6=XhT8&F9zOlv_jt|r<0l_{ z(5~g)hUI+j9LIO-yxaI2H=btdx5j((@hT)( zoCQ9&mfL(+Ev)T!Yrg%ue;Haku^j>LMuB-Y_|vC6N1*!sU3-yd4fM~S_sC^Cu^j=Q zmjG+X4@d%DZP1N`x3dA*l;@Fg&}?=%umIViV15I11m{Fb(2sy8D9fbQ@vYlrM zmj~j**o-x0ZmTUHHd3S%>?4@fkR$PZwyK#RV1g0}(tKjl?!44#3)ApNe(7hxz8GLt zbDpbl5NxsWnxj+4w`x$sN5L3;lw$LRT7mSOE;x3dq;$^T!H9-t68d>k#fcv2JU0eU zzNxG?(KWsTh`l{aqsS!KB+He?88@|0uJb=rNnWBywfM{anr7+ZA9Y;j6; zp3iz{jC*6D7|zOn%Lf-B6ZbasMLO1YGHfP3W`ScGvW??Oi4DSRiigjPrYIH|1tn{J zN=X}w5mEGD;M*PGkCStk6eaUg;Lwav9CN$^vJv@dVVbOhItV>Vz(C_=>_b8DhYgO( z^HfG$E{TC#mMSWyet`YZ@M`i0o`+|i27cBwzUSrSRY>2{n%8#o(j4ye_Nm%(D+0>8 zEeW_~DH>31Ytvv#0w#NhsR^(TnT1j;4Cq*M<>NEx>41D-0I!7_P+Gwhz8`Xy6#N^bWULr9Mbf_4NHQyG<*3EnaAWg zsGdo2EQ$hF{C8n8&ey}~(GmJ3P6qCWGdG>NT|5UDM#^2p5h%oYmO8bgeEju`=g%<_ zeB+~!+Hs~QAZ$9tb9&w6UG{S;+6}lO$_>wPcxhq@cqF`&fcMqk#`te1fg9n)X>}4D z!qB&1NniTk|2am&ztWC`hkqa6;J}>r^>v<;zYf~ic>I{dSY!`veey}CeRes__jGan z42d6eW3Mkua13-X5`2w*^2pKq@gJg&;Q8(K*B&8k;y?V>`y>Bog!%65CeA(L+}(Nb zbQJU6moAvtjf{2L?a9D4`Qow)v%?sASgJ%z(@%S{hW<#Gt`(@3QN`Nf45^(v( zH%i}y01r9|xb9$`tVRMesgQPDRJMu?c6~7;enDj!&eq@xUJn=vC*8J`+^Zo$h$(UB zj6>@GDgZ#J0K`D#}%q`>w2nD%T+{aI2y2PXOp2 z^H!c?})ZkrNX=^8-@04;HL?eGjb~}n)b+eE~%IRg5O#MwN>EdzT&3Y zpksnrhl0qi=3wtM;4)yiZRp6jrW(tGIn`(^GL`O`s9Dfe5cr@^sl@DyoVRxN&D7W$ zvp5Tk1+yJnPBd7DP378WxT$x-%yq)YTy>E^=eod#;xUc@MGo!c8m)AV83LNOSLd=Y zM)wcq3_v`d-wnL_kOv=q8Dd32t}uJvcmq^N2~aUP1PF~LA1VMDcW(Rr7_f8}242fj zs)MWycmy2EhQ`PIu(i9Kmj=U)4dU?~5_cVJHQ(1Sd9{yu50M-B_O))n6yXp%w3Vot zt2n~LX}nT>CRZohXI_1c3++fZ>=H{B)eV1Q)KY?H1Cx4T9bm^WfZZBTuaVk*Zi&Na zimkz{3F<1xboy$bZCy3i2FhlbaGn92bT~oMECyR~ebazPy0MhN^L%Ve*3E1P zOi`EuZlNo&339A-Nz*2+ER};vVa9_1Qm`ZzK%jo?o2>74XhXHfU|WlI zHazmJY=UxVEu~spw*U=QXWzWr^y*;fVF!C`CO?cq9tp?d`U0c#HQ)d&24=nXG%+px z+__Xb@5gmVo!f?bJI{}NcO%}N%h#AuyT5Wf$G11ey9v#9HSb2i z=SRL>oV%0vS6-C_-6%J^^P}H927UP-V{{T$%Z(eDMGIoa8sc8=Z51Me=;ZPZ4*CVWLt!^~QP)z*HivRj?p=Kr~y$ISbqf!uy* z&e!KjKmGGxxVykt>CF1CbWG6$^Hn`+e{8&s(Wj0{;3fOTg>fTM2F)J?7@)_#!ar>i`z9DJ;NhI=&=nwO}dp) zoYDT6(XOwH*@vr3$bg1pQsT|2v5Swlg_jt*#Ci6k=aV+tpv6JGV;&SZK=Nz_ZXQrCp>m8Ep!(1}R0DyOZ}4TWK#eWb zKd4Aqwg&l+&?xVR{5)0$%V>3jc?`n>@A@IQCNT!Lb1dQ;kXM9Np=aie1yqhYw+A6G z^B0*#hK$aq+~$Tijxmsof*Epio77z4kUeL$q6$&#|A^AI*j=dL%HCG61=Up z+VSKTo{Mv#9#19mh$jJhV5V$v8#C1HlGPX!qw%o-3)HQ<$b4Y~=j`@I2E5~;u4x?e z3YBVr8IIVS*f%ObA;7!_-Yp*YBoJXhspE@qkLAGAiN=&qpTgMUBWL`LIba_~wW5eQ zV>dqxX%pMK-L7U%ivCV&;Clmu*&+j&_<=&LfKDHjMMi|MA-Hvax<7_5r!-Iokz5C9 zz6h90u8vS4qYYkiRCmewUpjd&#q2S{G+WHZXwED&J)Mnc4{bZL&B^fv?gdy4p_jw* ztZy=y?QJYTusquv#tzXG$_UUYps7)-;m)Z-iS@_}GI;eE?}!+C8g5a|J;4+dHEvMJ z6de;#6K=_I^i3X(Jl@H{0|(6VMzyGOl~NOu@%#Xqz%^WIz!y@uqh}G~pz=qBNwLRb zfUc($HA7@;-2{`Lg-?nt&vAK}jZkUewZ_Cn8R9w?qa z+}iHV%^!UI2VZx#$cydmjZgX*vHncC=ee_gP6C!}^99i-Zq@l`rm*A%F|2$DLAv(u z#cMx-Pj#LArAI_x{pSmaIQ=+V=2(Z7%kcL1zt8UG=1RdKjJTQs&1}SD6lV+LRNzGnb30@x><55-LT6?f0w5se_<5k6lUTg5u zNkJ9f;QThNN#op-?mWx8-a-D*zphqKS@2j6hhgW;U{?1VY-UCu2i|kwI!QyQIGOaVO z6I@*d1!-+FYz!A^ZLu%c&{^>Vltsskm-}-shvJasQ~v}*)s0-o-ZZP~Cd0IU8hdrk zia`0qS-V;a9u|WmdZq|F6J}-Fcm)-{Kg5g%Oi(wNa-OyscRWqYwBKFHyNqB2dpySc zg%mF++f1;I-4aaz0~bLq4ovuNfU5_r3-x@;bi&grf@UzahHCUF3^fZ8xxQAzj1dB% zh^~e(zsvKB39R-|ZDss=@Y)I)&g@OZ_`|DbZwdJOrbkD1zSEs^+6aI6rPg(<5N@e+ zXThEv9yjhru0Q@5tEWNFp4G5mh&~3f`|gimv2aV?wdOgdPnsXOJ{$?UwcI>z{NyR@ zmSgxjGG+vGF1vBZV-JpD9N8&h1zPCVa7VbWfgOZ2}YSXk%7!=Gpa!D*^X#s~8>Xc=Mv@ z@$BzhBdh3E#rV$-any=PlhGCF6zc^|O-iGwiuvgLI&yExjw`<X%9J`9=(18$U-(#dDunIPkoBa$Slx;0)ZlL6l0{c8$DNKKs3-%T- zP!Rp1Ot{Hhpe2@JFZG(|S0=8B#N(a`BeW_~uw0RE8A{eC%B78deI z@ml6INp%8FR*f+mye)Hl#*z0f046zO4hSrFr5O(7TIafUMS^+lKAa1j&8#iI5V_xg z)%c|*W&Jh<+d7AM#(Sd7z~6ga8D9Iw&rYx{ULo*jiYTxaE(&78;nh=wJ{#YRZJ-l$ zXEi+coKOX!2;ebrc9KP4rG&ljcm$mC3geyF*`c+Juo8fqi9Tr@X0-yKNuWFP3^TPn z^d^>5I9%M>SvjQ!FMy#5(I^;LY!8T50roq<2sqb`JVC@j1h<}k)uN=N!9wFZW0sKs zae+8U+R?YCXSw9kR zZ)f|?p07@_{@oX!{>k?CGafUYeX(o%Phkf#95Y@kwfh{8wfe;aH)=ckV%K(69jpXh zZ%exxywmZV4!Wq-xbJ**_!>{s`GuM6SbehdG^s+PLe@^W#GTpCzBc5d4&YSb4P+c2 z?7WfgFvR=u;OJm;ypXR$n2sY@(x3Zl8-OXdZnBkqzmyXrQ#si)$nO87?WzkaSzT%!`qqIvv)tuwQ zAyHi%LXg69mrz4}jfiU~`UHj){*>W|Ln&`wEqXq%I=?Cvx-=@-P?{{PNk?Xf->z>b zs&CIBz#v%C@t^z?hl+b|9QiA}$Ofyx@W`@VCwCNB{j*BIf{D+6CaO(B@i;UJ@CpTU zeeeSCvpGkHAc01+;H71bCNxHMWDn&raLOH_vukAQS#8)BhSh3artVXQnUl3accX3(I$%Oy zV_M-G5L_)}2h6)=hfdG2C468~lQ|RtC?D`XBQ`x8?#vbQhAO`(mVTy|$^rU3^ZEHg zOC`4VA9wSI_WsMhe$mYz>MOmyr^7p7{O&LB{dfKSf_K39udnx)-riICz4yOMZpRDm zOZCu?sU;EGh1#|9nallesd-$7Q*oXQxI=9e= ziuF2OA1Zyq@@jsu9V%JKFr&Kj!0r5cE^Y;0VHO@jko3cU088|(wg*%F2Of6dLzCMQ z9_@UV<9Y=-|Li0&(5fsA4BNr0nG^llyxS|cCSGE200xEEmm2EZ5P@e8k8w0L(0 znGZz{gT34-tdlPkbyYWnzMmrPw&9IWYpjaT=cQ|XT%B>od^G>ydota(5@~Am^||50`kNL^oODP$aqj@BGVO*!F!(%*J_ZcQy_NCn5eS zXZOE`R(|TkfAcl+a7wh_Pj~HiKYZK#j9SeKk=Kyk-SR=ocG$B+EDAFTURU51-S_Mn ze^1~r2@)J4dsK%OLy0T_T<5dS&oKu=irZ)!XXiSfc7BdI5IO|YQLv64lpAo;3Xx>GT=Wo|LslVdYsdp}}#KXzEk}*qt-am_ag;29AOFM1SlEFbX zVYJIbdtN#_`%1vGp55xU5bILdv29HkvqD$n;{uokUyo$TCd}C$6npUD2hnJJPk?yuD*moO zNfm#(^C0Qb5v;9XhO^xn*j?a{>nkMp4FP<|zWdHV_kH^nvXn`yfA?p<&<=cC*s^ca ziOvr66pxA?!}jgGw*mX{4YQY4FuW8YrC2J=TzO?nCjq|lhXn?UNv>9cx$gsz+g*Xb z3-BNN^`Z=`4Z#FFTr2SB?s%_Jk`})Y@GUyK(8KU8F=F`k?)L-GYvR_d>}nAP$Zp_Z zeCNLL-@Zm37e_shQ_wvv^a;2A4tzHLYv|@6TcyHqJcJT!zNSwsSnygdu+wCg+S z9yiWY7sqPuGmktK#DITk6gqC&P>gyY*BPcKmkBSls}bg}`S>`u=5q(O?mSDlg97{g z`w^tz?QOLllF_`xPUCNA2H4ur^(__cRypi%HPqj?I%@Fd9Of_+3zw7jyhNt<_|pXV zC$r!1YZ{FOoVh`;yF8-3ysAb*!KE+Ap#UUN4rb<)V8+dP(231&%kZkKWgQy1>l#dhDW zM&*s7LX-i{BQrG21wFhRT2%?))G0unGYp3cc=NX}T@92Xo)0{x_;?MYj`J%?>gts@ zeL>_Qyxas!TKiY5d)Iw+)=uP7cc>%;JcJcIN8s(MWUhP|p z#~@lV&RHxayU*+FrllS3?e~z1?^0qPAM>;mb_IFCdXNvWy`ILGl)(r4P-BcdMV*!E!cS;qDx3{Cw{LiKNMl(p|8aPE z(yF#Tc9G)t0QNR>2!txs?)_A2i?*swY+E)w=*EK<_#13Y-FT4;?EA#p^}$`E#?)>J zd{A2=jcLXN7l)-0Rqp9<;g&hB^h6<++z&#ZM|%>QrUjBUDfBz$(x?z}Q(d_yp0D80 zI%98tG|d^!tuP*>7y$}UC-~S5d7POr)zc(P8a)iiALsD^jZEJ&wTkiiU(IF0ItiB+ zMe`%>8JwVJt<;dQ)mkHf;)Y7X%lR66LCVuOYY+E&kf9eqGHl}L84ur4R%pYHxEqe2 zf&pQl}KZR;kJd8b!}jrTb0*=W#sD$qRgx0DOfL zZ)wlx37;*^rhTFU(X*H(9Iu&biu2NIilh!yh_!_%k!k=}W50;XtK-^)n0N2dw1gZG z0uDUbd(AnWRQr}8YNC=$RMgl4x{snpBW&ZSfGGhb^K4&h?ZCv~j8CdWG(ZeWMu_|8 zTa_HSFB+!S)}v;EDc#6alSQ_|hvpiK$FM}>+N$X^uH(k#96QuwsEiwZFsCiH-y_r% zOH=8^yfZyE_ESknO{4u(hkjGTaB|;VpZeKYdrg%$D3i!K4h;-YU3+k`AaQB({H3aJB2&+0Y_xb0N-4%ng(0RPy?Oo*hakjUTRQr zLwwquM&ZcB6Rr%JNDnoQu)|*kb^~nCV7Y+evvigb>4BPJIzcvhg{e333$O8uDZ(Dc zLcxyX)v(Z5dFp9!gVrOskj0zoJ|IAvAdG?j5|4qx%|F9+b`Gw8lZQj$I)eM?!`8dZ zxm#J~!T=EKWM@X{D6VWlbWRL?6;r*H{l zs=w>k*tJh#6~GVQe)Ka(0_;$aFmZTU{?H}w#E)J1`0H)FH~N#u$By9(XP@%e@j9sc z#`*_ueel+k$4?$JNB)la_V$AZ_~x6_&T>-lj`Xy>w#Hv?>m(p*C3wy>`^x*G1RI|` zctF&T1ltc-f*<|pdjYR2efLLCA7X~}vrj*L@a)+G{ziK{2K({H+sAGk#Rnv~_(5@y z7dq-^HSPnagLmIulwfU5O2FT2-<1HEL4uxE<7#}Iq$0Le&_+Z>%X?v99|S^c#pTx! z+V0Lbf6@q`UDFG3?Vf@kyJ#P#8Yw%wXqGzO2=3Y}xD)GM5{r@h3>9b`X~OFdInCS$ zSSx3@#xW*sAj3-67R zI`dM?w!dcg8%kODym|m^67XyO|7XX8BMSmvo_ciJ@jEh zkxCOYg|#p-X^p*^NkJ7_8$!9UZCPc`uPQ6ADUmUh6ZdHvhh%8b{6nrWc?}uoyjvt> z(Q4$LPa1B)zGCJx^6bphxwGJ&oF*~IYN-a;l$}%sR$*+6re+*tOLmkEpsjt4?_Gfu zJ}D7rczS1~I^CNtX74VN8>mD9Rh8zZjCEy#fgX?b*1*J&L&HSLea%nGur}T>$DP>E z%XlNsKP*Y;Y>HlTXW;IgJ$_*nYdfrBjX9HpI3Go9poC$pDm)FLib+I#+`2H5(pXdQ+1J+RqOL>S5+A)1mp|*}o<_88Sq`0ld3H2x< zrW#(D{P?Cfd-hGraA_kL28Bg-a!6nse9x=d8X&8sP9b1Ks2<4)3WFtKUOGjc$orT> z*!Bv??uUk4!NlnlHo5gpI*Rn9DYP?FZ8~iRtaEg$aJ_AZ=xJgAhKWV#)xMiLdfE9&>-GJ9|u_E_zI`TpMJww&7gMisI74s%u#Reo6sSF z9ujJN6W1v0O)&|_1Q?>4MrkyGAq$0D-m#vDZDo`LLYoG!4Ll{>tAa-t09N~lq46ov z=+sR85Yv|ZQ4Im+(^v^0;UM7Q`1EV1z0-OVFGx>{4Ny_y~MB zWgPK}>)n0Mb$6sQ@7^8$UKSeV+%R_s+YRf_H#eU?eDQ+)Gdpq4CE{zl@$I*v+h+XU z`C~&-ApzS?odoRP<<$B6mf#Qu+XPGc>A!z;?I%#vx^J`}`{3nov_li<#UR*;$BupC zzLxE#u0kd3Zf}3%8}0Qr+$yd8D%^fB;ot8hJ@c=_I_)IDht?Pjrp~2re51YIMz>d% zk>Fcqq-Nh}r=xoJ8|~e9<>uTY!F~E+F#8T&7f)D72>Tvab|l)P=fnvpVf*pp?FVeZ zK40J5WS#J{!SQ%Huy%MGVW$MA2fj|kb|>G@?An+;GTyoU#0gH~$rIjde^COCfByWK z>&aQA1Y9j_Y&>|@>fn=&P6s!RauI&_N8IDV?ru)o9CLj4b-3kp&`R(O9TcpCjZbzZ zU^Tt_BiG{rS4zO~{Ya1pMN||8MqZ1cPz62(YIqw5KdeaOjm`7B0=x<|ULuARze;w9 z=Xl_au`IdwoSr2#Cer)?9dxT39_vI~nj6n9mQ5%AnfIek(3aa9m4ymwU1-=uHm(b;D+~x%s))n54vH9UM{Jf3c}yNF zV=&Yb8slq-S^HL{XzwxsO4vOF-Ry%RtUL3EdRWG>E;SmbX<2QhMG}oL#DcE_Q>us7 zpm!re`z|PT?IoiepFL8MMPshg9ZTmiU1%?E7??UVk%7`-poT>lg?W@@Hr&Dl$2i=| z&`gJ&i6n6izmVxm%kC+(f+jTpguTnOj~b#tIoE8zyWB&FWSAoNNyYPIBIq{4 z!>VAt3<{_{rz-N$-&FdJU(1xo!|^qDHo)Q5yH{SEpgqIrYRa-2IWhS42(=NO zF$=H8k;KR?NPBj~E-GtxXg5~?*F0GN6eN6YFXW*PfPnx@pwcm?+>AI4UL9=U67q!S z1k|))uhhmv(91Fd$652v5pd9Hjb5iZr?duxnX6&Dn~W&K{p|A702>>B z5VAcu^7X06VT*$k#p%|_^Her$jN5Y=Iv9C~!4v^QaPZ1Zy)9QT3{Ox(0Wm0iAXiQx z1q{pmlmv@H5aw0~wjKm{*M!r+hH*0pY+wgLnxeIWThf8X1dSk0o$WptSPcMdWN^t5 z#)>?22i4)J{b=jg{S!$;_-v!P*T&=2>h0&1-^qI2iAF~qv| zDci*D_v*M5K3~6a^udGHc5X+&rS-=AxP0!F@9zDtmVozK-j#qLC;x(F&kX{{F@dcAq|FUF_=M;Zsg&d;8;$kG*(+9)|gE`0euD_3ef4?!E2rj~-?B;loyf zP6w?7L?3_r;uuR%Rk+Fh@4EB8-BkDBgH43j>x3|@MEtrToY6*duUkVES&W5R{ISov zJNT|G+*5ON!I2^UYk%3s?BT2v;{}8lEvol)LYiF!L!IKy)C|!?L1O&kOTk^^n!&+7 zw3TqQJkdEanU1*B?+V+SKABSG1*J`i$k)OjLjAD!^ zqTOwRx==cxQbid1(i*KHqbgGx+R};k6AaVVMw=w`2lXh|H2};isv3jt4*g0gUY1M5 zSieYg#`{5!>mVjrBs5Ntb2LT6& zlqh@zV&F<;M6UP{~JS0aMWojjI-q$@Dn1}uZ z7GfUdD2X*v^?bnYE3Lr}SK8VV|C37tN9oDph6%o%Vak?`*j zka$C85LY? z_eWI|4UnEI-kgH9xwUYin=KAwj(0MRB40OFTb^m&U>dnoT*bG6rWQqF31a36A^FV0 zOh@%QD1E~zr32wf;)+0ZZ=#G?||4M0k9Fj8vRiL(z$xbm+s2+UKH zQLHJ}rp_l#2Ccf0Z&ZBo7`F7VoT#X5xaTh#W=(;MHA;mm2N)Yq>wWNUe^UBLD}-(< zXu!icBaIco4R>$VB}zl#YNuutgw<3>%~(a5hZzIp<(|fDaxRhRZdBF2MZhjr13xeg zD8YnnXd`=nxGS3{#^U2ZaZHE*2^5kF3Lz z%y7#Z9t9tUB^Jk`>T^$kO@rYOn{5r7p<-bNO)y3&9Yp9kape*0F)WV)yg%;zE6j*s z2YnX+p~gf6Z-VY5JogDlhtTsDRE^2~Bh-X3uKS3s(3h?e%~WGP1?JYYf&HByVRktG z60V~;_p{<%3B-LLIx%;C+5UbX=k1R+U?OjP^7QG$hd=niix)vqx<4?VmQ;M#mpZ@b#~MwgelnS@_;X3BLYymSF$f#jx}t z1nG_6`Y+agf_}0a3E$p6`bV3a>(BYi@<)z*|N9#oN4`%O?PgFq%lz1}hfg;)o;;!L z8#gvLVeY$;^6vVvW1EP1{AB-Hd0hz}Kc?-YM`2OI-fveMv=X9kWs}R!ekBOB_6Zdc zy-!C7&s(=|b~nBq(3g(TeG0p9+>M04kMVE!b^0!5zY_58wzr?J|IM@SDMAm=12<=c z=We(E&)2#CXcNTY%}t*7zOm8Tmm4U{?)UA3w2TOtp}r^qJ$pR~9zI-Nf*WgivbX=_ zPdKF~Pk=S39p&$I(B|FES%D57JZp8r_jT0X*@gD%-dMvgFt?w7_jwYyc{`BcnVX5< z_HcB(mXpAPMTV4O4>Kj>BH#v0MVepS=w-+2g4)-J?zo}MYWxvOuic4T%A*Q}3A$)_ zd$1gnoTW+7h#?q^5J7cmvnVZjXlddK$em`f%Qz{1%nfEts)vtv#^J^j#BHD+< z?KEB=t`VnjCzHaVafQjakn_Io$wM0zeffUQxi{F&BZhvya&DF7 z*?3qcc|tlKR5({qHo8>`gP~8F*ur}87{7o~$!F*Rge3M%HHshRm19ViBCu8bjTdEW zzfBUew=I_7ZXsFAZR;AI3O**!=Iq$nVc_nAf;fo=m7Qu}h{DHX%i*Z73S(_(mSS*r z3o=c8g&CIEB>={D2_{@hG1oZO#3ADj43WTXDeFA1<3hEQ8p{T-b^$#Yn1K))OnQj6 zXv-d8it8mf;!pKA5`c3eJkQ;D(4j zQZ@_|%!`H%RVUhD@`s@W{P0x3K<9W&kO27T1$E&>^WZba!hDU62JKtNPT|a~5$D1@ zBEBcmURrSq>BwxcMG{~%%oRD06UFF1)`hbVb)m6H8CSXdqeX8K2^0WE01xi+oC|N~ za`flZun80NVYK^VD>sog!J`q6YRF`i?>FFw=frE`B+TO2t09U~dD?i94;*OFG+32T zJ1&?;uy>j?miw?OSa1BtQ;JL9T05~+p=X(<7#a6VnqXEnm-}}AY)e!&I)ovQtQL*U zpbH2?78mwRO)wni82JV>JH@~}th0KmAOeqg!%_^2B&bG>^`{jgabP_d;vwt+4^5*P zwLx5$!6Yj%U%iPGuL<|+L8XffXy>LLporDcV1WIyv0~dt zyz!c$U&iPWtu-VpHQv;u&<9oIS>u6kKHU>@cXJxUh(5QPwb z!|4PeYN{>tF`%f1m5Xn%g?o*^#^(xBYr?AWG!ATx=^BCqi!{==Y3Dccf^zNmGQ*$#1rd+-EH=7R+aHzxnLh z8ipBu))+hXuO{U~EVUrG(R(4pD9ssn=GeOEU!i`JGLiD+nX#&wE( zk>-BGS5MJj4+HAp3ImdX!7thAFfYdO?J;0pWu7bK*H3sKJG-&&6oP@K01y+`78 zPpt5RXg4i9I?TX3?)cgjytmftTVs60J@Ctad*T{CVsc$VxOBYIKA0V3M;$ylPm~o} zk6rB`dYby7{?!7)K{RvVxBO{kpXv~w;-VyrUASd9ESCFC4%4;1?ORhq71x;Fpf^kG zZ8L%c$D-X>#KMdtXA&o0^iZ+d{wp`|=X|Sj(R#G25rQ8LaklOd5;mfy!sISWRZHFj zj(&Gy4*^=4Wg=vbFVrEIWR7bkqlO zr~a~cSVCS2bp%yoAjMs!-6NhN#}LyrUhTUs13e+}8HuUvaW7R|69XST&mfzA{)R6e zF9Aj!RAK@{32YnikxT_436$3Jvo4#- z^A(S3gi=6rmPR|s^k8t5n0DE3k6UYM1R9IbdB}nznN8hzHcOIHLIA0j=x1JsmrZBX| z4?g&kj2#0gfc387M1lxAC=9>$Mz3ONI+e%jQ^w1nX35Jw2^j`P9c4l-L;j(O!Fmre zz5+$x_(5&prN@cZXrbE0_ckee2^Oyy5dulmXlE_WZD~4-$^Rvf%Fqawa>%Sjpq{9^ zPFb&l9T6ucSTmuhX9$?tfCn*8bv33fz)@6kgAaQg01QOf_>ISn%k@xT*k2Clr}Ah@ z<3KXgVIXf_2C4(8aG_(E(z1hr>N;bgM~$bgcY`%;PcNF-8(G5;op3jHWszKF#PHIC^?LEc*7(%_yl0-G z#UQ8EPxP%Klpxtt49o`s5xH@X0xu9S2%K1p)`dW$PPROBZIWC*&%r&5W@H#xmN1%VGODqX ziE=fI+LjSrZhLChrsEzeGw@SEbeR0M_+*qAkoW`E=hm0`dhqlS_!1TKJ4zw<+2qZW zdogeOC-;pM*@si*$#Tx=9PNJNi}zkU|Ky`T`!k&h7cW|(0PvI)9eZY2m+^*CF@W-uvP?{L-gSe{&f823X&>iZt%O|G(b-E9T!{IN=|BpcBO( zeX`WN^ohwX5M?e?Zq@z0zvP=@ELDDvm?*7_wpxO3n~xvE)ixh~b{m6t0A()o%`r%& zfQfb)UIl;LLd|n47CWVqW5<-dy3FjpBj5f#!v9+Hi!9gP{KA+;!d*P`>aiJ@)8dQy zDhw3PAqsGn6uCIuszhk^L?>p=Z*qV9@x{&3Uw)}bNe*7=ljFja- zrZ5nP9DuO>`Y?DuoiWiBX`+ply)u9H?1K+<%H+#0)7}y1;}s&=f0-GUH&3m7m2wGv zee-?#o9ZA_=Sr6`c+9B+ame#&i!bP{zb*_W(JLLwQ<~6*z|`M}b_&pTd=px}8v>S3 z)5><~J22=&HlB>n{yaT#N1jU4;ufS2CT8I86%gPjEvpJe4t%DTWVnZ%yu*N;kwdFv zm0;yaAe$mw`&`@k$mK{cdzMh4b}TzqA+~X1Enibp zP%-IkwG}=lx0Wk<&j$B&k-blL$kGeHhpdn=6Se3^&VimLdID#0TOy$ys)8|c@8 zhSuA%jN$A#m19OY)y>Y)OR`vNz`~YRcadBs%UNu%tOX0M zcF$=FcsQ-Eycz9?k4aB#yu)Zs>&Z64?T@VQqgZYe?nB|CI_pyORmU{|Ap)C?mpmOO z|6T1Xv+<{XgCs~>w8r|WK(iP&{aSSIop!#{81T^2IH~@JpJstI^0#<572l z`tdns7kjUL4l0942YA zI{0e0^9^ho>(_K$`f41~zJqdmH9$6xb>)L$NT4ukp#9n5;NrXA<~ zTKf%MQJGNCD1HTR6d{G7Gcf4!J2mhmX%^JZg9J@UC^eF+<6J3sP#V`Q^NVBF8%B0I zTjK^~REM5ZA4nz)4yr^_G7iklgb5bjdo+eiyPI!32)Ln{wC7pa2y1 zog#00Kr?{@@k&~P`W}cd@yaHe@}#%evZb2d#xMh&;&HHXwOunptl1t3XJHT3PXacD zsC|!|u{jB0;Gy?Uf#-+S>;kY2kg&Zn5$;a@1wINl2-pJq`Owz3Z#cux@fUVD;DW=V zMLtO0{k+B8k zNKcGxC`Cr;45b%!Ig@h0C;gP>6+rT}&V;Ml=nX>QD3TB}o%x2iW456GWtx4x*_-2T zW~*Uaue+ImO%Vf1$qop8JB05UucS)*m`yPF@na@aEAS>S2TO-OSiX34_`^v_ zk#%C9${IdBJL`*2N2>kE!3QOpkL#A{i5(y|qO^QS zIg*mYAFl&yV4B^yJSeeMDcN*3(*Xi?5!>o@{cMN*@o;3%rM2F95=+BAV*d!P&?z|4 zPE^k$8@ptC)#K@-9OE_2UNJA?^~pm%e|Re-Wx2#mj)xxU%=dD>`ySfxI-DG|CTAtD z)2iZxXWpB1U1i^&|NQy$MPoBrrPvRZd+mQ+f>!Bwf7juc7<>rFPbb*NQNFC@6JFo` zHWY5AX@u=-VxUr&v)@;y_9x`Om=H9|QYe1%$qVz@XD{GUi=dUixU%BN>ZkL(h@(Hw z8oYA~lD!<>2m|Q+k6yqH&KP7)xG)e$RzIERy@|og3s>kBZ7iDj+t)PlLK`oX@ghZj z+q{-LZ;WT`rrAB85kKowe>Dzj`%JmLcrig2X17eL2e@gf_EW3l|C}8uNj-~-e&@M* zXO=6^JoFb&UOajC%S%f0AzZF8FyW1rzAuw3YyS;2Wpu5cy!)CEAbg)_K)eSmy%v6Oi?S-BPel zs>oty?W~+AidC>aj|Dy){gerSYx-qZl-CVN+vYcX9&~h}D!hjvto@-b{E^S=SnbH2 zgLfoi_3yQDF!8<+^4PYiWYGa9eDx`0b#Rfr5O@6i*^dT$IgB-NOV;Tuv=J6X-pxS6 zId|aaf;Fd#v%b-JSJFk;X<*IhssW#hShZK?dPIt?c(TOr&CaK_L8S13x6`-%CE$Wv zxtf#S!P(<5)Q)ZZ!VD+Z@9H?hzdID{EyU?CvI<;}W`y4|auXTr#9}~8I9Z3HS)ma( z?u-X5waYOzk!qrfLn{0wHL=?-nFdTr5k~fQgQp1 zfiO!QF4A~p?~O<_;$~RPv-8yka`^qm53cd8A5qeP4EdN+O-M>mbq>z!n*HA*^Ib&k3#4l6JOK$X1|Mv<_|7$yJvAI(q|O<4`>Ss7ZK zxrrnWR*9jn5LY^TkkRuS1X0o{I_Nk>h%j&27-=dH^=_=$QJWgQroT5N}2 zGm7L`1RARg6pQ7KS(SUbTDPvMRXuJzJ0i~Yt8w>u8SM^>9nPf>TEGOq{+!Eq7^{yd2?b9XPjb< z)VaLPTs5mr99JCEi#rp$b9tH_UJDq%RviXG6eb*|u)aF&4ApMDEhJTL+ftid6T_}R zIm66Y}sq2doWeiHn5>NGZ<@$RTwkAR?*jy77F(s^Ga&`FA$yZMVfpujr>!{ z5_9-Qc4zY-VboYbi^CzGG@P`UF=E>)O_H>CUUlqtj zPl+@2io#RyZ47?zkN@f4LQ0)Hp7VRid``JP{Gq}#220GZg264e^l!jve&=^SfD`^7 zo;l$^PsE}K#`EtzZngT|-+l7AIONOk(*1)UeE#|G{w}(sp_ab&qtBq(E<=s^FaEoC zUcB?-(GNIT{i^NTW+?tItTqsN~%|LetFXxw>r*KG_j%sGX7{An?n^Xc+r{?@B2Lj2eM z=Y7(qWp}Tl3lM$IcawRj<6sh!51*{Cn(ZWvA!N~9PM)4 zfICvLYwfQV}$g=}o$L>$=Fmhk98QVZR2|qJ9hXSaLp#&kfUagv_sN z7+FwmMIsE=prQ3~Frlsy-lS!=NXm|&Nm6*Q2d*6>oci(ndNZ(?h56N&2Nu1&2;Bu9 zHETUb-|;t!yT`%NLKk^I{veti?9a71!M$7^iV}n%*`n>X&0#whaDyteg`+BW@~)cP z*rhk4=?>ipO7(#=>@?60lx({{7iMqN$9))d&SVy|0ugPdP}%@HRk`oCt7w%Ws(S>z zno6*l=$hxMJ+IG-n?=PG&+rnj0QOOwDLggj8FpQ-`=u(>#nazi)Iog6IMaLA^!beu zp5770dL48~l?SPGtg&WHZutP7A2BXFguL$pC%s{1PA$7J>{c-J))t|VV^_5d(VBf7 zUY?<#U7hvd4p8}G(;hev;(2LATul*tI8>o)iG_5DXJ!a=U1#V=;1M!HcT2wV5w8zI zz8Ka$uQ2)%Ds*o85fSGa23`R_ywC(dq3u*tI48&9m@|U~ng?K)X<(1z(9jc|8>Z&4 zwqDF69h)pMf!v0&-2+8H@W6<{#g+gStZEDTQNoG7j&3(Pl|wzdsPutY9hHI<4RJ*6 z3x5>U5yhdAiHolB$>38BEUt%5HxJwZPNgyssCnO#uw?_Kbx<#ZGd=1h$MvZ9^=aNc zY50wDun*W%Jv36?BYn;GYcYDdMw-kDqoHkyl|i@yRW>f3cIr80JOn#AZ#WigMrhTb zmdssUR69e|Nyt8Rt*f-DkM-2U89;<)RHxxgH;*NUbMS+P2%``qRD6u2i_~a}OMgDe zlBj%6h;w~Mn}SoQ0m-P|F`b=f13v-zC2VB88fq8E!$cY%;bohO+rHo`v(EwNn_uj} zqKPW)J{{;+H9(2<+)9)b80crNe^?)eU`LPUuql|?SEso@bTr-wU*E#{=2uuc98ED{ zTAJTbQU2cS=%_uP3c}SG>8FSjzn6kNLA~$Ry4bC&JBrd);%)_;su~k#^_Y}^e*;oQk~Mx z3ghL&PY}~HJ$MI>lGoCwUa2D}CG`Jwga>SWRni%I6Dtz=Xiznf?=D*(T~3mv8|Vee zsUw2ta&HeTpZp6tulD+xHKSVNl`!*V4N}&@W=i9AN4d5WCn~Ys<-}~*1=(zRVruTZ z7ubJwis(~s!DK(!ybe6QJ`+ADKJEk;=8w<*-#ME(f-(PMv1IciMT%*VZ%;{YM!V@+ z&Vnyy^FRE*f1XysTA`oU?4n1MD#^LuOn&Ji4ku%CswMR~hs$WU?J8Mk!58xZ3{o;t z7Pr%1xW6I>%Z=PR^ZlRvDHPDhzx~aRKmPW&^Q)i*8l6#4B&i_jgqG#W=TG#mcR)JV zlH@lbi6B0kcOu^KSTN9;?|;Hs@B1*|)Okv?U;qM7Ze((Hpp8#F;-&Gf%=_YA`*9!m zqsRX(G_g!5-xz}WvspOa)meWUUWvaAMeO)W-vbvnh(D@2ZW1@#Bp%F9?}>WDOO)&T z?|&mvywa!kk1Fa9$!WdGayC4_Oy-E|=4v)M;Pu`I%em;E0r!R9=XVzaVxhh6J`9xp z>&HO3X4?7Mye$>5)WK^sEPB+HO9iBk$Rl^imu37FF}Ry$JabL# z?;HEqe^!`(GVw*K|Lf*2{Wr}IfB%2_7t{Z7HwiuuOxE|tz4kou4GYHd_K@^COVEUObRXu0Vz6xJ!8Ux%p)6<=D=5lMl_{ zW5dnRmEIO=k~0iw_JV}!x1_QDxJHB1*jEQO9NJxb^1H*4795-5NRey+KzKCO*mI0B zIuS~4h`f;W(nabL`@L{-En1*_bP-So;ZXJvHJ6=yl`NCUiB_ zc-Q-q9S>GlqjxOo4=&Pw;*?s&N1_4YTyXFv`OEJXz*FTaVD=zWUU-5~nT(A~#~~uc+c@Hr>Ok&$pdJDO%9V%U zr5!3tlREU4@}f5*3C0L2zQ}z5s%uD@fLi1at`yLj*n0!_mXS%!9e+ieqR}CIdUsEP z2jJv-=#;q)#-Y)xXsvpB<%02S2^67ooXQ_L%76mMfJ^}E=v}|6r)V})0Z~C6j8Hk; zsocAv8pojvF3GM^M`IVb2Z*9=E;}!E@8AK7Wyy_tv1o@dj|V=z+-#P#SW%rd)dwSN zXSRNj(&~EZ#?nK{`qneF&6dx-fMjmV8&8b5 zKpGbPD8H85)AGfkoahO1PwlQ7gMlr|33XzwscjoPEi_PP(gf$)VQ_&sde<<~WfOkX z0spDH@VsYbvDJiwcBXa{O-EZqqII^aD$L4qv0n;rx$H~+G&UwDRaYJ*ZjFr_TnFmN z2Bq&dkbK>)?TMif;#amyRJYzppnPwFom#(xNU*c_Vbh#A8trzxs7|Rf5tQq>^K{D| ze~;)eN2kf_$hdsDYg%;8#}YF$>)MWap1%VO*98K zx#VuF^BOG8p>K&lGd_&>tJ5?c=e6gmjfrMrXiO?eE#c@5lU;hEd~bp6;jwqG|=92=*PVS6+3Nrn{ExK-6TzKCmOqF&!;^4T^KDGaI^&Oy`7LV+fQ)k`MMU~ z(_iR`fgWIKNy45zQ2NNDZCGOMYiR~`T$30sg@Ue@5ow#9~3*U^z z*m3=;Q>pWCTrumawbj)k*rhHv@3(4P#vF~DLgxC|jO27EA}tfyDUZ3htmz&5*`0l_ zp7YMNP1ujfr`uxOuV%aVUD!{Z?Jawm@ZQ&U2=L5an?_}zi0(Aa+gU(Cc?mnpb(E8JXD`B(9h|7FY{ zpK}_P9M0xbndER69^$$GwaAC! z%4F{y6dvNafB#Q@^4)6=d&WR+VX*kiyvE>WPMIV5IvD&0nD4iWH2>cEd+_4_>T)Vw zeC!|m;FFIQzPotr=*mJ94u=iyaRAKfj{+UQ)U`$^E?cVk*B^a$5Zc z@7>&|i|(Erk~X?xT6E4WI}>-hzxcFkqYmY!7S*|^Y%Y82-ZG8`8AU%GI?(&>IV<72r?)82mznd6nU+0Z5NcVli z%ICbsax!0U!a0`oCX$!GSqGFrky&5+XP*g!v>SLfIcEXNsM{mX0-EY zy!=^zd(JUqpc5UsolSn17=RfVoOc>?f^!|69d``B6DzM9(Ko)UL_;@8;-%S z%#*A4qWeY7i-Omm#TU%d^PUH6$Ie6dfi|=W8n!%@0UwZD_JyxRKCfA~`zf#-^d~4T zqaQ~e(ZO7gxYm?wjoX#SvVcvWWNcYN7%f_09RhTtNJPQXHrbV)uuq$a)Hc%X=t)8p zVKkeO>&ARr6#HsW@9-lhaErOw?khjUQR8HkldHWDd$jqvZAM%3lHIB z#@1A$K@pT`W|l~CqSF{}@(;sA{pF^R^ERl4UF$~Y*%vvv0NZsa+G5+5ZQqW_Mz%2J z16yt$z0INWoi%+2k-`F%jgBw5Fq17}p*gdInM3V3Dym0rWT)LI(^Wvpx~0i*B>x5Qc@D zM{$fG>v)%v>VTlJ8V)6|!4-TP@v+I6*M0@Q61VcRll*VO;Yv4hpL)2`SW=FmQfbKkga=>nSi z*pAw#(ML1v>4qwr5ta(f_p>GI@}SH603W|7g7tgvJC9I=FT<9j6@_o1OT9-{cVH^m zIGChOjetnKO{1y~bD$NV>KS~S+HzV$J3>s-Zqh>EQ8Fqh`clL77N}V=aD26+*K^#J zJ_*9qeMljDQ%t0^#UYG%Fck+3;J{zDq$v^>F$c7!KA73EA)u4f_L#zLt(_)I;pYT7 z+3hFf^O~#6kY)~Ts3PpnHbPM|WH!dvq98fWv{~6XgYI_ z9p^SGUFOi6ZeP3%_ zIdoS2p3>#_07!e)S%Vd^D4PE}rJgZL{#W_rbLd%qc{!1uP6}rflpAru>&ktV^oC`@ zV0ryF!5~pBNrHMgk)BQprxI1jB?dPNz2UfXGr3NuORjw}vxc`{ET_}2^tOfHaaAxs zz2@34y!U&|N7dg@%=rekOYI z`SYJAZnN6$laI12w{HFt1^WDjbinDPcB%_i#{-}~s1juACh~*=h6YkxrblA3WG(Hn{*5;`vK$lum80$kxu&S^yjXXO3q>; zi}ZR9$vY3)`%!oP+0T%8N4`Nk`6cE3m9S`X%NXSSfLCLnoAv}*di@du?fu~9Zu8&% zZ5@k$H3m%_W81XRBa-Y{YOoS(+h{qPy*v16&x*{C{H^@tgO4G4&gS`uuW2W5ywHfO z1~|b?`cUAs@)P9XAv+9fiyZyzzA{?xy_UG0XFcwnKMZm?@7J#58#uHu=pms*S?*0} z1K)mqC@2QWxdZ_d+F)!=Ya>2B`SQ@`InV2h&;6!8V1P|eETVR`x=Lx$jg(h6Bkq!q zP|!Cg{V>ij7`5M-FGZAbqaTPo3f@b5eK~#n=wm;7;;{X7+&(YQR^}XXC!7D||iG)6SP|N6HR`KqkNy zMIF&>pg6#$fk!(V*MzZL2O43r!F99F=$3Xvt)tjr_SSgOSZi0;>x3uU8c>|n086%V z%tZ%C-f$a>xNeRm_o&T-gCLDzEL<}fw-OCv#x1y`yzIcP4a6(!w-XJtaxDIMOIKz+ zW~@dF3$drGWgtY#v{{F28J#TwA!|?vs-Xm z?HU6odBy$^93srrV*c9pj9Wg++zz;-31;yOZ&Z2v> z?m}$tRGZCdyH!R``&cVB7Fxeb?WkVN`*UKy<;^|a&&{+^`b!ZEeP;m_Jx%bEj|_N6 zPi5<2q|Y`bqp-#8T%ewt!`OiYS=QwQB?dJdp;mqHp80UBnv!$u@iap|%-(2uZ6O;E zY-|+dvDbbhIs^HGPi}4l?>O{34ggHfoANN&X++6jQ`q?k-95npH*WHNqzY+PsG%uP zaj1;KojlObPMO-am_R;^f%1`8TVu5ly=GCHU{uIPjpk_}L3f%R`S9uVc{_95h#8XIDTedBVknstO#w`nTt+HfayUC?5IT%gcd&bCZO{HiJBfg?OeuQvOO`66wTP4`Pxhd1~JuW zCd%9uQ*Z2MQXT;!8XujzP~JP=8)UHwU|WTsrbSB*;G3v^<59q_bT@(7{5To#ba6}1 zL4$ix%aJ%J>5ka9fXnODRpPcNR-=n?_T|x;Y3`0kdSAsTK9?$fcI1C`^va_1D_+qQ zSOX0m3E@^WMd>)ALpgqSKBiZe?l%D{_Z9CpOiW?}MDJ{OwqxI8wuiPn?ANyR#tzh? zMv)_xZFzUqXiMX;NTM`+2qXs|+=#h#J60Q3F%gK@2c4)&Nh|?|HX!f9(??ev@ZrqI z{&7b%`3qiR4tHXFXGx*Nq`Y!3{yD|P^EPKYyU)_Yi4O*Me$f~bGEZP^RctOG-04LIp62x^9lE+ zUt{o`$O{HJB*%OPj=J}JN%;^4x7gCZ0jK$Q|DQj2_pg}$XyJs5cYVnTm)n@X|9xmQ z^ku{UfB#uB6CuRQzVps!pBXXuu&ZVOh^9{f-FKdDEGIr7%@Mh)8mB~{#-JK=4mBZbTKl&s2-zTH5xR>w%2CBs*T5>-I zi9-&(s+?|Ou!K~V{PQTg#Qm~t*)vC5xhy>VWrst1RC!0^@(r)cQ_=Zm^P3oa@c0Fo z=M(z5G`)*#OT6s6JTpi8Aq>u=`$qbUd>8|lIp>fM9;+9=J_b!0jQ7EN!?)Cg2t|)= zD%({{=H0qw8@z4s<+DhLYdfzmIO`m5Y_NPmbg8dW+qykLLQVRbu%r<9^mJsbFU0=9J2j>jpmn zcRc@PkL#ybeh@fLNVqBvnG-Ihx4x1_a02%EA+Hgz^g8 zfkl64wj72$%m{F{Ec_;x*9Kx{qvR`YhGXBu&Ax|-8p+xr{bynyJUW4PLmhk!v+?7| zN|m+SM7uzcQO&}lOL*MCzR!nEbLbE^$)-|RYSedi*%M8Z9P z9Y4BhT5>hK%kDIR8`K;lHN6la5gpM3V}4UV(gSPgcq=<%yYqcbvGiXe1)))mx{1tYLD2j;LzQztBzaX<4GT z<}frK`584g2V&ZZO!nGNIZefG*tIJN3`B8}=A=I{Mt3wL&{`g9Og(P_L$?V{&%(Ph zZ3w*AP(wU7>}*6AK#aD>)PQxw4Z&exP6$AcL3Plqm{&tjTaLu#>h)+&o;?fmY?D4dAYngsV z=w+%2vUA)qK`ZBhd3@_zZ#=iIp<$|}x700)vl1mw7WV)e<=|RE`R~Ju{XE@G?RTxi zqlFk_Ul~rNOP>AU*!P(jlz9*^1JQsJG(DBVgp}NP zE#?h8rekb*Z^^X}rU;{`c@T%HH&GGKX3`*6MB~>p8`5d1U5Mo<}Yz3^Q{poxG!5nubqHEKTprxRp1Fn~l1Fm+KbL#kz^AM@<{@>hr`` z(sSiD=HS^5m=4DI)n=M#7Tic{97Gor?vRwRm~1i4o-G(IOdCe#&z=IPZR=ufA=BRe|g%+z(QQ@pl9 z{$*lM2rY2KdYWZB*zo|1md?b!oOJ_3q3CC!P#?hwbG)Y~Fu!`X;YO-yh`yPtV2(7! z%)TTF_>s6DpzDK9fHqOsX zI#WK6OjDk>=eu>g4qRvr)Hup6ww#C`=y*&GF**uka*A8F?)hfM2-UxjnDZ{In08>f zA6T|*CUPI*5lIoYc8^u+Yl_cg9g8ur^^KUzlqW%oxfd+HgCAvp+dxA$*I1JSKh4IIyc&?5XCK>ds~OonaHm_rcKy^2cqg&nDJe z@bppkz+804^=GPnq3+GuecMdoMpXNcmB&R+Da(i4xxDjJ6rOqU5C7qJ|Kv};yW9u< zKJ=n)Fqf&b;__eIGMAtHp{eoF-Q{UTPHu@dz?7!!bpKK+L8Y93=aO$2VDOXgfBz>7 z2Dfi4PbH!A?#f?wei;MVba(ku!ZilE%N(uPOUa9QqiAD(@AsgIpmC)~P3OZeZzTWl zd_#HmfARhj{^_UJH!R3wQ z%gy8}8F^xp&Bu?QJ-fcSTqQq#{HL5}&lo6oy)3**^lLpK=f!<(rr@gwvfj*!=BK4c zYW#2^HU5-S>X+`jH<=rSo3k7`QsZapdO4lme9s&62hh&v5|UIoC0!MLwk$NWxW2U7 z2f{RSzSXDRzrhJt3rm}sw4G(1ulU?we4+ge(Xa~1(&I9vt6sUHW>1&7=Zaa(3pBnO z2I`a2Jtsc*d)TfqkWa$>mkS1{*%Kl8Fb40uF#k88`@*x9I_ZA=mTNg;ISK91Ip%v}DOLL7 z_!evd)_TDqJ65DXgFGwgUI+ZhSD*FpkVtb?$zU8VV1{tnEGSsezpa3!X|%XMt*`6BU_i}s;Y{r-MSbg{Jg3p7ZA+gS$X(^hA@_x{oX}Z zI#GB~1V5Iso-bjXur?uU)nZ-k2?H%e7gI`J%(VQ@wSp<1Td+odQ$QuTU-{A3)M5zU z#WAp=z-A$W!eskH;klca3%7JsInJz^`+~eH$}vUQ&``yMbafq0Y9UjP$Sy*HPHswU z64m5r0wMa{g7MVW)xg2>L#W(=b;j0gXWt!-kIm}jY1deJ_C-#}P@bc?2xS$N^IWud-@kbLYL4{keGIc?Q}bto=3~NVp!M zFCqu!W8*@EAcxt)Wpk-zJ>kw(5&FkC&zi8>#@=f|Qo{^nL0XU4pqjji0jWmpkw9WD zPn)b~pKMQ0=m2JF3XNz)XK5DN4eR-1t5;JWNhq9< z9AP=uv&%8oClDR!+dJ6SgMuN|M9!Ps0ia@TLE})cSR;wt+O|4PNAe^Y73(=r%f7=p zcr%8ffagb3w!HiXEq}IIg$b?dV`0qbIjjwBUr(^iN%oco_jC%TGfJH$}a>4zbh7(i_HK)^S%_w&y0%Z?I;eH(UF z<3_US!#=p4v*Bi91VA9|o0D|l9fGQP%Lsz5A8KeA7t|Q0awR#}>L=8_uW4rr_A2fQ zgjIrd=zL4hU-xU{?a`MA(z{c6^4|H@XcGz=jBN!@)A(|%M&mnAw(0{@d#vf(zC@VG z!!K(~6StoIO(zK(QR-#KgR1r>K}U7o1>4w=d~N7h%`mS!siCFV)B&h z4wUSM9&2c3rlM}uDJJ4N26rHz;^^Hf(o*{x(TZa^AHfDzXQYnf0a}{;cD`*ker7mU zCFCA7VuX4o8rrjqX)cZTCeUd;_#oRsid#T?mXh`X;cGE)DV^13_c}gI~ZQ#bb zfb<8^dPJycOg;^^69pVPTF@T3vz*RwXc37S_yYSeI6K)LJ4G6g)Xd1WhkTniGcp*Q zBDcH-(o>#^gW_g&4dRVL%iYrx);==dt-7vcuOoP!DG#wKXBs2WGa#cox8IfBri8uB zHY?3*%n2G=ty-5j2F62KR&%pIHmhSd`$CmXvv~fbXwUErY%v;BL~7(VM2|E>$AW#u zQFQJ`kH{BATFf_P%Q$35W{nJ0NgrXWWmT?Y=8S_!B87=cwNxYZGq1OYHWgGyX2OcY z{$`5CL4q3t3J(DXbyAQT9R9YY+BFKK<}$5-V36a~)-Z6Yss3`Rn}(jS)!aka43*p* zH^t1fj(`+I^Kbvbzk(C~*D@#k-}(O%)0#JsB=c2Eoa-rv>723n z&(EOw7b#-sHsIN_#~-N5b>EwQYC&p(=KBF4Ke*UxXn4G2Nw{*l1Lx9Sje&UvWuCe3 zI-v|K80gMB{jM>11mqJ3=9w@^Wj}yHmRCoPooVf{Po{1v|Hbc1uKll?UtH1XSDf%~ zBu;q4z2xR^Xms9=VV;^t!mkP5F)h^QdpH=}4^i{#t>XN@1OJ;F%Tn(ZHBXhZV}y7J zrw>HEOHzB`i0f@rFHffEd+~!MlPwxpf6C#r57hz4NuBe2BuYVGJ@a{n@kYd)U&f z#!dvlsqt4~AU@mVpvR4mmL)$|499l%?CL|k@7oxfZt$)}PL#E@!HmR%Ooqy*lX(iU zd>7!AyKdD+KKWxoK1i7IacWpJ$xOd!Qf%xC_-tWEdk80|kALm0Uze2Ez{?D*hw-gh zrgF;L?N%)@3_tJa;yGKH_}N6B@_EvMXjes6_MpP*E-E;U1H%F)JMKO`UbR+eYvh+p z+bV|BDC!1TL4X&>*UM#0Jb(4D>Kkh1cd%RhSq-G;z*B$vC}x|U*^Y4qaj{KEuUF6> zgAudL_bi!M-gP~A1f&Mx^ny)L1_nQke`ow;MohlIZo*HCZ8YQ7%?Ps%_7 zhg~znCHTeRhHB$FW5$j%$uzj72N2c;GrB3j0j7dr@J5K9+U?*!Yk|&lJybE-Sq<2p zWyWcQ+~UfYIL@4~pXdp$42{o~r{X|wSHx5wOX10ktFhi4SW@k_FlwVAZx$pp;-uhe zYfj}DPX{kI?6ILfLGH359Uz)m%X?n5Wdozq8#awwZ|ce)D!MB?pYcSev~GYnC$Gq= zFH&1rZvbAQH_gZ(pqhKaL~?A@7qB&MVkzCgb>Lk{TN%hLc)~W&{B#^pg}+$SEXJ?A ztLnWo1;r>I{D4#-K35R0Y-da9s{-xnm9c@7#G={a&W((;eM+nr0ifZuNTzg{&P{dj zLWH(TIUkUZWm)GeJ^^|#xLzYya4tDJl21yPZRcmdo)R%A^;twj>Oo7m#%;XL$D3}f zYovGko|N54eeiy5Pn|>LQA?@=Rga16`>+F1Y-)6S)nSa>TRjqsW>pNx6XaQBOAR7h z@|O=6xWWE~@+**^U~VXTa%Go^4m`9F!){PwIY1L5v^_g)fRQhRIPEd)K+(yAT)$ zGX(}{_~KCc(1+25+O}+gbo7LF0$ngey?-;Tqw6-&#+BbdIi|A?deOWzH)d}prZ-Q) z1t?rVDBPiFTlm{l*z7w)6kluM57@4ZAW92+IoGX1l|_w7z$DicjEVNxjTGmd55DyZ z#dQg_rgp$zdM0~08BgtR?G$`5aEsa=pL*(7lWi#h31`>(ei|+D*!{5U34R-=55%|L zO-#s097E#G)7H*(z`mR|6R$oUV9&MI^uo^^LCG*-9a53E9*L<4$}k@Fw}Z#dOIMqH z48(_Snxm$P6keMs)0o(D85y*@)MG-ztg6VIj?OnW+!{w^Dhg+Hd|^(}wv|? zWH=2I(N+k-qiEx(sza|XV>NqiEcHPZ6M}ID zdqTt8FwM3c*hp(hU~$7*Ur*U&0YPyPxo(FVyJ-%S4mV6KmwvIk%PX$KfE*31*s)tV zdIP7hO+mj2hy-}X5~Z3AEou$(N$L@)Xn7~7LL4PrP|@q+W}I0XZ0B7ub8*=iMcpHY zG)M=Xi80nbYG(#QnOoP|?NEplPUgpX12x}3mDe8SW|4ZX_}|hHai*_`({4QFMX`-d zYMw$C0o3_Qne&y=6TbFpC2CxpZ$jx#!)h1%UAmc^aOB$eLPL8@gj1Q?R&pk~nXHh2 zh_gTLH}sL1@@IR59q4S;2m3C-;bw6Lr`sGI7rBg;R4;O?+QjHT7L< z312wlR~&IgQ2eub%?V$8u1tzfF>g`#SK-N`$HM8BDSq~|_db1)1HN#`fAyzxbHj{jjLisx-BL)v8T_laJOat-Ty3OAl|lqOV}lMTpaOFKKkZ2mpG!! zu4DeozbyS~`7ZNx5?7kYu1dZigDWZW#OW?3&7`+D;J4lRjx+x^6X$&4gfBewYmRv1 zePO}{C;Uu(8{K`zNWO-J^PNpe5WINt_)B3fn)>5Tln$It)CwbF5yld!G8Jqz?;A-+*2HOy*lc2aV@8o#l)dt4ZfV;6GGd(4rDb z&70NU%MLwI>J#fzftup+3o_pl>~5@$-&SUVQ)PJa!Q;I>v;vZ7MUQ6TPy)`tac8_i* z_V68CM@t!8SRbi&TE2m!&HMYnVjzrQfzs|nkk8OSl7?}2DBcO{2oJKUV7^U+txQEw!-r z-O$WU%ll5An(#X2>O7<<^90*4Yt)*7z{u!CJk<<3t`DgQ`GATeHXel4+rfdnxmk~M z^+-h4vP?cXYFAre zUj5s)ts{F{K9K_youX0~2C74|VL~W-zU^}}nK5QP|IJ#$8)P3Zo4GW~|uCvQDorV#SP69&Zh6b~}0| z7o?HthV!0&Xr$$uCV=BjIRP446mP&^!vg!dh;i*rgs9uUH3DQ{eKF3XEy4leg29{7 z(-fY7V#D-H4S4#YD&o*uPsNAH+5#^3VE~;C{%;AxaH0vPIx0nZah$8>_B^W;Z=8j;{fpKFXP34zgXije;)R z>X35(_-x8}ThQuWKNN3&p;fX@VW*SWkh+OCcjkyLyxE1zthmYVZ4of$KlrzP`0j~{ zH#h&@`!AQQ;SvAG<}coI|0UAcKYYrv=9}ilr=R9u=H!c=hK95oJmgD`E-Jp?Tkucj zQqWg7MYp8if`LfmOm{3b_v^)A(T7&Tm4I(NE$+lmetf}4zM&Uw@i)Kt>aTvCGSj^O z=QqXWz2+M)RF}+4I&kq9A35)GT<7#vZ3_lU{}2Y(bl`$P()e{g!~IoDUeaZbv^2QA zfIGaDaBVJ>=|Z_)npr2@lG9f;GX@{&6kQrG~cbT8d%+-2sfk*P;`Q+5Y0APg$cE< zv2bz&Jm>sSsDz{@KT{RSvq)tn>#zO67mHm6B#U8AL4ffs_k6#-Lx-y4kK0zv$lLGu zqq%T3$sN8v#QKgRs|g%-TGP0`+SOw_m5w$aX&A%zZt5ngN&Q8wFqUJd@<)DYl`O|w zKmmCLX*JRnk-*iCNI6jm>_v*{wL54`J2sdQ-&BCe;Bp6g8PqG^5~h_{)o_lfYR8W< zq>~SF6KMnyJh*FT{H>0WM@#W_bjzx4N4=P&2Y|pYum8`hEP4KVfgx6pZgfcD3lbVV z=9paR+2XznUqk)pOd)X9+Xu+)fsQq_NK;ee0~};UkU6l@_pPV>acNGVl7==<4nF7d zHqWso%XL_f6wjLdZNElLR!Bq? zXdx{o?I7!$71Boq76)$ieDu)BFNLPwk0Xx@Jim50EI9@&`XvZw^Vef&^jEB8=z#}T z_x#cBNWbOT^6MG#FU4F1Qg}%9+T$qH@v2nI9jKhY^*ImclUW(hpy{wLy(0&IwdcP9 zv-G$B;@@GG{!jCl>$m=w)DCGb3gYch7Za~M6&@upg!wdI_(`>? zRwcCJKVvo(c<!;R? z{2l^IM+J4qJ6KbD{aX@<>{P7;l5O$PmlIjd=yNtvKEyW;cL%0Id zDC@<$M_l*9e8$gln%akwqu7X`v4-eeNZ+i^v{X5J>0@J|#t@lpmwePif-z$;cr!)U zfm|hSj7wQ+D~D=ZB9p}f&}`7OyIPm1{HhKmhj_bsBO&% zdj3+$xK6k!DuKeVM_Rs7|KZQ)#iXrv@}*yTuY6uMUpd}wK&iA&3{M8p=f0ZwK96jN z*@EoZ_-Nq0vaE#g?a8T$1DW<9F(gD9zlA}ZdQ6ht?{$nA&~P-dkoBd2T&QU~)F4e8 zQ*xjYd_y&az=2$j1#=Kn{9Oy9Ye=8do*HV9&kS`*h~;O>ia|r%YVZR*Tc5vQEv2#$ z_nViNu0T6imPTL>q6y_jf*WJUVN;pc?$o@vA-+dwAXtMi+(r;sqizq4Oq*HJSz;5g z0F979Ua7v$?i)rq`#N^Un;3%v)@IvcL{1$u(se0R%x9G#)?PI% zF=^6KXB?alNDFgL8Lq8iPK%LTDq;x}-mFY|xN+VGT(~^BdOm={#2D;36f(ih#1vz* z<0h!xK-cIgiExgU82JBGHo*+w*&_*TjzbvdA)wFAf)nlN2Rjj>u80xS)<@|r>a1ws#>@N7j7?2RKEt55J&5Vd+I5>&aTBkMc zte9ge%Y(5j%xGouxc8wMiK%~9l!%;gIYl2`({VhYT6y1<5r1|kseIleXn3vT&Ufsj zS01@$((c!rxCwI;XrY6+v&PdwP0$<=JrxlRJDeoHA7&$VyA)`i+lrI!5&rtT+pkM5 zywJ&a+^Hj^9Q=7P>+y>EBF?L(9qB~mpa|z+FkWxYMV`IRuG_|}I>J*8FW|VhvaL6} z)D%dgB{$*w6wY`gI8})^PUX$;X?N2RPaDWe*#z|yWBrI7$T?^Gz0aqQvNz^a*)Y3X zrkt1li#wSq#C#*i802-4Q;M;akQ}!kNPwdA=lx%bwJPqQ?r0KsaN!i*b}N0pOIPx@ zPd-;F?&?YMqOyPXnPMbJIi^f`ES`7G1%njxjWI~K?A*X0-$8qc!Ik}P;mPMCPEP9H zqUHU0ea2;Z_mQMlbogAKWI^O7pUae&Bst0bT5?s< zZor#jfF>$rp)&?qv$F7)B(6ZY$^86EXV|MTfJbW{hvt8K10L-o^O<zagrdS#LS( ztJ|$~`uBXxRlWExrZBq?vhJskKTdU)YBKX^buaXyvpV-9wTg0B6u$VdJ{dMpV29G&?OnS!q{TL|x>%qXW zoMSb-MZAYKp<_b{#$oN^Zrjv*z7LR$&PC5mt;yJz4n>lD1AF7s%pB8u@l{D*;zCi9 z&l@~qin|Sl&kUpruIQqP!G~xZGd$m9K5$yb3HZ*rK`}cfdy9wXmqD)OxA?uDsl&oS zh3ONC32kh&bD$m7yc3T0YCcdEF=`tTo38jNwnt}?mv8#xej8AW=>yiKd`6=`pL`?n z9sneTIZ{e~kZ;LcQQfT2sSi=eqUN#FE{N2!L`SaIk1m(FYF4P^L8O;>tFWgm{pnbb z{I{uciS%E!Co&DqCgEm(V8kRFBn3g`tEBLzCmOE1QlTerlnIb4M3C?~)gbAo)0 zcrE3AA)v(_ z&~uuZEd4-u!HU%kgKJPL#L}1zjDa}Y4GXD`s7QRb<%zCy2)hO)YP#rE7$*qXc-a%7 zxM1QKdN>VKYdZz@gwWu}Jc5Axpwdl$}R*t zdN__Dgl^rHq=-a_z3Iko({|N_!ksE`K>6GZP@Jy-&mCQCR#e&#psQ7;L<~37&Lhz> z(H%mwYYvQr!VGj#P9?LCG6d)Pc5qJPp3}zlo_ZgB<>B0T6uMF5q`Cou0{bvV{2^T<^8o}h zu?*j;pmvxeJJ5lkgvzB%<8sAI4oc7$$r- zP0m!*?K~ko-f~=a*r4BTF!L6sA17VrDV%0H**q1Tyg#9C9DNO&kk0zpEW%gEml3!q&djP5it=2vI!?<^VR-d;!%!$Lo{(!b?^l*8HKPPQB=;foU zRm@cr8xBYG|MgzomFXCLS$6*ase8XrOOialFEUS_>OA8bSDxOU-jTL<9VxfGgV7EJ zC0Xv>>g~w+(^8unSXg?&jbN&gRH*gfg=ODN53zakm^{XUb01Vz%PYu>C^ITnU~I7G z!Hn}vvuhhS*l$MQM;SdVSOqroFf_jYd?PaNIVbB@)$OiZx7xpRGUI>5FMbgj5x-yj z>y}5WS=GoUEbL8Op4cQwWL6vXBOinxw6~YdYP|nzok=A(@vYbYGX!`2_mO73=>my6oAbM|v%hdX=0SEeJDiZ9o3Vi%{+FJ^1*YG=_sVU%q` z5_XRHDbO>}=rY9H%P!Ft{GM;0&D3UlwBU#X%xNCnNpZVGSk!Z*Agz?y7Ab?cmY9>d zwMu~1?5sRDiAk&p4s_*M6Qe%kr@~!$@o9pRjj$Ja>CE zkLPZW_U?DH-LB-Y$Jo$k{Wx&*K{s;k;|X0seNR5VMt>igl4nqZ)z6P~99@KXVwY|* zmi@!icfzSwoKZ#=bsm?_>xz6N`&&5{>ivh>=X!Q}HB0^#virsUElRR^I>7yhaPk#T z>>4pJ4lw4jXG>yC_ro!4Iu1MVRaL&A{UqH`9ugyAPP zwr3Pa6&gg$iLHua?&`!_x&lki^d#yRm&w99^!#Mf>x+Pmo*ANQY*8!B9#x}$i~1$N(<#?OI8ALT7e1KVM9idMqhy#1V0b{XaV)kP zxXL`OsSvcZigQvRw6JlfWN5a4vl5-MK!X&~`X)KK4F|71S7zz}bGad5wj=3Br z90)Tri+U4ACq3i1GM0_8CPdgljUG==fg=qs7!g?s5?BmY6uE^@gfmTT4tfkG9qSM{ zO)0gRE$3#%pv}(QY;Nq40bR;MWkh{g%qxa00*V9|p%;8eeIkgA3UTZ^!wJ@btPI>v zNIi#n&Z!aFf6XTnhhV0u7*i&kN~M|q#2aH7%_*^^62v^fv9O}7!1=t7B;&t*@d}EOf^Gr;9+u!y`T5XHSAe7U^X z6pcqJNjd?j6fR5QYgvF6%E}<2X~NLi87D8~{1Iap)sU>8=!y-}XA$odZ(xU@KPz8Oi zW_4~ww!~8zJgkwZ%=T|SP60FHF^l;0ro+dE&=waTLJ{6L83T~?&`^&y=c|0#HMI+_ zov4cJWYO~prVRbJiQTY1pQC=sNzuWpBg2r|m7A>R^lcWxG{uS0jno2WWKGg3)f!VI zdvuZOQ$X8%$uQh{$VeWzdjp@oU94fQ*RWXl@SNa9Eq80(R)qb6!;MYm)k1=J1h2L! zq#X%`9;*ugJ;8D7J**=qIK!+LN4`7Sat0UIMX|fs796+@mw((XwLR7&{_#hg+l?ID zrFN?XkOcX;FtHfcvLQpVAED|*j=rLUDZ z<`k%Ls)-Jnn(tIKo&_92Kn$F}Es+ue8SO#$Zo5wDp8d?Hs)}&)>57F*)PQtDO5JP= zwhJVXirwoKsv+0wg<`>qK04Y5$EUnF|AOOEX@QdxAv)Z9vQC^sc(R_cB1&cs0de%6 zIzC_WJN$8T0v{uX#G@)|q2Z*_Ooae>2JgfUTs``y!`*AUle-44e&p`Wgv%ezqdR3L zPz+B~qw5pi_dfaP{&*tSc>b2o!y50b(urQ9{l0p3b5)ophE<8Y&Ny1v24LAl+b5(Q zt?z%tO<2Q;T;us$`zLk@gVBCpJ-fLooL2oj80??aH9f8CJo9*Bm(JAs2meyJxPSkn zkHVSC=zlnubU=4RKX?!c5NF6^ZqI)UgYguu{WG{myZzIEy1z=R z0xnPlglFk=Jc(=Mm8a^+J5TL{Iqiu;Cvkn9#`!};OBLUP%zPsw4~>s!0GI}3#reu> zRCWH=xWYmwK0kmb6K5RHxV}dj$X#>4BfRs@_3PjI>A>r9UbgJV)f=Oq^Hs$pmqQH3 z^SI;!Kltd2VW9J}74l*jXdZh=XHb<_oyI=i?<}4JGlb5FSrKK5ScWAG^_cm>H2KKC(*_LqtQXD1>mR+kR%tZB1eI&HC-_b>%U zSI`>gI*lfafVfnK?SKnrO*2}4WiX8|J8Hh1YBl&7z>b=r($~QwF>QZfduaycU48pL z9Oi7_WnW+I8@`~P{+|$0j?tK&@0pSa@1CAcd~}py=pFUKvJJQ@oO(i-2-H}5v7|6w z6goKYx-wasb`>nRa-3P`Oj}tV4t3U?Ue3_JvMk?giVa7NZ72emIo4bEc-I_q?1pOw zMivYY!)1GpLU%M3$zUc5Hgvx|$6)%>0(7sw>&?o3WIak$BDY!)Dnqd)H^w2Fkaix*69jshZpjy;*YQ z>5}iFWJ=!ltlDL1tTrSrdX{yYC3U1i#H8tJgg>$gD3k#;nV$}MWkV*ZesjpK+81*{ zwA2mE^+M+zh#kkBq-lnod zSIb8bh=m2|lv5|-vn7-2s8Rz(;^PY#AR9F!62TnZV-cNsI7jmpm~;7!zxw;kx%_tA z?{w{(tOMc$w>nrJ9v;?FyeQ^53?%0+=cUOu)+EY{HyveCKn{y}*O5d*?dMi)so%GF zn%>JRu@P^gvR4_i{HU*CLoLPL!#{a!tzln<6OHj7&8Puvg`(#S65?`are|wBUVMl+ zs0nZIAw?b=?4zrvnvIpg<0kOo9bfVleePKv_}I7K*DF@7?U2g1kg>envlPTM*1@hj zY#l#QHXJ+=%&MS*EGpqG`QWaRbc2f>)F&#e{V*v>!FH}F&^l>!-sF-q4p^fGLabOb z*#f$>Nn5(icgGfVj@${~!FH$tA(2M86+$#%Nd9J+E(VR$Ac^%!EI1%2;tLkc(->Jt z9W{s!3d`1`teKV8l1RoP6?&!g#8NG>2;lXm$`T$8Ma9&9H#Siw|7;@aE-Joxdp90*OV4i5#Yi51~45m(7i1 z^&|@jv88t=Vl>X7dP8=Z72v%Chn9RF*zH`OqDyCenY+ff%cFFW<@1adzt(!rqn>3j z9eHMXj?ZguPb?TF%t>a6Xn9V7WKD>-A7T%=cfq8rTpU6Wp^jX?TJ*iy*wi@kNF&iK z@h z>O!`|m`aN$l~7*0;H!X2q?uWz(>ThK5~5^w&;;#%%eiwq&{ejuWkPgZ$h4i8n?24AqN$a-&&O6uU&`?m0Pw_T_|B1#{! zN3zG^l-2Fb5npAfg>LD`!A(p;3tbuJ-~tv_c=EphAWSbgQkPG!&sdez7ssicHQQBQ zu~w2Ig1DAvlcj8Zs<*V4ea8cx)m|qx^$>GIN=ZmNag>BZUpFSjwtFTHv!c-b3;l97 zYik6@>y#@%Yu2tMrgBJgDIn$3?Q8F`cDhO`rlJUK2pQgkQauE)r%f^ z>8eHI-psazH~IF+ZW*LDWME6dmtYqL_QiZrQ6R?mcE_ z_16x|i-G54QRBU5!PTwRtA7Di~#1fZG`)HVH0C zd1iG%&E4Iatx<{_LSla*)jUzkYlaw}ipaP|Yk%d`XScXv0XV%UX`5C_oU)H82%Tg* zJo0e}qsyosx<1J?uF=}Z;MQk1Z^}QHFbo(-X8EEp_zLh3fAJ6go4`MO^LOqdq5XJV zkF~EF^Sghd^X|i4h65>(ciy;nZ(Nb3Ra5(b;5gj27^KXEi0fT+cLO1w0pohCebt!X z{8OFze~7^i?%f<$WIYLkkk}W*;PA$X-MtU<%4X(ohWj3_&3|AzxN0>NInv~8UYQ@y z?|$tX=8iN!q#2=Q_{QCT_u8CQT;tlUXJmcPIZAtf$Sx$UPa5UTQOX;o$oAj=@Bd$| zO43avI->O<%YzgF`=(ga^~3oO=cByYoc`h$;p-X7ZZDaqX!kKNw?}#N6a%eFk{tP2 z75tLw|$m=9|Q9! z?kU^tYJBH<*xVP^v55wXZHIw!yboUr14g4X_8IMd{BheZ9Gi##V$(*^p( zlb)W?LEuMy(l|%AqR_Gn$S8Fw1M%J;H>?m`Vl?(Rjy}vxH zsRzq)$sX;wg*TcT<87aLXnX=bpu@Q%!aCV;<+io zYb=}_8U&O{ON%f0Yr!A1*8^D$s(*!)hTw9<$Qsvo_?da-4bRd<7f6k z7z-liQU7J%HdL}YMa`DW+*-u`q+gZYGCMwI>%YnTCOd}ai4PKQ4O^XR!w%}5_DksU zhHQ6P)1;|m#aLRVCDRr*OOYmU{UP7IcZ*fFIl*LH85W(=a_CzDcI8gX)dZ{fj^qgz z&wN>yN;=l6aXkLXf+R^^k{sjXjh1|d^VG6Eb3s$JrnNH0A$}aAl^Jm1J-p)JesI}$ zc(R_=7gQ;mSQ-GPgp!c6ybL*1t?wmf1Emw^F&Xri3EFXmbPd8rCY_7j>Tbh~P@gy$ z1vd3`Ny_4sg|SK&;$)jzI=K4qer|lU42Jw!NOz(b-HJB4!p2jw$~ZDkgoTpAm$Hyc zP~>(wV7U=uPOK!cRHELQj#PTeF>SWyxX)Pzv{mlIDJD$)c(t3Clel7`_p?XZSVbC% zxUh6>vbImPAnrKZ*6ZTfotQ0cA=QCZag55&IZ}Y{`Nu3oCL0zP}Z#+-~q@Ia)6zWUE+!K^jODGJn(8i$AHI_yT=H_nO9kFh7 z?Y8WvGJZkZuOjF*6$B{ka|5?15{IV}9Mkm}4a+fhvA%I?*mkyHx!8g}oQeX>!b&WM ze=p-DKyS)Uhh>|jWPHT8f6^73h6Ql0>#UJb#U{tj;jW#BKw={|KdR;u;O@Y4UqHQE zlOFz>kn9ldIZ+sISsa=4Fxq9bu9YIErysCu;s!TEu=a!T_e1e18i(&c&zU~u&YG-v zM`DaQV(`HSamR`WW|BBmEz3wCh=J&pm_$OML2JGrg^ z14my(Ys{k_0l_XZ^kot=pl--eA9vt)T6Ffn^kog|aFfu?(m8-drJYSeM$m=#-YeVF zUzXMf3iuFRe21>`vZB90JQfN>dZuSoRjFv#foO7Z=q~Pc-)HI}thY)$V}R_D+GUvF zl}Cb~oO#zG8V%68!Dd`&bmrkpX?p87&ia1VGt=A5>456WrGtgkjj@3affmq2CzZ6=^`s_RBs}#QRWMvCc;gM5`)zMl?Gc|IzdU37YEqCI z;7l`GT%tpi;h7$u9^9~@tA-_Id7P&(Jd=lH-a9Bkem!=W z%V)*$obEdQr@Qen)Fx~1M7NDTBm??i|!Fd25-k1MMs+56Y(J-sb3Je+_Hnb#3 z*(v!}uP)RM62_nKxok)va>|k?x+V2Ry;wI*yHgGwp96(pW0OdI?4!yN-sV39R zqOzxLW(OJD%Bqmt3bs*@JH+iK^RbD>;xeIK7Dy07@_dS3JTYwn)eutz)y+}=BGf?= zKn-APCuBE?jq}-Rm#tDvVhxb?MZw)`<~xcTHNexnIS9Ubm`MFG;P9b@@80egZf-?$`Zf8S}q>*aI zDR*wJQy4t??S?$tK}8ALJ@d}xi|XoxVKbBLMl(Aughr2vCDm-TVpNIUIO9&IHGnnN zj{4_UK_EnO%sc9Wkr(uN@nDrsOv%BII8xN`A;K=tqsjkhEYGn2tG zFcHQ0iTN&uZYv=LTG=RT>A*wJ(;+rzPxm8N)^BsvyZ5{ z)D-=u&)o@#D(gV3W!o&yT`d{7zVow2NH@gX9gN%b7bdg7^gG}R@F7|Iju zUjP*OY@t(0@|Amgn0b(F<0W_uA$wVn3=lNi-*?Z2A*J z&&@eLJ3jJ!1ml$V7hJ7=aSU01#BNL$L!4NOIY3RVV?}#1n+3^QBAQ@P2IYmk?q1W3 zqdAY8|05+R_d&fIb(i|q?Ev9E4sj&|XQG?z0~@UwaNs9Ea2wq(83V(NGxu?vi-FEp ze9;&vg$Ex5p#nP1{olTQ|9(VAs#Yg!UN#blnZg$W56))7HTQq}Hs_&`j%LBH0)t|> zed8OB)#1!+&p)&o_3~^698?1DJkEq)J_*LtG9P@P+cbXq(;xnDKK~rh{PUwnS}hJe zM3OW0vT-=X;9K7k1~-9-0X6h2opCC&eE}NxLwYsw1D? znPOWYD=0YbkjVAypxS9)jIPjeIHt5Gj8a#KF}TjvGVRn;FDM>#4bF&oqz6Vdnl9yC zTh59}6>;hfn-?59^pDdWwoMM@^|`Uu z_3)l#AaXgRCgW8??$z#j*mbY_Ie_qz=Bgl51H`gymvTRc64i_K`q*Cyv_XugLO{5i z#6sQUk)OMiFOUiAB`&J8Iz)F}>QoHh5zY!L-d6!WCK5mRkyXBmgwN7wLdl2a2G}Kb zXbrEdA{_9>U=YlA2UW^dfbJUDr6cwvuqZpL7p~h+Kc;?_7<>h0!oT?+-(e>FJ8>pF z`A;;<{ri9jf8)lRKMD-XK$33*FT6lV*4(%T>|-#ddPNuvGu~%+X@1DgZr#2i`*GH9 zMCpa8(;4q^-TKX&@!W(LmhfK0hcn(U0|OyL^}EWCa+m+F|DXT)%@YIjdicBbcSbMa z%lWkf^LJTYk9b-qEX?mS!aZUpzz43wqIT-^WWF*$3Kf2FSeqF){H_cE61wUGutnt}hvMmx{HbGV^3tS_#MOWM)hj?hjx#e{{MM zRh%Y?251bF0G+{jMd>#_9-N@$+>qu-gL*K(NQFJ@5S>QBsac5Jc4;z*0rcT*V%bFf z<-c9lTVnN!xz#H|I#Eaa7oci}k}tsW9#ee*sXQ%85E$9;!@b&13$*VxR+)XfFQkX_ zIT-@@iTZ`&c@EDOSwO66eu!mM2jh!YsD11R2u*WOT8>BRSTbl|G_Zol*daab)fO;! zeT7gcts|zf8Ls#~5+i<_SBAtmLp|=%9@Q&Dz$9f%E3vUj8r5ifHSfED;n{KFuo`!- zCgqrlM%d>)SBJyD^Y?!6%+dCD&FhoV_Aig4?Szh884yPwGQw+AuPzh&7${YluM)-? z=JCeJ%iL>y(R>Vq$NL8lSWAEP@LT`l&o5Aue&wJheQ(FNnNQD9ZfFayc8GYE^q{F2 zpgQy1{rxoBXPdtQvZ=rR-v0)N{XLV3n>L?zMyp==%$dv5;-i-ZUI|ksL6!cc!AqzX z#=QCe+oqmX;c~oB@9$$er>KmK?F0@hC)kdZN=`OQcG5#gm?MwTl zmzno|7|`#qx=Nj6l{7Mj$m3mQ!b4h}$lvp9Uqa$zk|e)3zdG7pp~?ySX1gb7xx%D} zr1GU&%a;V5TErwN4}?uui_4U=yV}%usReFmXS_PyU-}!rcxG?=y$NUcJ@d`6w~ejp zC!ereTCBxYvs7nvx_9rBPp+JyJ4=1kz}XpgJp6bxU%g7N%U)Q`4ZbW;6?n-QuyWyI zH!8j;8cELaaxpM=lDDaO;(V#bt4!-0#J>U~&tLxMDI?GCkKD2Nkto0xx*B}MK41HoY}r$ zjeo)MUm*G~H@}lyAm)pD@fY=Yx&FQaU*|Xfhi`Ec z#*;leIeEQa8eIL%-!!XfB2UmBZe{>pd$&NKPM?@#!|@0)`ez>!!X z6EBTZ6c#TvangSoGk=l#Ux9Z1*Z$Gpqn&@(EW*rD9q~GQGX}b9_zKYEx4!ppL6bY? z^>b-*l>|X3Ecgo03o*kI6 zn3$7^c|I{OCg$rC^VcWle{*8KF)K5IM=+%t1X&&(XsGc$+u%*-J@Ic8`Q-yuCS zb4bt39MUs0hxE+MAw9FhUY)f(hxE+MAw926++UgG@0CgZUYX?Yl}Y|yndI*kbC|zZ zCi#11lD}6b`FmxOzgH&tdu4+EVq*R`CgxWs`FnMezgH*udv%h(S10*Jzgv_1-J0a@)+B$oCi%NHX)pfz z#Qm*F{@$A8@2yGx-kRj^tx5jgn&j`TN&eoN%%UhHB@b-jWZ%^p;_Jm$^7Gy#KkrTQ^WG#s?@jXa-XuTo zP4e^JBtP#xhX3LB@E=Uv?@Y$4cg(@suH?=n|92+&zcb1Iok{-hO!9wclK(rC{NI`6 z|IQ@;cP9CVYdaW!-FYm3hxxxVX%Fs9+Jifj_TbK>J-9Pz4>+@6oQ>By>*t2ch~w!< zoS%YDE8V%o83>u1A+k{dtK%di$aU1qM*4lT?DpW&7TUOBwglDg#lOS;E5JSfK#X1??CC3X9=>z80h^yVdlEIeC`p$tCfm zkh@y;Bp(O%RHnT$9jextY7x9f_tEb0>eq!@x^wShEPC?{Ch5NdrSco6_bHY1@?>3P z@+{{EguS5QD?pQ1|BrtWXp*X?e*LeWA?~@NWG{ENwYqpXRiQGguU1LNa;x8HjpwbF z#Picz4YuViUlw1AD~&4pHvE{5@6?ZMu+f>ZvR7;k?+CeBd6Eh?)IwkRg|ApUA8Mpk z&0XQK4ry7Y^c`|Xr+0WbgBhJ-T%pU&kyqQP(~@)I&`*Brpo`}Ne7!3-Q+RI;FA1sJ4EId#St4i4$MEZNkyj zj3+oon~HvnqVXBR6-p;sVZ0QKeCEOK4V;}F(I{pZA`7?ytEYC zC2o`uB{$TVA#s)N+~NUMHAwl4_08DamS(;i&k;w4p|!D0XbXHLuf9vlL$G zPwD|S2cVLR5S!}w9_`N5Ghb&mk86f395|d8PPnT6YuB(hfoK8aF<>0gh%Vu-tq_I9 zkr4cpSKUmh8Rkekg;Oqd4_BF$$$}f&^j$56nxk-(OJ(NGP&%>XAQUIsagtnB&FeW7 z$32waT;8$662ywCZe)TQxVUhL72*O3XnafYKiz zN6|$@&gTxSjnl1|GaYs&Wh= z{IY6s>~$@~IpCw+p>pO=G0ABcqnB~9p^-KGcJ|or18FVyXfmrQqO98aXjDDU%roKv zbnF~J68fRx(f6^;P!*%Rg)`~+Dp;cBi?!#g^h+WtxWzBs#fZli%I|2o+L)I1L*$I0 zx)~`eyDbl?s8uFRQLrX3C%!QY@+%nBJb}+!r0uHa(s`4#nHptc=hF(usRu)-Mlsm% z-X?XpOKpsXgrZ9bq_~GWFNJms3d&OOV&xAHeN?dCaB_IF;r;^o3!XxFVVneG_>w$y z+ZB6imUG{x6_K1(Jr7|aYwq#paEL^Yc-dVsEPTO*cnV33PK80wT$p1W<-c@3+LNRu zve|wy#F7o}#9bYqESHP204?b@dL)12qOmQ(e8m8V;WTLhWFfdkI{P-;piU1 zj0J~bD3KUdo~$iaqE!UiX9%`4n`65xIE`d>a29Itos{xf^wct!tH?s1KKhrZyMy8G zwb3@JqxsBz;%{`vdbouRHNf4dJQnj9LTZhl(L8vcD9>8Ca^N1%*D~j?p&oqd)aZDB z^dzMx84pr8$J4|;uAx77755niw?BOr1`i(W-&m!9ho{;d;t?X&3;6i8xSQgg>+f8T zHv8}BQ|GyVb}Ra0J9ju|eczY#UmSxmhL2y1x+i16S>BxI{@JbQ?*N0{d2Hcy^)b$I z5B%AAZO&+vYUUq)Ez`|qVQ>8RwS$5a-qbwoeN|M*NQr~<+6j>jdoF_g{4RH(8Ph4n z+n?^AEFN+A__Z6{Kd+O=;~w~6Mh`_#yI<_z<`w<#zfaBnjpYiX+XQriJ{JZmg78;} zhYxRlF7?S6be-I_3xC8w+h@K!2IdjJb1+aU`}Q(L8cfcr8)zbT!R_0zUPaUHJ?3## zsn2Ky@!q%59t&<;2oB-3XOB5zaP#KQ9o=c9$WR4UwR_Zs>UFT=G=hpr-?L7#w4|c~R1W*P%o4aHZmpGQJ?a6{nuwx}fXn`=whmS_rqj zajTMOu_loXK5O2x*Dr={P(8-PJJHPJohLEQXM}3}%B->mOE_O&p}G*iqn8(+N~uQ2 z7{DLtjXrX@YBzLZSW66z;=^Z2DEJwE78k}zm=#7;vPwO~7%47Jh~ZXpk{~HaVxBcD z7ry#+j4I&H2_rl*)HwWBxSlYw3HDZ;`H3m!MaG5N^!*GO z;VleJ>I!m%I~RFjO7NGE?h$wk#Dh6gVIM(pHk^?qUW6-`6oZ!y>q@$$b|$fjIZ3(3 z*Dsvn0es~i)1LGT_5sj2-RZm zNwmS`1Y3vKZKk+<(j$KfI`oRkz!G*&aNX3bpGm;W=9!0ine{HsQ#YF@ z{rm*FHH20jq)u!sA;mJ_U7?^yA5^p=54hy@V!0)RO4s_%V;f*LCy(e7N?f|N-kEF+ zaiA}xOFTARZ<3AzCG+#nnuXPuLCmlZE5eH)M4PM$=b>TAo5)Sonq@QNjv&gcWn8(Y zt`KjlS{RPX=k}%(3;yMS#}qzWHu8#5nFpva zT;WD3aChRIo2>e5tJ>=W+P*7}{Z?tY8GGTsGV7wlybtzfMAmYAYLf;H1g7 zA)B*}(u4kl-li^D7hQ|b3`;VzNQyQ!+f|!v*0ZE<&2g8tW_x6ll8C6aOP(Z|wQ1h< z#X6u)sfI%ARea0b6&aVQac3LI;tpeseX1KLo7uKofGybLv#3*~$lEP#Pk+qUyGqnD zoEREF#b`PjqGHw`_j%qEQ)qiYft-qg+!i&ZwBBshIrFXPN(Q)6Y8#@4*|OO(A+_?g z?uh_AMd8Xk_4zhXD_7|`T6n&gZ3BC3GHR-Gi>l!6Gcnr71>z>kZ@uwk;<(y?q0-IM z3Q5%wEytExKrFzLdKXoPGTSbTa^Yw5YH6v6w2gzeZPT0H(}OGCSYjx^(o^M77ZsnP zLsKm`93;3{!iMA58*0F@=h`*UEmA*QR>)8)aOusOvKQHHN-(J>dT*1C8w67oi#6>U zs@YIWw*8W8Xmvz;ns2B@_*HP?=$Dpqr)ZKSr2tHFVuMhoSA)0Vw#xNpoo;4)-5}nm z7(!sZ?p{+Yz(ke(A7PCe$TRvwy({mdd8fms2`+j8qYuwy-UNif{rloMUN)Ft`Tp0Lcbdgh*VFfI-@eBc@SLtrmO14h5I?H# zbF&+H!2ZS!`N3(>-?;I~z1z3rqns(^JMYL>IJX`1?!<&!pDFemH{euHxy}6a{(Y6o zc(b{}!rySZ!jmwFH`&3{h7%a%UwYE|1kA%DJ7IABdUWCB_+TDSOc*iX4y;pdGe7+( z+)H+LvpIf8u03$)KQKQ&Qw^uD-@!bpI*lFo!dNv*dGVpW#^+6gCazpI-?)npXMP+$ zy4)mZA;%a76~CFVoV%}O=sS^pl(j^j8Xo)o_aEwJGhrOn2M-iRaQgm@8>gqCoXqXh z+jtT@d?+p*;QqfK&UJ(NZD_6-#HmVEEtLA;0df^6|0#Foshkzl(=fPyKVq@92>M{Jo?hAQ zWQE-(sCsF}bc9&W%D?ispX=x&wS#J6$&Nmxif@!{L5z-{36J98lKT*sMoL_PjJ_J8 zZZU7d!$eY0Rqh+|(FcF>+wrKhWjEFZtT6RbfG)iCYVjc^3Q@+H>zO0;6BTwaVlvuP zCe#*hCBc_3;kWZm*_vaoURNKOs_ADq>a{OL`V&%E{LbW+9Z^_d%^;7AO!ytAtg#qq3?_Z{SK2ETvhhcXS_#{3{7qWCKNz&U@M-i%pu%duNKE*Ec817Jih#DK`mrqNLgfiz$k#f~8llC8|ZDN**Yw zI(;2J_0UrI5>*gol&C%nzU&~LG9C=sZsh6*Snz zypAr|#xT>EhLCLx7T!tL1n){;t(fIkWNO3oH3^D2atGUqE?H(1N=_1ewLqcQN8i)( zose<0Ny0>7JEG|fkpaD40Cc9G&QP;p);DSFjItpm%Rf{3s%0c_@L@Iu_8J8cFG7oo zXKqTm&H}&4PF;z=if85!qas9cd|1|+)=8||D6TpFB(f5%@rg_t8Fwl9yf7huJR_*A zqZ!Ww^rPm$r*gA{7;5bItx*k$)RCX@rs0IBV@|QOWEX##DzX)&m21tZI8(0EBu|`m zm@~|?gxsS>syTEDB(>DGLip=|hHM;owge3K<~*zZeg~ zaOsf6YrdsvdPG@P`3h>Ax6xeWLCuQ6oGnj+KeLVQeFK%+j=I>JVAE$!bZJ6}HqYfV zEnL2g_Qg7=P2M8&w20{%)>R3sVlk|+3hS(lpABlXg;LRM+Cj~Of8}Cyd6Jq3=ER$B zh|vx)m_=vSUL|7ktxqKZG~V;~nfJ#$xCJwk#@aiyjG>MtVRWp*!JQ8dFDd|Jo$Zm^ ztUKfD8B>COx%F+{#h40W@`gWCVDzBKrah%q`;Dwqgn~z&X(F39{T4#nyzP5BPP^#) zJ~JDeqi&MIQWQ`*V|3F~Oq<}oAzaq@lupY^>n^Ri*pDc@CFBSl`*i`59M*J-}4Nivwzq+j)!-1}vowl$F?jcx0q!5;sr zHKu12NXRX%CF*RuLP8wPwykZoP26Tt(+MP9P7KK1ZQIt=yAl+;PB9dnUz)a}rStis zpChRCCi9gS?X<*M>e8=dp3OX@Jn}x+r+z`13R+*-mJqGa*KI}bLrj}FvzfkFZ7c6P zn$J3fG%LRfF}j#SyI7e_a}>uZ6C8-Sf#O*GiV0?^R5bQXbq9hhtCjm01&# zDH5$N^OSggw4gSC#qoj}5ZXsF;x}LyLgIsdpO7^bsqo#ROKj428mgnFEpq8HNgEAH zF=tY{glPusA!l%3t}Me2+*iF*1>TyBkX;CoF{Q4MMf9l4%U-VHtdgTAl-Vz3g)^Er(+Kw@e zW?^vuqt{*=&HH};;8*C(KAf5B$Hkxi(+_|5+Vy{eYEb_;sOI|ST>I!Q|Ho-h4Am?P zA;%a#{M{_XIL7qh@5U5%=9rS~KS}$~0MSvox%s)IxN*gm%!d%AhWqX}9x0q;pkoYD zHRB+GuzX6Q`SHiEz1DSF4R#oFSj&XO@)j_LH=HUv^|!;~aST8J39w|KV+>yggZm%d z|LB7c9z58kv|I1Avy*PLAJuV1mfS`RM%j5ZN1wXsm#mV3T}Xw|1;E`ePETJv1|$R^ zDLw}S0}gV%G&o$9J`eHQv)CF!>38PQ!5hnujxGf& zSVo5V1`BX?gBSWhx+3|qrZX}Hz2(glO&xYohizx@WrI)URWUhY@YuwsH7)b{`qjbT z%A7D4&f*bM$&&6??+6_s6SSo{Led(NA21IArk#lJ7YM1uumZ&(CN!{6txF&Vib2dVF}ECis+%%fwBi23Kg4 znh=|8v>rz5$K(bSb5xd>B4u+)RXD8h9CFZuX9JxbDND7fHNyWS#F`)i6R#c2*%5a5 zAfzdv;j(L#>n*0XrLHKQUEegKZ+u@o6&E9v9*bDMB|vnvFGPBRu(fSlp3l`GIjWOJMnP zIaJRq-ic8_64VvbJ7N^f5(5Psg_8T)Bqu#~s(FsmX!tQeM<5B#1K zxFIs)l!kz@JA8s8$N2P;9yRl3*{6LBZwbfag1Ba$>KmxYM`cAhanCKmO4d<`)}@i` z3VIN|6VZ4}#sqUyp`%W_1p8L&8|6cOv1ycNnS3ItlYY=O5$$Db1LYMf6iF6Vp~!`i zTm)orkDxL%41kYt!{aiM*^F%W(&@t9#mLSR)Vv75lkRu z5+Z599E;Tv!&4(qDOiDJEcuP<#=;SIcEci^wd`A>fX_mQ3&U}I!d2meHCuT? zEXpv&vq^c+dUBcDRQ7^(pKKW1XG^#@6&HfN5d$xGc$wDlVGW!vyt0Y*Q625&b8uOY zKDHhv%N+5cmx>ASda?8-n4>*wv|w@cY3aYzV=U(4$l^1@KOge8h0&qxHpdxp8oyzQ zuTPho)ls(fHglUkTVc<1)oPRBQ=4m?_i#_-l?86FZ>-QEE&(wp6%ti;b!~`8E(%X& z$*9pvxE9t3Ed?Ibo(9drX2L7Ck!LopT);yIj@WGbz&d>|StVTDyghxYEZ=z#hi>GsTlr!~e zUjxJGz32Y>AHi*$YPSwt=lSaU4}bI~WA;5CvWe!;KM%aaz`4Yj87*to$D@0XlSG~c`#c!sc&Wy~8Gu)<}BfeGu^z5;V^->Cj8 z=H9NwHC*%8VER~3^WcFBB=8!j;&+szci#Ez*6;uR{g0$R=`rR!7i0e6?}_qn{;N0t zm0~`XT;%q(pPSQP|8?2lxXtgzn?DL`zRtlQ_<={ie*M;G*RFkZU+R+{W6pCi=I{SA z#WG?bslgL5kT~f&Yq^FyYQ}w2aQW%=PxiQb@#<4E+`lnra)2E-pGH3aEaq$T_soyy z#$MwrTfXPPzG8>g-(<}fL=x3c-#=wV6#y&V4Lhz_2PK#ASB_Yj1y6XJ)q==nWFDjd zJDygY>^tB7)$8L9t8sPMxBmQFf4++;#;Em7j~;zQOqhZ9`2kmmL0oNl|Ne-XXmImp z+<~P%hRVfHexf~x@ zhe@>RF`y4QZEOdo9Tt5s_j8Mmk@%VyjP-T(?BjObEE-qX4<0Jo~OU*l$wal$v19Yewpeu&~;1LvBh1Ko(R1AV1 zSJ9yZ50x?X)&>V&gbU@%=rveL>XQch@ylLXQ7O_T`V}uLs!>q9#0L+4?HFc86czDU z+(mm~iWUEu^I*OAY8+^#oih$bgEDShmOAu})kaEIpgC$o8dmq&+O@WDy>dzw+qin^ z7VNEohi{FYr&OfYzIamJz>9~bnB!5Yu6qri~PA)jlp zdjiP27sES1K)?v0j3}C58t>GPX=ER^gT}?f%E&e-x zo-7~97HM#Wi6pEyN;V6=4Spmk)FXs?REMrZ^?_D!-srkuHzSO}Bpdw>!Q(32B-2`v zYK@Ms+tct%7rbkKR_#?D3;f7;Lm^218D*eabtU<+zAVd6E0hxSl9k0aCZUYTcW@m^ znc=3k&Vrfbm69heP>rjJ-BO!t2cwRF$eE0-bh*o&cim={SvN~lICyJ()pf*^S-PGL z`vGTl?z(wb+RS(9!t)Vq((QbcmZ?o_#?-g#SOr?JM@&0V=NS^XtcAIq8RK`RnIk$pcqx!MejosGga{#%DhCedzd!l0MXjXjTa+i3mZs zL0E|sys`x}xbi^(U6vOe;XA@hIMAabM`mLsB(ig*np<@p6_03S{Tx5mXQpXtY%I&J z0!>0m<4zdjt8CiMGsONWU8$Y-b5#Z`Arn3g0?2C0)6}F=iW*zo%)tJ2WNPVQL z4+Oz(Ud-9u2R-<%9oFh|M8B4|llC~nirBsIE3tE^BmmwTQDe5Eh3r-G>e0!uofF3l z!dlu1TExDz713B@@n20NP_4Ph@}h+^rXa@pk2G2K zwlNE9ZDZ-2GFED)MYXicB|=2)#y5p67>hBo@VqhC(kmwRa56{5u0)XWMhl$;NO=<6 zbUs48tmid^Q(llGYk)PJE!9->%@HC6$}WPh z){fL-c^dhdM_QuCV1}%tqxy>8XKE$`z*l%8m6Q}dto|Z{ZN6Bx$JrLk&89ClewJ@~ znU{-Vvt$dZ01C$mNKcT_ z7Rby@;xTKv(nY&DNw)|`dkR`o<0x0&Bn_XQR#@fbnhpUZO=HrwJU-5uV-uBo?0BGY zvaSOmv>+df%S96glmh#RDKY(35|$9mAYe`ky15Ozv$ZNLEtT@jhBaE-nMJPblK!M+ zjTY_Fa>bxik#m9Dq zZ|86~smy#O-MS_-Ck<=2SgE`!y!MQ5Ny>X_!JH@|pkKkMlm$HtC9mc=oi9rcGozQp zBjXX5dduuYMeHd_%;(3@-Fd!~jDFKEW*lE>Wbay!3D+h^&yd#Id%twz44gQzE;e<6sG&-i=BC0}TrWfevrP zkYxnItZpTsDSOstWrMk)IdMj-#7Mb;-!$i~(HiBkXYv26&=j`wV&SYq(4Egj9-~VTmIWVYzK0MpYmJsxPmL*#E3D2_ z|MtHZ5D2TXA_hutIA!S!1I$POBbBiv`6x40qN8uwkLD4BF~zerTBD41Tv-*=v?t^> z^M+bn_TwU>Mf!W~`qycr&C#{%e?=YfFMarLU%UQ;;(z+?huAENpM5vF?5pOw9qrA} z=;+5)VL=tnQ#oYZ89v%8hsONi2g+WQfJS+yB%g6NGg4173$EH98>pbu-W2oBhg^eG zP-BX_6fTYdJD!0Z2GsHZk7FPis8Sq#$lH@JkVJOB&MR;C?%|)}993if*56Y2NQ3xo z+~7R5xIb6>xFg-7i>Rg*ZDWcN%bi&%$Zkw|RLAdr#6U<31F0hhkp_=r5Wj;l24SET zZ5P8}qZG8}AV6kAFOV5$Y_OND*9&mrJ@yO|&8cw}xZw$b2}wUogKUYl;gQ+nL#jp9 zM6%p0oO3jXJ{Ehz`$Qg$_$29~X~1qU1rNj@{fz!cyd=?(i^jQ8js9$mH4pxmHiq_6 zjR9X44OoT`Cns@dL`SD#SVy!f6iy>;OT7BEbTFDs53CsZ@?q4tW~3@!TR_K7+L>7e zH&4hT=+I!2d`kXxDp&kP-=1z;m{gWkR9QzVHTYW`*KmEum>bXhIB}mqyV92?^A^?O zN52vmsd@|40Y)mzO)*QAZhS`_CZ{k-a8Vb!LI*!-=1GTNc`jj$Ty1AX()Rfw4 zWjOn#&oZP_8~A54>z7uaVQP@wxPk*}XUk)B8G~!Y#oe5GxFX^dDy&2VXBIt_lMH+j zc-L*<>cW@Gl9*6`7!t;BI-d%CDo%5Btv?dgC9+#-px8MlwCM##j zk@c9MWo@lZ=IntgGrpj_6e6Jl;g&>N>z##=W>bZ<^wu{}f;82e@53Rmj;*LO6?2YlV?*iL`COw{Yxp?P_ z2CIU;4rfQcoh=u3%jz*L%`-@#I8Z4AKeb#u%O-;bs~Svz*Pp5v3MhvUWaUshvoxW^ z40|f`s@>GbcI)TMCFd4zJqsQ??TzaCqq0o4jY$%ot}}_v8om#R)*3x&pCu-vy+=)s zD5st}lX1LW4`OhuKpz%Q4(UtaVi5uG)vT;|<1Xn-q?$cJR*iw$Grd+*=aN z=CrkQn>f-rF`03BQm>OumSwALM*>P#@)PjbCS<8yGeS)`a*hau0nQ8REF`90P@t^g z;((`xR1OV8TohJ+EjG)rvubv1F|+#Xc+=JuYrW_rN{V4_=?lBG0y8!@WyfIU{I=O5 zjiA72UzQ|2%c|UX1Qm<<0$G{gXki>RgzPfUMNotq+IY71^I}`I^b$J9&6?WNpUmgW z6Tiy0bJFoC?m4T#y2W-|Y>q1f&I&J}_2TWlmib~wzFaJP%scSfBu4TP&NRVDi{vSN zA1b9kZfyfEedM<_hs%TBHnWS|w^S=tTEE&R8DccjF@4wY*3%h2JdPJ*MXGX`U^ZEY z8Iaf&$QQGf)Bt^`fICycJ|{lLO&wNwZH-MftoGWjkCS!2CSIgsDQ9u@8C%@6>PwvN zA1OHMcgm>#&R~{u#v|i?t{KbGC3*-UhBaKxpgwWO)T`qPEA1G8DP_%$xDJhSHD(_9 z-O*0=lLwxTk$GUtV*3C1_x|obd(tW|*}wnr=Xdwr#}!sn40gO=#Nx4(M@$~W;P3vk zfBbt-TID5sVX*K16_|Vb_5b~!GxxTLYqoBDtQA+R)VVL3+`IS5C(3HnYi-b@N5LE< z6OlF_0Z~$+GtR_E29ru=0P)J_t>Ze+?ngg*@PSrbv6t=r5d#%`)Qd}t5`r;BsTafG zymegX*}eJZ2M;1QpZ0-EXDRJWJo#@F{Vy4K^gDq^H`kMq7e|fmKO1>=W1@}unbCRN zUssGT7$^Z#??=U3{V&)989SwG&q?4o~+B|&7 z$#7I*nG`~2lA#;q1=gOLXEzk_`YZr(h^zq1)Df4jn zAew}67lLDT?##$GbbjKw8EL~-_1Mu9(nDv3#^lP4ph15Kkt>j`bRJcCles4}CpH8= z10!x8n;4Juie10C90^gPOTd9KnpbYXEIwZOU=&5X4OlOUS((uzKmtIR3f-C?!!q8? zreLS37sb^KWoU^#(xk(m_?uO=a0)3%tZl9HEwg%z!C)#g&9%;}sx(FAIG+UGrOg=E z5_6h~q)gg^7cMhKT8VO|y(<9dR+*+jTJqPDm(KLI?{vN}13gAiZP}JS@tMu6?KmD3 ze!Xvxm>af9&3KT(40-guS8T8o8IlZ7{8^u6UNnTpo@BC~Jcv$McyN#obB(!UxL#9| zmQjx79E4@DWZ<>RYPWKWjm^rYY^+T=K;gKa*|f4q`%9M?PYjO71YOXa>8TR%01>E4 zP^!_aLJepR~hCCae`9LAED7-U=!qAd$jyjPXk-AEzV`kc-)2cKL%`)Ch zhGCXYASy6V!y5wegR;DPT#~^R1?YHiCmod#^B*SL5QjBh@%LM{l@(19xL*8pnOXsX z=6;#=Wzi4&6B>AYSqhm328NOpAKzlsOm~2m8nFrbCbvp+o+?cwG9}WXmRJ*GI`TQ2 zcXK<>4BuO}Jf@v!mLS!*F6%OToYj`ljj?uP=zFV-E3zKcy^Cp#|!zTqp z&WkKThbMv85WXNDJxs4N!Gw!#z}lvLF=M09Hk~D%&!g)?6|Z zys1OArgT&xA}x7DX|1=KH*?-)Cb`HW*f}s&3o2EZJeTNfLhy;zE9dtn}3~Qd=C)U{>yBdEs~KuRe7m3v zddoW{N33LNf-CYP)Qxgd*(NbImloNa#y#-p z$eCad;Ef0$w)0SdQg-23N`qL!A#3cg+OP_PN!Tq@v`oZ7XPO+vkg-AlIRa-psA>T| zynIx|vhlZKZwy-2*hq!l2_7&9d(5cEa#?zldimI(g!UPq;BoAIi66e=1+v2hl0H+) zAO5lTAd4!Pk?}mmBgyGKXTI8bPaUimvo+0&79ea97Zv`7 zZP!Fev{->=GlVao2dyldgqR-!6*=VP&F^ z2(A{Xo#^ZQny2yALcI7osB6=47Rk0seJ%5x!t%7$LCsYG=B&!_+gu?UaqmF6Y~A>gO!oVUR+7kldMyp}3JC(H?jz1K6N$ZO4KK21Q}j znwJl`qw4S!8Mp0n;mncqrTk;T;!y$ToXDAqXiF9unQE;?p2VaZsf@2U9gm*g`wUaV zV-g<6)H{nAHm&BVkztrt@za=4zRNbrn40L>+Q#B$RGV<>-<1aUoCreY%@DXB3*Mtb*}FaQR1G@}OG_SJzm-_2u(fC@2k z@3Pp7p&HGi@t%D+?F94hg!yUl$&8>GX8*~b{Mnzq^_GEegV*?UJd;GT#+o4?)zMxm zp_suQU4k#h;~Lt-bv}&GjT`VfXE4s3k2BihM<2t0d2ReLwF|Gmm-3j-Xn#=*9z8n5 zK)K~EreRK9sq1;4S?=4~W`ka*YwNs`d-q;@?P>GXaJcXy@-faSEAN2-MDxqPd}{vM zUsLRgRp%p%dy8gxa(UA$PFe>Hpc`C@EVcjUA{azoXdeSY4;UCTVxW^eRLUa;GRr3X z2rzQK<5|^boUdla#qm1MA`u6?&k66Ha$?LGPaHy|{q(HrkqaN^&PQ|LzONTQ6V4;& zJ94aNJo2O4%n!q(o({9bkF;xP1W`-+csBKK{-*S&r`H3A8Rvwf56vG_Bbm>g&V0)~ z&hkDB1MN6e{EyDXK!y{{O>BWu+}TqO|a&_$cbtv?%s@qF$lWAK{p4IAJ* z48$V~gSbjyhry#8R$ZB3Vdw$6pyfrcMI+6Vc%wecq7}$5{la4)do4)|YH*DPk4?DQ zLk?D+LNiMoo|qfk@*V-canz!o=`&6E?3wrcTtsW5CpF1c(B?q_A1zv|PAcAU<~qaK z4=I8{;IUAmf)?W!@r0BM z!R%_)n0KLO7P;5_0TNnhlOWWKrr_!GHFJ+&_r715Sw)`+cQZTo>)0`KcROas%)t7K zpUNUAQXa}puqTZjven8;E~4{{JX5m=1%n47$KMlcvqFcQ5391)e6u7fg=KxI7e{NhD?3U<-8hHLyVtNvW4nDHra;~b$HI8_ zr=wF?1hocq!s)L^M*8D6Quv-LCNj!-zGF;O7G{lf#<3n1CvqSF?M+80@4UIUo)SX} z^BddHqrIxx)R2A@SfYtq3=oQJCcFHLp1XyC;q37NHbO5c6|2$k+mAUv`Yz&6@`dfx zm7$$j_R_`zSWFQl@cbM-ORwOR;S$7qo?JrGCrY+toNk^NeX6d#9Tku~P*X6DUtKY) zLV%7QRD)tuqb?OV&C9Q^}Dbv)MHUl^vPcc*@G?K`9OZyiJaZMsuMBDNq89JShAb4z za`3YeIm>mp3g=9LdHKE-J~yMnVM)0OC&!0FN;Me$CdDclt5Gg>yRGPfBP@olkO%}0n8;08 zHm0=5cLDjyY}-SGBJzJx7pe-B3iN+d8`quU8Y* z7I<+W{z&A^Gv6wRuD64HBbT08y>=XC84EV!HBryNK4Ma_;E|{;yOZo~B%?jdk44!r zKaB~!-Xn@3JRgUG%z1UGMF8*4o(RShNH7Zcor4K2?<3_EN*D_8G)TY5Yg@g@29R0Aob5MlsHCu&S^WE@aLShr;a+r`mo z6m8`vf~2MrXoQMD+dzo|v*F^`9*?A9RdY)6Z7m9xyqe!N}%O@wv zx1HT--r77l@7KA6lL`KPaC@5b9D|&GtcOrJ3X-XraLo!MD02>x_Or-)g$OSRI(4|! z={{X4?mKVl#Y;N9jCE;${`}AXJiB#3%tFC)G_iH6&m{$rKw6h^Wq&_>90mUXX2OgA z_RpCK|HpYI{L_EE%tdRK7^#S}p(L4S`B)OBSfaX-DNes16e>xF7crLFla(&SzJp@d9=$S;h?(ssCXjiximm;CO>hK%TqRoUP_?zYQ$1uw-P_ z1JXHrgWSS31so*>1)94}dub-~Hpa)evn(tMG=lRk3erX@B%LYEWoLxygWBSUQY4*= z`Sh-^u+y9fJv2UHjb7ThTZbWr*vzOKO>p#ZS9ysE>kC&_6K9LtM@U zw{eJq>QP10gI596pSlKr;S3k{OKU3fCE{_rzeF!Q6;krMiglfi=R2z}ZT{Zs?)hE9 zBDpO%;->%+gks|0FU~7BNgZ=q6!V-{LHX&OaWTU=s6W!+qV79`O+ZETu#Zu6Z}b|1 zfxyx_PSr&COGqUY8BVO1glJoK3~iCHylv>K6u}=P%Cw>q>Dr5?lm&V|e<~#91_?$zfoXuFKRBg``1wVu*<`!)Xl84vj*3mU~3}D!@#*AxB zpzE!QrKbkdP<*I0`55F4O|$^bVQ1j9j%1mWP)o@5PWgkFrWkV=ED3TgiuW@Nsr9f% z+*k>}p)}5TEziOvjnc1U<87?!)*&&1W|CQgGIkimQ?0Qowc$vNo&Y0~IcX6cPZ7Wk z%n4!&eR5(EeUVH?@Lkibb_m+Y<%QPwQxyb164)%yIF%w|E?66(9&3}+&!4Hu`6y`(1 z<-9KMH!)wm{>6*SlR$p^6uf|hg#yj3=Yl2K_LA;L?5QZka zvjoBbtGdf!B$tuj`8(|SqIo)k`nsv71zdee3Tff-f=@FDL0>>7DsiQ+A$R6iiOE+j zX!VwC`Z}RS*%wR;5pM>I)f(d#_pRCU6}P$PdE)TyjZOYh=rCR6-av4s} zw^$Tv8I*WS5Q@bpQu2%{VuNYJ;kq0tI-YUy#^(0)Mi7q@_vWr3jvL&Q&|R zU`uwVN_;gn|U?DZPpk64?wTdzuI_9v% zc%s(K;Q0fLD&n2p8P;KXO{vpD(I6qlcL^$|Ae$^__r)|qX|Xwa9!t0K_Yjkw5u|8S z-QC~i2;8HKR9-x}Z!K^0Uokyf64+C8DjsHMGbVF`q$aobENw0vL~N0k9B*Z5pEra* zJ%Wf8`FET1?AEs?{T}!;hn@g4{gO-!?#d(tYr;ENe@G^0GvFUDQ=Fp{^Kp2We+c;M z>avMLf7HwwJPx$5p!xOfeh5dlW!R3$%$j=jd@bEMLn>h45O99%PXbu-%+CA3N=3La0-MVlWTT{)D)(=;7jb zdUj6U1#gzZSOlbLpC6 zfBoY7-`CFl%Z&Fj$F6W+eY&ii{m4wOf-e-Lx%9VCptYix6nu>a^(_T|B(1f+`ERZg zzi_=3uMl{C1{zPYQEN7X_M2*R@cP-Oqn@ zrXb6-B7%>Y;Z6PUX`3m0yFb#nNt*74Q#GaWzL<~h5qG;5yo_@OzfmKv*N`r^r1n4k zle6?LY5unXR}`3;0y56{-wFln%ZQ{ruK|~t^$31@;|KE@oQVP*T$(6IJXppL{1qcS z4bqs%JtlMHE*P?rwAJbP(QF2`v8GB{4ltQ3@XYk3IeP*mTPW@N zD1|zQya@K~tPn(pEtrtTPJ#p&epXm^-)-v3ddpPwZE0>ZCG?{&N@|$e7M!>-GL^vl z5fKUAYwDH-;Os(d?9kK0V!v^EONP~q~ z-R{_X$2mWV{(y)QD2c;|Cn2{D-t$H;qV>G3-nD5`WVCXG>Bn;$u&hs*|Gx;asx93?sP*xvUtqKpeVQ_TJOBCg8 zZ|+%k%|>zoCGVvQ9b{4={xZQEyJm>q4VhP|22#K@D&oF8|zw-2n3Da~)>%H&jI6F{OkL8}EhT9Yt z_d{+kv}ENOz2OAsaBQKjtrbfNi;P`4)|cF~8v}|DS^d7c&&}`-xxQiXc1>UZTSIx9 zLUDya6XtM_GD@HKT|dOJn4GhB^|+0DDkOb;u3~(92Q8*iDms41@WeCI?XirK<^x5R zax(i(Y!0}=e+Nt;m20SJ`&7&wl&s7yIA7oIE6sF=cq{{wJfFo)*s<$b;A<7GBYzv0 zSel?f{9~CtUu2IAXE*z%Sr06CF0_6H>3%;nlJfa_y8RTpZ;@eXB=6Ef_4=cV35sSEd814?}t5@V4T1;<{o6Xc} z%tbR1-#u(D-d?;Ar~Og;pp#kJ1wBkHyS4L03X<+=lHV}_d(xv3oM1>Z-K-Yhuo{1T zk7j&574BZ|>L}?x@91_4{_QPKauP2eo8iU_e3J4pmmS{PZ=dN~Q^;D3sH?_URJ^n&rODU2fse;lC@}GQ*wb%3Jfl+|G3WPc5s;FZNUWuJXN1=PVDR8jj+# zc4*7%pS9}vM?aGLm%r4U>v{CxGXA%>pDAG7G-~3?F~%$_zj*!h>2H?;ZE~>8bmy6G zHOySf-wqGZc+V7&>Ejf5<1#_>-P6~;*O%=^mQXJ%ZGwZ3dcp$DZJ)asmJV3K2T_|m zSyIhC*GX%WG`aQX@;*jvT-* zph_x<-5YJ(!f{sKU?)EW2UB&Bv0-q(B!z&FBMbU5cFqZG2ESOoktv=O^n?p?Rxh@t zvxk2 zm0a0E85Zx^9H2}74d-a@%mm@IOO?wWPeFo4Y5+Xvwc8-y=U-j%&!eM^0K-RblIn z@P0!;GTL}F3N}E($na|%5t|n32k?8T2w)seF|T+#cI7@)y=*L&5;>V5+ij(%4!)s zJc>e5CgHkmkDFN(q+nK|T94f(f0a5z#9#v@@YJZM=UE}c_d~EySx_AVFM(*fJJj{z zx-Fxr)?MIeB2&?!^e(5O(T}%)7%lMW&MhpKTeAnzXg>>=h*;YY9j6*q`?6n+Teq|0 z7$iXTEZT4@RuDLe6T6&Dyhu$6ogs4?z3-qWfx#HcUaxK9)Iyv>gM~jxBDS}@dw2%+ z%=zmbV5xuVZ(=^O!Yp}P9pW8NLA_gV8$Bz_<_az8G)fGRU6AwF;`+)N(iyl58@ShH z4wt#7){vCm$&{AjhC5RRM~=P56@!Ur2Jg*%heB%C`o=b{L7eRAN)z!K($s1y;uXFy z#$6#6M{dHn4oHQ1b=%@jg>UQCym~ykfjV@CqmaB}G37eSEIqe}sWRA^fAYNmO4?jF zA6VDym$oBB6^<=4+zimL<+H0C>K+09AV)~qH~17$7#v(D`i00q_JM10o=x`W+UD@t z)8myAT7raT@NvcIVQnB*SeHJmd>hKN_;^Vnn~^iY zxNcvS@{C1(=p**aMkqgEgJgsxlE&cM{`^!f$$~o!4md!?EC1 z-7!nci3@F-IXW)!7n4zi{96#SsS1$9naqR>+`S&r8FI`Bml+>%Yz{h7C%$Yq`*3gi zdg9rsC`9YLLa>_rh`aLb5`QsM_9XTY@>BN!f1-es&h(gSjvapSqy|^70}X)oq4C>x zKdih;(1nyp+jXmzQ;DuY6~HX_Bkh2!{B_vmI#W=j5KLOXv(c$(l0PrmAW&Rm=(w&}7X26l{KSFYRUj;}b8w#F2y`FH7sc`@3>TYKIHd!WZmaf7&nY%V$5z?z}VGyVG5KKZF8>l}@&K^Xyrs;MucG z!PlSsmM9RJTGdb4MFH|T81W#{mUtmsE=}f*+#d!X!~+6pJ^R9gte<{KLEe8bRac@6 z_l9{eNRWu_`6jrToCcThsPh@g6Ph!VG$%<@m!nt6*l|$siO!xR!cv`IHcaRS3YA!8SHy&2$6Yc_84FiZ3Qzb2 zWb?Dbfp_p)ZYP|wE!MJ;p8N!Dx+{YMTMELx%Gj&BW@{e<#mhh<3Cfg~tg79{z-*i2 zQdOjFgK9pONNdKTl5U{_X{Jv)Hgt<44j;Wvs*CDP&RG+?l!2ty)M zE2N)^pEk2=NWtKeWGk6XzGFKohONPQx>DQ z5oAzWRGaJ65>)nkQ>m&nbtc$nN(1Gx36VJ#l;8p^5)tKrB?k?&n)q;MI=%?TmkJ*U z=?Xs@GZL3<8?dpIA9Fy;b##%L z&hk!#Quxw4=HI%|d&}5^&Jg6H!>^5p4>L}YExe+kkb0A_YS$szPPdpt znZ2_}kBSu6mI*)admkC88P=|92Iju)u(9yA;e|j=RSt#*)wrI?4w_{%&@WIa>%K6S z5R43wzc>NF6^{|Q1ePCJ1aXjVelMJE993wXhX%O6^|kFhd};f}i$A{e8^^oPR zPOBDB$Z#E%fK<5QBm|D>x^rv_#xm0;C_V{6lfZH50Ep^dE^W*#(}$pZ9Hj^*L&$~4 z0Vc&DB#l4dhDDhB05O=T1_w+ppZqnea8H)GXF`Gl0%@mf^sNb1tJ(-#)d)jV+cHJv zzCRIK%I3HPAzkFUC1sd#B(t#cTi!fV@ZCOCeln}d-UlREPRXw-U{cjbOej(^`Jfz&s0_XU*hP;&+n zp5|kZatTI-EG^_h!olymaTBMW0aNx-FXd)b{eIgWdiE+U#*!KEVJhyW*IrLf8IsaU zXIe^)Ne?|QbD=}DflyG2yFvP!UE zXtfEdunv)*&aV&Qj?>cC+wyJ|SND$B#V#auxv-xazaC9bSENZ*nIm)0Z!~HHJnazf zreWR4LICT)6x`s!$;>toSkxYdfgzmIYo?o>0eSAGqAB*2lb2-CjnZQu(2EhCebx0& zG)aqmHf3DO)_-#2zV%OTZcno1<%K!xr!TvlpJL8CulEZ7hBKSeHRI3z zXB+!iJ^S_V{o8;4FE4i|*ZlHh?ggK6Q5v^!k#6#OGma)*xE8-0Z*k!v3jY1S{Jnph z)^gALvMZ;@+$VfaE~hn9@w4y!?0Y$zENQS`^_w^7x6XT$>t@AHe- zuU{Z=I~xxv;B+&|7m`I%&I)&q_mF~RJ@;egB?aN@31#-o+0sONm!;N-#4GULH7LObAp+#-(+d~dAY7(^H2}!O3k9rI{#x?rLkec z{RwB}E#nE->8M@vCZM$2ZG+WB6~e zn`Y9ys6n7A96#`qH)e==PA{6CxFw$@Mr8R4k6eMDY;>g)8BzlH2qj=H(Xal;#ZKl&R}MImi>F>hNTb=&fbKn#9tv9wg~gfP8Zoc3CA!T!Nb&%B5wl zGssPh;zsct%b3bVaqI_B@Q0y01aGv=?Rcj40X%shr{Jk`giM15q4wIke5sO zEo>y533L2JI~}ZH7+G075Sz;LW8L>0wj&v8@9NFIw41T7I5}(Bc*ur$)-gCwXr_O- z$}qA+K?@xnot-h10Zs3~%mpo?G79KCN_B*!8%+@=Huvv|8A$=^IS{zFJ1(R{SGl5e z-JWafjPE>r8Z}hl=(pCy9oxnkAT(jFKsjL_ac8fo?W1 z*c8bmR^|&9)2Q|EW88$^ueE{0U`!Lm?ZO*Ji36dW1TT^lhsDj(MvEe=Vh-J4|-%hOpwDvgd8kgiWNk-_l0d zKU%rdz;mk&7J9-!pq(jpV9BfpS%AS6cpE5>l?z3+K5(K9s;;yrpl8W=GIa~O?RDCc z;RWP-zoBOw4daBht;c?Yl(y~5a@v4=n%CRF6 z9Tq@USY*otT}SKgSne(y17?Ao6g`MM!`vtO;ee3t5?um01)j}_Eo@auIQkF>u?~l6 zJ%lY%a7zkgvz^F^;S=5m>3M+rjxS6ZaHbQSJZbn4w(%IYANq zGhR`bX;Hl_j$F_ex+*YU!dun1Yfj@P@?^$6i6Fd{;~|XYv^sV>bL0$W5`si31BQ6y z7is{hWC^ChSzd1l)Tuc}=EODHkC~dIE$XFW#&0^bkLl+A4cn-C$1V^h@@Z|*H)0i3F01k zn5V=bTvKy~jS%E7p!M_B52n5gE6)2e!)_Whthr{3H_jSAxN(v~N(j6GY3*SoPQjx3 z5cXBr+z~TWfv=0_7;=W56tW7DPgTo{wJMw4NJ`!@BffVNk54e8sU~jQgcx$Ehy6adm7EE(=?$7kv{a`+{2-(4?Xlw7CS2eur-R;(;jTTDjdE!N z6^ehwzKt5!Vko0CZYU_sm^e~2)UwfuV+Y<2NV<3F@Bo;&Kce1Z3YRJ@nFgRaPz5GB zF8yM0&_H^Dy=Rol7t>)y&u!1~VvLQIsys4NJ&vj2DUFv;bpJ-?%M|e{1sd3uTZv!h z@Rv7kcDY7;P`~*EZ_MAleEwY5U;IV(`}9-RM{|AtxjO5&*D2zK1vwMB=K1sg`}<$B zZ}uDWQ&N-n>lb;B_40hC?|+}L^v}=HuPKn9X3ur8KRdy0uXD_^O<@#M6zDvscTsST z^-XQdT=`;tY&y@MfAPhuS9uQZ=fLylPkxF-0sDXW!+ibdN6(-Cf*Ix4ud@jkW;R@M z?mFKmPku@&oHNDF=9+UP#a9aP$tSNk8;(`)+VvceEXg@(>^!fP*~WYy$P^$TohV=~ z`#EC}Z(bmQ%2Y@I^L-R36;VLOCkhOCsk~BXm^OTbINB_^xDJ|0dG9JKqfvE~}|FfS_7>y69P^o;`%(KKv)=qL~ zo<)9@cu5`!p8Z-f_Bp1cbX;j?ZnpTJ;q_}tu&-aM9ZO=BG%txY>2CATeD8b8aU9R- zTv8xeJ4?PFPC?!qoI#fvUc5*-ztFS%fC7@wJG%ch`>yAl7c*0ud5|fV|1}SO2`HOo zBq68pSz1{=?+u^ID*Wf4ix)aMTq74UfAk}@FbIGyMlOB;GW{1bQ;>O}ob^WfFbcA+ z$NYjh#gu-IQuyktOu<*I-q7j^MhZ%wUee45(jfECVmit;a<`^OvvH40xXYj7DO4n! z!KU83!`_3Dj<{a^F4={R$!;CtB|G5e-e7TDs1O?8k^}uxIe`tAygi?@OckM058r#n z6P)O|1?8W>yOd6&jt7Xg<1*_}etfd9ulS5Phb_vLKp9>GQ9vyC9Pu1t{*a#$b5Sl4 zv)w&{yG8kOrX(|4-eNkyiCfnYPa&b*&wd{6)7hI{*M=r>ME=`>+Cw8*S)Nl+Cq052 z(>10#wYXHT(r^&aiB9+A;O3LXC(^~ZNNCAh3a8o65ucvBWZVwZ^Xs^3@M|Lx01?_U zA#MmTrzUKLo5QuZS3PY65+u8~1N~%t86imhD$SwObt0amE&WK-aGh~MA{BBLJ7TAWe?abTWZljHmxdQ4;gGUn21Y!Vx~a(pP7q`1Gn~cTXl&) zDv?U8nBq%%+T?FW6!J-%u9_#YJe8r;dowN6llVzEbuJdb4xPB%~-_~_anZtiSM$@G|=pYbCk7QvyEHr(B!Ad=psn3L|bDc zVd(ug;8Z0(F@v&Fx|M3UU&9%GgNE~zZ-Z+?tR(+(SKZfrGqk+2R%64A_=+9fw9^oJk(7FDG(vqFfH-ZFk9&^RjXKUz~ooTce#|IRv6b-7-_I-ZU>e@afZ9 z&r8+Tgk<}?h>XSg3OIIu^nNQ3W#d1xC@4e9QDql(>R97 z&~;FHlwf2aq;`aA#&C3QU7(sl8F*p|YuzE!V;d2Y8wKe}1_K~Iks&)o(JVvuk#BCK zDV#wj#(fL3-xYenxeF}WFnUZm80oQ_oQ~hX8|9Cm!M^kc|x_;?^kyXUoX~B@n#}jPpE(zgDYL-(pxjXwWZz=;5Z8l2B%p|@BFylK$M1; zETw0JiIRwCD&7di8wT8PaVllM*7IMlYTHqBszF~{Z&I18Az{WW^kw$8-bAWYltdF- zPyErw-~%QYq9k(+a16Bt%0#ovt=Zv6S{cU0Qsu@7L+%o4#XV@;r8kV|q^!xFh;T~1 zwER$0u2WL0NEz7Wlu#!CgWR`Mfl?bzM_hbQm^h#xT)gN*76;qcYv( zrn2x-z=@`dR0=#m5r&fNbLw{A@Imyo@xUggZ*%W_S8n(w*1o|9@Xi#@Z8})yI=(nm z16R-aBymHnG0MzHSl!X0X>4U_yP+^$U$RWk-8Zc_#L^eJDey3f zNEJ?x1?3N5gK;#xO+i@r#bLHS;BKk{eva4(;tedwPd8a*xkWEKuiWP9BQ>Ku`ogC7 z;2zeL$#Yom3kMlnhPoUBT#R6&oWru3<3z_%J4{E4>Mi4t5IWj?E=O$Va5=%In< z`Pu4o!RWBYmt{h_{__o!hXA~lKY0Gxd(AnWRH89sjzgHqj8M16+(T0Zo^KI&ed35U zhvN;T`BqMAaP$*ee5$}WGa|2v52ddxjFDJAx(g zLogwDXyU288_gJ^ne-U+BYTZ9WJ;i*0O5$f5N+D2I%o=GQH-N8`<_ZC3z;YenmZ5O z-Mt#ADY;0}tVUDdUwHh{bmGghVrgsb*4r5QE;?FqEXUHVN4C*)^y)DEYUF(qBh#w% zU1Fq1mCxlU$WO}Fru3sTWZW4KIp#1*FN;?M&&i!}7?JE+UPkiY7l%=%{EM;J?ferK%W`rhpCTrsUn!t+Q$4};oLje{R+>;W61 z?JB}mU$r#$mGZBsBpB#q#q`x3=1Ot~eY3i4OKeJ_3{%9c4;}rtU1YewMQqyp^;8Y= z??P+Mp=Ym}Dkz)_Evr9yWOzZ)76sZ#1S8C51t}o8RAj(%ss3kwTo4De!D;sqV%pq* zC*NsH945hb>(PWUcp9IYUrhH9=5slU!VN?#Izw$>@zhFsIA*gm4y@i-1mNCrr-5wT z>~Dke5%Cf=Q#n zBI44$;NNBD82@OFIqP2aZ!s6$ti>NULoK=(^ker)ewbQyvzTc(lOfFox8N63u{B!r z#{AhI{?*^=w2RB#N2Hr2O{llw<^SDL@K=BPXMd=iE_WZ1Zq`DqjuP_}R~-4YlZL{o>WnzPH#HQ~s$6 z&?GwLyioFuD3H$8>lale^NY{F_dVsX=xK?9#lD#GUo@;3b9nOfVpY=OPOFjDtVa6D z-IJ$HGRvp`>~eKdUZ-UKCdK%x&HtWG$@~T8vcu=ueDXB=mrsw$Ke_+(Pt}e-|2*+R zt@2`=@yjp2`c!Ox@%qJU`KU6^W*#w>Te?`%-eQ10rI5$aU%z<6ZkAtt`q`J#SJFDB zMRUrSXV3C(CCaR-M8OwdT;`lwC%2EMK=cSi_v;tZXZq^XFVilTZ>QjA-+S{WYln#v z#lAJqW!G*qN;~sn2}cYHFD_~XTPb8%bg(pAlSgNB49y7r8d$F>Pt zojc^O6KMs~)u(JARJJ+3^nspl#yZjxp6?|0&Xh5z6tTh#T~G;}u`WsqDC4F*(E>&E z@jQ`a%ZX!a0LH=f(Zta5lm3w|Q*y=DII3YJO`ZozMBmvI_*#W0mP)WT_vwx@imNSh zLlBv{gK2;`Ix>nx8ZqIb4eZ;%nVV1_6#zJU5RFkt%s6w|91PK&@VPutTqZ%b=EJe8(#Z{d^&nM$~(2)3<6{ zaouYdm_W$_zWbiuFBi}g-4oDj=A-s4Cj9i?JKWy)){~ECz>gJ5j5KI$g$74=*Or54 z1bz(<+~8X?l8r5SH>q6oqs2+VK9%!;XQ~mGbw=s&;s-C*L)n%Zu_p;&)i^rLm7cmT z;^y#0(yi9gPOGU6(kttIgW$h)!L+-Y-AO?b5e69Jj0yWyDTI^z1m0*>UeM2u^0e*X z#PbA2RB+T_A_a&Yia`Y4Z_8pjx?%Dxm8s$gN4Jx+rh;q-zayp|HcJvKGp6G1A@-E- zn9}yCvWwKjQ$;3sI~i+f48n`tS%Te5*fhLGa7Sue8OQ22l5KYqe?y0 zq2(?s{7HYM7DUk*aH{>~a6Gv8#4)RhL4O8{FcU+Fk8`@Zb=|D{ena*_5?W(u2PN!+G2`iE>wa&g4olcx&8=aEYK)aY28kbgb?rBXsr0|K{7AU&>*fYils$ir|=F~Yek~wmoLu%jg zK?uBUD{=mA720TFJ@WpDYLXPA0{(Yy8iRP*2BS~M-n1T-;4pg9@jmdOU$y?uIO8f& zprK3N5xAjr1wwJXI1+H#9YZF&sq$gXlSh&~bIN_AlO_Yt^@@Xi!afLxd!lb5GDIT& zb&X^nc!`v@AwkO!G)!$0>+!z=rj#lnvNjjykp%cdRf4>Nism*I`?Tt4O$;PmGZwpa znnuYALEL>LOyoZ7BLf%r46%6qD7m|AP4@!F9^1);X2L{^#ye~>1#bD9 zBrDI7E|P3=FOqFP`@ft`^CbJrd6KcuwwLmjPr9G+DVp$$|E2lamw)$?^D5G*8DD(< z=9SK_SlpMVW=PXYJAbIBt{%JcSR|OUO`2VE?ilL2KfILE*RK>V`-uXb-=R}7F35_sahqsyERP*X>={>~je2V4z;SWFia>ge)T_@HLhsq zsUcS{S&@~#NVCq>{i1_qv~Nj)w%5FNYZ)I{)@0GeDpQIE9WJ!NL|WD%6jLYz++Etb z)vTly2yX5`sLQ?&INHnomRE5I0eIJE(tD3W;d{L55k z>HmqITmtbx>(C?%XI?B)ai;lv&s_dLSP%H-P5gCYc}nL)C_wj2wR5H*C3{JMGyuip z%=IPBbMCU8DKO?tLE-hqpY%efzn=OJG!ykS)kCoKaCUrpkj_cLC9Ha!>Tcv$x?-pk z05+)O+Xe=?z! zp@Q=p`8nfU>XUkKTv8q%d1j=N9H1AJQ#?=u)Qly~-#DM}n9h&hma0hK-HiI!J9|c` z63{NF5WmsU1R2M@flsp8zh?%LWeCQre=X+khCbMn4!>5{`9c8BvG{# z(4MeKstd{?@A1%d6r(qhtPBo_Z_>-FErS8xszq9+IHFR1C zSKB}z5Qrnb0=5I-uGST}i<%JG5g(V@lrfD<;{-ic(%jZuaGRNdFomh1j8-ij3PaO5 z$im=lPi1fEQi zCEsP;G`8YthIykIVW^|N2RE`H;?4}+UClnRZlFuw!8&ifA#6L$${ORGnqkByr4>|; zh&9eW9{VkbUupw_eduU zDz}HwFjzVXphW9ji#`ef@HpX$UTCE(>q5w5{8#~tvTO5qT9 znE~(1&6;g)z0fcfLA757163eX>Z(i1D4?8nj5ODkqn1qJQkWr0N|a2pLe8Ubv;iV8 zv#oK8pt;IRSX{}Jf5;f_$YIXdafddaPf?^<0BjHUBeuw5oW=|>&1bWpKf6_07I(xh z#T&W^8KmHbfrUR1JPgce*DjgJ$E2Nepd={>=Tv{Z*gm!fOuSv8SIFICaM)y zNS8D%>3%GR2!Jh$hR%Fe%5cb91-S46=3*Hr!zK?nt6vJ_ktlUl0FdQ4!Lq%4)Asym2 zlgv1nd(0S(D$TJ%2Tbp3ME$ZD8dVZ#T6&D5VH}W<8Xu?$z!Oc18XrKj2K+7#rE5%N z3lnO9=2XB3DYtFdLxhu1@5}X`v7M=?aohXpe&r5KvVu35%II$2-!p^F(I55x4oqWv z=NvvLId!}qsu~lL7#iCvMq%u}yEpO9;Vz3j=LbK_1@eokTb`I99?N|Kc2Hg7p?GvPHW{OezT`qc}Td;H+9fAH7p(CX42 zBbW2l%Y62&iR6y#rxcXZvI~ja>i4Ia?Rk%oFFA8F`G3ti4fXzsC%57~d-m0*w-jiP zk;{4PW#0OM2?<|)suYycvI~ja+U4{y3h2>)r4fgP0?k4H)!*j6{O3xTHJHGc%!0s*=f{KD~AS*&mAYZ_F(POIVr8{c;|A z@so^_XSo-Q%>VVjJX4^Yh10mpB?XH+%g$v?&E@HNuaVO{_99c?nmVzP`E6q!k-nci z{U6In3g+sJ%i9_7*`4`M{_^K<693{~NV0{INrL_7|6Jknz8cgdtuV*T z^4qe;N4R%Emfp@G7I!vZy^_zfALWxI%qN)w^Q$k@8lv9}1&frKDbPwR?a=c7Rl+k) zK-sgbEdO8snsK%?r!P8}6E=`&nPZpFhwT`<B-O;hpSHxo5C0~&L%wu zvvIcJjqE&WjD#A~J;&9XI%BijYbw%{d_*EKE#B)AT*}KC>j}k7P)@*(PLZ7Y1N0Jc ztO*H}5-@S{>CqkVO2Nsd&rt>^StohTh)0c_B43mlMBf<}d;W}`@!!s-$uUicG4n%0 zx-uxYCC3GqO*%#27{*6quHt%ZjYZ?xdzi#!M{Psm)GCPL=>Z!0qS!|{Qj16uFs(y) zW=r^Gd&FM_k-xADoNXqhqcFmars>!Gm_#Cmp(WJl%zES?B&ogkO~NS^=5|saMi@3l z#9gu2L@iS@KCD*hoT&zBbJ9Yv<(7ma(UMKwbNf*fX=t&cv)4tkH7`;1DmIi*i(^EO zRFX*!+&((IR6CE-?4tTWCukd7y3H@G-)HaZ&BpW^RxxQ3NxZ;-j?{JMmWlkj< zw;XJ7y~iAr|Eky60<+MpHR9@5E=}+vpKW0Crr)xYOpS?~jp;U#w~avyoLKl<_7*|J z*zDy>zkb7-Hf(yOo!G`?Kv$xlqn05YYTTz*Dkh~LYd)Khp@wn724^}g*|*OIH+Ipa0Lm9%S2nqyQb zWHnPcGT>^smblyWoZ#MHl7#yM;2*&Hrr-as{uS$+{=>Y!>7V^y52vv z$6o)1Dtt?O4aP%b(4Yq?Vy54naA`v#vMUl}Am#*@_7n2MEqdTi-CQ zkTW?l>Jh{-m{L!7-3l%zi{4owy&94$QL6&ZF1ZPD0|!b|8mm!lCt1F-8&4q{C&Z{Y z6hEcUXq)0%r#s-%eX)h)A}@PFUs%pGTp+P#Jg)#oIQd?Mjxp4+8u8J^6M&LD3xtpeHgb`{O(OP{dsy9M@f3ZAhfVv-F>Rf^KN6ftmTP=vdv zaNsd$o{rpt9g|&{GlU|f<{QqaciQe*D$|taxblemPTv8TucbXKQ|(!7U{;=dm@`bN zbPfWjEf&89JzoRpD4)>tjVn1BN+kAaRg_^ECsHSuJW|kGaC?_JM}({CKx>OGP+QVhxkY)BkPtJRfuluMAlYpPW-3Xo<);-nmdZtsG=ijA}OP_M^((&(t*b+3-l zni5*a0%0BiGnV^Owz))h?-{uAG!)uPG^IzW%8`p#C1LR@MwFSL^Q0{B>7H&g2D?Le zsEU=PL2_+rcP?$an?j^IJ;h=*UC53i8G4H3QcH|14UFp=sYrJ>>*}Hn+Z8<=0 zJhEDeHsV^;EGPO+F^r}UEUISUm=uW&VKOO}jO@m|f`P@M?VZM8quo}~(oftUZKP~| zf5%2~C7mp$J@!LU*6YGa$V7E-nns>bjn%@d!a0rm7BpLYVrkSXi;)K_nFxvVc(8G$ ziQ`rFIGi2)roM~yhSA;|Z@stah|FU~IyN zbxcFB!Js?4F-*xY>PzsjSM0VnyU?zuiPnU6*AAb+?$n``N$UU~0__Gj+EMEf;|i!{ zM*w;q$1#v8x>`HAwf0U1twILvU|Y%3B1rSx5+sk+@{JtFwRQk!q@VXi2%%Cl-6^)* z`pu?U$#o0Stem%dQfOE#%IJL2tTy`*_q{1S_(Hq6GXupgnVlI?b8D#z#5~g!)C|c@ zYqWCdgT=kuQ4#obxRs(Wr~pixLK4Z8PB9+9%O}SCCD;q|#|k9(t*NJ3_+>0Njo}KD z#(OW!M@M%J;o6+&`Tpk3t6%8)`fJ8uS%;xZZk-mKwIE)R+CA{AN+t_qU5guxd*`5tPur_3Tua8F1eLs(g?$y$E~uP zZNFHh@|rNuwF*n=y^jJyo+(H)U(xUj_LtW6=~th9wp`gwsnCP^{0p64EI-l2suHQh zFls6eXvFk9ZYr!+772TKY0s)W#>y}*!)Ld^4p~N*G+~xYZsn$0&`wUmFBFhJS&0LA z)c8z+u#ZuIiVi8{app$|Fu!B|>652_y!m&xW*X&Xz0-ovZd7Ca%FbiAX>}5#yq?pP zhpTl>c^%VvRM(vR8U4NGfyND!7FilUlP>?a?`Rw^eQ_yd{9e8~=wynodI2RheVRXN0 zUZtysl}|gT&eck8Uay`F7`HwC^G_v}UH3}1Aj~BQhkv-lUD}bHOCL9uZdS{E^v)V* z)DyQOS$f$p$=o`GH^=APu;GHbW%_xgB^z6_m^8YUFv2dt1gRqpm;MX#F*AS+CrbJJ z0m?irP(R^?$XUt^x?O;}`X3Md+@F{1EFm)&E~hij$@Q6*frr6nM zxhMKBVk*XC5bQmpWz;}qT+7iMsEf{dgDhZ6uUL7sd)=%UIY6p%R8+6roiXE*B0Ydo zEhXIN0;!WV!#RlsBh#N8-X8tV51nVIY~A&H_UYW;*Fp4;ld-OK>|@liZl$FA=F!}9 zgFPs=Yq-^1+cc4j@&R%~<(+x~MzdbZSrExBaDN-n&pNox#*Iix%pTAS%Q8NvLnFy- z*`gF}8KjuIrZZ?}8OmwYIf)ku`cx6;Mj*O27yb{dxs}am#w=9-Qc5&-G?u>B-qB4R zNAz ze;G9NrSmm}Y^-662g6%Kgm~w;kNia&WA{y)x;pVQT;eCUXgRYu1i4 zl-6i@HNB3br80ZNemNdl9urx(?y4SFUB|`)@@Fv;7*fJT@}+F)PHu%zsh6BB@4r{P zG^Gy)D+LT2(WAfL6cyyK-|n5-2g&B)sDh@br?YS=qc+_GC_@DZ!rTHcnn%LF>()lr zZP6WH?Kg!PN2I63LWgYQSbrq-38%rnA`T92CP~?_nF$X(GBzn^OgZw{F&Y;yhS=t8 zIW|W4I6EI0+pue3eK>?%TL>E}wSC$Pgsj>bDo}5@vk3 z#9i7$OXXK2;92K%hlzZAbeMIxJE93Aac6IEuR=I-nF@b}H)R-v2Xqf1c(iOJ#@#?L z`FDdpIrS<%u;=uMrqsHMkvey4WNEZn9E~HwahkYfYuw1nk!MhN=1CG>36+i+sS!rI z>5=n#h3E1L`50*1GqjwdF%AjW#Du0 zLh(KAH@37Fh$y+=<{}^e2!08t;Redd1s_)~z-0I-6HA>|vmGS?Cp;2yGV3Z(c|@c& zpg)UkSl_Jz=7Czj0V*aC^a1`HZ0nPqojNcHib;%Q5)Hxrz@5RKBr8MJHZ{gT6C^kf zN#K=32!7~Vxd7@K)IPvpBd=U&#+4w7`+`|k=`XQSch+Ol>^l!*WqAaUr3)v?7#W%h zKvMR+M`-vOvLFG4oT!`_%?+pm__-VOrSs*J`~YGmiT4q6k&JKs6P~nYnOHITKZHk- z7s1$T`puUwzGPq8SFb+%?C<{Wv(JA1^G`qh`fK(PKsNjuiS7-0J=w&LEXSVIxWD}J z``?$mC(PFu>3zKQFa82O3npVh zT2j6>S*-;8hABuy+~&N<&}KfnYH=?nLs;oFt#cvIK4;vA*lu%P+~@T2=~;_=F-bd} zrghHa!sm>9zpZgz-v#>N+MX3*jXo;f-snc|`E{KVsXT@*IuKk&Vl~~F88mBr3#9rX z&_q9wuv;f7p2UwE_Lj2Jp)Oow)PhH);|-svWIDt1LvNLk^2i`J;GO#FXK2sXYuhDt zHNA3&x8u|3(obdU6=57r&m!lMoa(}%dsazwL3*tjK`_>Bf?{q)?dOQ3p&mP=A-Z_8 zW0wj($~Ia)*_Z~`6!mAGLDvM zFuGsCdisX0jCW>q6tt}+L5$ew=#GXTdJMHw7NcVfzB1%I;#M^p<^_{M;Q&?B3c5Td z7xE#2){(|QqN}-`k)M&`YS6|KA6Y*_QNW$Ar) zFa0EMn8opG_yKM#z}&e({x)jC&OkniLN}!mIIc#P8}PV2?;p2{l0V86yk&ojl|xgn zKaka`Cji0FR_gD4X)NoMyrb>(n+9g>MMFb)3}=z&$&||!To8K#T8sQ1fWm1XLWds6 zJrCHpxsUPKnl#ht8Y-N1NIlwFNbzN)fDr;V;t-QdW0~Fw$_Db3wsmGT=yjEg0-ZpX zxn;{-c*uzW?G>YywRfxL&TbkdVWBM;+7xez7I#&Ug{HZ{PrShZ$AwEU*9C$W4zAX0 zg-$l^b0|YPf_#@P$E)0N+VcfqYnMn_U@N9`>_8!NKa2r4#TU!L79Zuvk6+i968AMYSkPwx4U=qj| zD1%D@dAWpycyNOg#23t5%8t_=GDCI@p&X|`R^ezvFkF(_18;_78NyT@!+==HFdyk1 zP!yI7*&5@Bk70~Aj6R4`Al)#Mi7y|op!`E-3$`l=a+SPm_tVrtDeJjnbOp}_CTve` z558d#1a+Zkkk$BHU>@NgLsu*&QyRwgG*QfbZ;}RMOlHBEQ5j$2PF0pd&o`2CSZkD3 zISaX%Cc9x3(uVa_%v;9;hw>t&_uGkskPhQmMDmWjwqs$+!Z)-Vnx;>is23!|1{#eL z9voX|U@|P~3gdXW7MSqK8;5ZP!m9)=pPrG{P>liR^-N)hec%YF3Z3S_8CES!2w~m9 z1U2!nS%p>bWeaJ8%*qoB7XVbuLmoU}L=p}@s-3n9$T@Rpe3+g4Z zH!bmUM*GK4uHC)D5-tH|d+9#=m^lRI65{0?^2aIa?M>meCP=G@WL~aPl3VlE3!a?@ zw7TS1X>w8%k;`?qEtm@vv0f9mX>ZKi+>=~HLYEX=u2K?SGwCvgS5N#mL%}jft$jFO zuo`Ju!?c+4|L^{5=x1K_SHH@oSxS}6ckh22$xqLXbXvz$#^3qLKebQW&A(f{WQR{?n^r$zCs1uN zZvIGnelB}|{->M2`6GtE2_f^z)8EHTdxDz3xn;q!XNXR%M(5&HE~P@9ckEbB3X5d4=EUbIi{1Melrxf)u_SPx*3`5WOp#nCecM%1irxouk+UQd;|D7rkh;j z!u;wLPCX{U^*pUXa0@o=*qwY+2LhX)zfI+gJsE2znON_8ESYR9yRvkS!%-b`PaI*g z_6T%q*Fs0ZPt&Q^rOH71Y>5mI;b4nL&} z)~b|z4BHs`>hx5>JK3m2Kid%}3}UUa*;40L|C%0_ucQEPnL3lb1aQ&^8gN zEN&$K3XeOk4c$VOTU!v8#-S?dKo&{{P;}+kC!4}4g*6y@t@tG}PbPh(WOtF3lkUCP6w^hI2)>FSKN6yJDd-gZ9{f=&OkTAy%cD40PNk$7T4jC7T zqTaN0Y5d+rxWyc~Hnh~zH40@{tjo}W7|UlG3RTn$Zf)zPuJ$PAx)y!N7CJYzb7jL6 z=&En(#-l?Imf1#SSGwpZK@(gof=zZ9(f9Dfcv^rjOyvuvXz3X#yf>o@LofZ*jvO1V z1a3Bs6gcG-rWKP1*nz-L>}vY3ArUiCo_mrT3mkpVg=y~Pm7q1==wt;^(W6aUg55JB zlfjyx#>zcM2y$OS<4j~6#yZhw#ha}&>gJT578aCRs$EWRUArFcttp1EHc(||Oal@H zO8z>Mrh)THg0|>HuHcTGM`EhIsjwE?HI$sUKp$6g+?}YiAq=G;?oy`3kfpO^F!_jg^Q-U3xZ{z;}C>z`y}A zMr+w{-tOj}^GnDqa2YmWXo$f&uxMDr#qJ480!+XJyl#3|G-k6CWq{%(NX=q#Stn}b zlE4E-1U^Hw3Ktju#az5mu5MBi6Cy%bJI=4Vm)}-L)gIB_<`83Ay;}!OI*WB(D*J|^ zMJz&@jDV@6L=|NGD7!#nFd+_$|6*p}6ksLk$Ocx0n2AwKa`8l%LetmNddF%*or4v( zNX%h^SglNG565~R4sHshpa#MhgwwdgmIE4sr0`iD!WWOgY0Cs;>lU4(L3z+W$9;mD zEkR<34ua{~Hieds>>1GpK~En|;Pr7&r6IKwzKl~{Mx8cPY$kT~jOd7`77#&^E(3@~ zQNr*z{r}9p&&w@Idgu3KW@M2^)Nu32GUJ7N964?=V#J7oTFv-^BHLhE#u|E4S`-7i zjYfrgGeUoWn&>=vAkRkR*)&r^hCG;pQDGQn@S;~D(uIT;4c=%mE5q!(b8S2e(fFOu zH}d5BzW3Ix>Z);%J#kKEWc+^OcRW8L;u)tq!41E2#8suaK^Kn$mf6yJ#gZ0;fD$(- zQ0ok9#CB!0zf@c|PtkfS80?NEC&T~8Wq1OICuV`t(qd>Y7=6@OdyGU!EtNS|qGQojM@5oVHjC8rnCesN@{o#~hq_H> zx+NsyFFIXh4+PFG?V>`KDiYHiM9+eTLLJGRe;p7M9t)}L6&F=OTCx?dyP14#QK9EN z?(XQX-q{Rq(SIbD=k~*9mX__epwE5WU0!8fFVr*l?>skWM@e4m22|a&{KFrLeXJ$= zcH8oPSMm?P{ljl>H!%yZ08;@W&Vlc*EyQ!1Z}P31D0Bm=QusD^CGQaE{Qfr~_&j4a zzi0@)3AE4semOt;O_w*#&tuWI?%#dj?39Oxcm5#X!c39M?!^lv=(!*EH&NnAKRmqq zo5l^y3iBH%>32nlFUQGtPp|9W{`Ln|DtCY}B6WV65tp7--Mcbx?Dt<3E!{Nc{yG^{+DZTERU;d9wzwC@v=WyWsa z-S-%kmrZR_;<|?g{?E54Jmxb>oDv^$$M%-QN`^zGO`H<;xGiH#07s z`-WoDxfwYUIlOlNhU_;{jVJf)>29xEm_PlLTdNfwwcQi^;~#GjNIoRf3qGiIW(d?L znK={WgH7LK{ttyfiE_#>lp@8vL8#uS+csXi_c>EKuzuL?DzQ5jpRsR1Q3#C~JN7N; zq;~Tb^X6}J+1{J|k16Np5Nx+;>}9ZxL_1AIr#oP8k~DDfH=s-fvTHPDj~K$$Q5l_CRnpSrSd` zbCXTThYHf6@TV=m!i{H^&W^RRq{XlJnOTC(3p{d;W0&2efGmb$&bPeS3RHo=pq<6Ed}(dqXawL<;Oss0?_x~93(DtYKK`2yfil;F+-O? zqKbKlLrgctw=_9tc_SxZqeUBl2+B_p&6B{X@PN7C^@iD_18aV7FJaSqmaewV=VIm; zH{wez&`|=OFZCz=XnDBnD?G-N8!bLmzL3!jCMmFVo;-T8SQgl;Ok~!#+4jz#C#MEC zcfCVavx5#3n>dc)#OBj_35#SIICqn3_iXkJRFB$VMe7Y5u3ELk(Nv2cF!J+OSGB`m z<*f7gbH}87NtOAneAop13W?&&U2_*{+lw2u!?+KmS9?cvKwKuXfCT%o( z%c=F`q35T&lZl0+G8cRf$iNSD*a9oYI9vdhXC`q#p~+Yo^wvY#D0u*7ak^o{jzc%5 zL>W$Pd*djS$~<-M+J*ZRWlls5HdAq=OOho} z$2E@?Y9SqF{SvNnA90~Q=BU6}vh84Aq!e3tkolpAjgTvE0N2n!OHhEqZ~oW`$7iLz^Q8o_V=#3abe)L!{FN4iF-ZKA7*fR z>TdRh3o}DNI3{!S4$&s^C7#H^Br8=Du;ds5DPhu7ZG%A2jwi55+MB*}Th4&Ze8cNx zTmZbodR03BYwr$`$sk7KkAq$oHK2C-1SRS)+8`8JA9t0#Z^oYPEfAo^M5Yr$+_ES9 zqx9m@;96&UvNl9pBYS0|yRL3%&eZVg@iuFq7}~>aXnS)$AjwS0L0vjJqR{wOq7xlI zFmrOpun`&5XhLOw!ihqNbQa+Dku-hwi!GKY+0$0)W$ms(mT%kxd1K4?Ug_Pv0wF&cyJ;p1Utz58yH&MrK z;0p4!RiRgN>~W=57HH74Zl#Zky{#@l!@0Jrr(s80L;&3AD?&w#q(h6!aj0=J_dwc2 z2R}Djq`u@Toyf99U(y7v3Y=*k^AK3k7Ev1dNf!z?M*jJp4z#s67jB%!6~^}^O_-~` zavfa=`Z-k2(gm;3h{-w1v!DU;m?dKumK*r;)UGpzn8LSZ2v(@Kkw^4_FH{bokBMf6 z(QgG%bXaBe>e-HMjYODt`&asq`A2tsH}CouiF_y!I!SLK?q(UE$M~H$%r@+&TZ+Fd zPPb0fe|TW!{P1z!3$D$@;v{Ofb7o?T(JId3-Cc6)n~Qda>&a=}U)$;%!(E~^n^~M^ zq-59cJGOUDH*0_RkAgrebf98`v(i*LTiP2OyZZ)#T(mQMJK6CGh9?M+X+^yaf}Qg% z{S(ss;M12c$-U{>_j-Sf6uHXcP{{>EM7bKKMZU zw!ihQ4?f6uH*N*6p((&5&i4L%1ZMo{2QOc8vLzV&L*56jxCMdA;&ljwIiPH^-n-MQdmFGJz zF>5cl+`rrY6vEu!EyJy zE^{EEyzr+9Xz%^(y`L#2@xylrR90rc_UAA237#9S{PQ4SVBa8o4TAmt#;*f`pm~5~ z@adPzzfdM!M~_55q2JbZl!XdA2o8SpJZtoAG$gZVAOE=W8WERAN!C4G;n zIKCXSixl&Fu)y+^XS0{v_Gpdr6iU8dS9iAqVUK}@@7ys7^r4^opcCQ`b1%tNjv2&V z$(WVqgU3f15Ml0E+A!6WIZ}ZyA8%9P(K62wk0#9?o7(UU=KZWT69^>J68U2tvb!ip z`ldf^6YFFCIH6w4qq|}qO$*&k(uKnaQpzT65Hl}6hE^hqk+lQAA%CfaW%Ai(;q7=> zPVkex;?ji3SGSdT!cG(9p0BBwUn$vXPK0TJzT9oIWqtwC%t4x|1NHPAUao^udytK; zEa7SPlMa;RD*Co((SHQ#AS(_z1yt*fWUX?P3MWTVa)iZ(Bw3p}sturffApA@JjQdP)d-JrCoY1ife|W@9en1XtH{!=Ht{4?D-AwTY@>~glP6G zP>xMUbhdceEB@q#NvOmqfzmk+r=*39{1y_#jtasym##tz_&R3F2Tm8gEK`m;n zx-14B{77OPQboa=R!7n)2_D7M(I+H23S}Y3N()jiA#9h;9Z8o;9@}Gf;qy_AIE=V2 z*{{B(1|ATX{B~fHN-M8?IHjt^p1jhD1(8(JLxpT%wV+aRn@R4QA~pemKgIi`l$V@1 z(W!9niUNYio#90mG@B&M9ax&<$MX|HdRG~h!*553aL?lbzshq*gH>6og-D4Cw3ThP zg?c6wJXOgz#}THw?`G9+HHO)R0QIc)5CgzpYuHXCL?c5CK!lah&h=E2+hz_FM(2(T z(I-JiQf0mjt`sSu3?2y^R+%(e4iEqnzx$yuoqCl#;pDaHpJL$8U;~sC7D}`lS^|M6 z1{BQ}A9Oa9BuBS$Xfeqhr(B9^15au<9mD>$xou^T18gYJ4}28$6cq~HbZRGf+wz?N52}{N2Xz5OV+Z&7fr@*23(b&kbUV}Gk^mkDaJQy zlsSVZM@{BI?D!T|syRxtiUUO^O~1!)#HLx34oeKdZj^&hk>bz{E|_R#$fU%A7DSQ* zjVFrTn}@iFE8=my8A*9hv7s?R+W*1;MwWQ9k z<{7b5$w+>t;AuC9Ta4kon+dENWMEn z7h;I#E4&Nr%*Ggq5NZUF9^&jnbbUWi9=^l1=LxEC)reF{X>-PLQ!sc@j%;Ux9-Zym z5wobdA^V++z=Xy{#TsNLUo@8l;RLYZ15}>T+#(M0N|j<_hm1S9&dx=gZbjrL@H{k9 zN}bDspzz`c|50s$Z$6Vk+Gxa(MNlH>ZFTgN1tWuxCDv5=~=Isr@mJ5r1_V4 zU0ZYk|LU)hYOuONRGG&>6hNj%63_Y)ybytD1zLmzI!EgRUCmvP-zyV$tLyMd1Tzue zQ%mtqp}Z;gS`=VO0n&mf;pT`dEgKt%EKD#SF)^veN8a8#L?t45r2*@JQU~q10A(u< zti1x|PC88)RLh-(!gmZ0lv6Ae*3~T&Ax=e2YbXy%HKFx}GKqi=k5-MiQ3@I@Ix^d% zho+zAF_LOGvG)t+1X-Zb9;joc$iljya9#(%CT8FklOJhOlypsgZ{?-MozltqR~Wve z21x2^14DiC1NTuMDClcHFk!41BaO9xvYy`(-)*&NXEt|@{KOI3jhw;fv%L~K@v&N zl&4fQeyy-RR(=)dmW)*Q&?nD`4ZIM4Z5#-zT`PHL<&9`-y3&XSYIat24BOC64~!OL zWYwxSKMk0?C@88Ag?^L%sKf(fDyUX%Mkz1&h_PdTQKtIh3&JdemBo_hgc)Mm7Skn; zM15f(WakXNS(4-w_{~)&2J%V;a;im#Bq9H+*TL3KqKvPlpZa5+sbn#^1RLM``j%4DqQdL*b z%P;JMT5HMH5ixgLao!N#{IvKstc+wsSSq3>n==V}z?^9vM9}urdcM+$M93uffX5UU zsU1pJl`vZD9rNQ<_KfZn-r9SIJ{~}8iBR(zvkLvTY0j)vPgFy(oN*qUoGn@Zc2s6 z@x(RGbSK=3h)-pMbu9Lg5T5n|q}SPlW&ofMR zDQ>gCFS;#lzGR|_OL3nQA!$E`2;7Z%mBE6%EKXHV0|>l<1pX6MQF!1%4(&9}T)dEo z(LuGqqegtGSMOv^`m4M7JG(j&6jMmocteHzb0iq@o^6S@sXl&|-Z$=bueO&0{OP;Y}k zRL<8Skd&96=Ma1j z!Z#VM@1y^gsKVT;{H`rHXKL|I4X(eBePK)dKEuAc*gagnb1L7WoNrt1p&x($?_2#$ z-*099&%@t$b$FsL-&0}`$&yh5C{uqoI`hB&*SQ_tDVjSM;c*Yq%ZHbn+!FUOw!dXAhEp&@EcCjA2n@6W$}6$>@lbxbBxVr%ik=2BVA>o7@q1~ z^}j{_q(4Dm?-`|y$s}8N)YH6-USGxt1LDMwmooLA9y`x?|t;fJsUc3@!0E6XS$unD&XI|SLEjS7if0NVQ2rU~SD%Om6Ki(fj<@_08xJc~LMf)n z4uJJ9zC#y8mfgG7``)xxU~&Xf ztsjJWoJgI61@ISmt~@>NJ$k2yX5YsSpGeE_)(#YfGaxm5INsX>rb(V7ziqMA1Aiop z-gf6A6@GE=G=}MJF2YO^$X8p%cQWL_?RC=P%?N8SV zgZKBJVNaeh$xhFi@CtF^85A`sY4=Z)Ly#)_D%dE28%qctHY(wwT4|!@V~>BuBMA8g zoE(!ko59PucDNbU1@Z!yA(G`^;!tPL#Lg?~Wv^R~=j?=!$(W1VZ3`eG%wIFM)> zsl2nAJP$0*{jzZKXcI#(mqEby@I$V}@f2yGhuakrXgStCic0|~gbwYM1KgKX&lX)Q z%mY;zN6Mi#hr85Vz@}HkEZsS|PK8Aamyj2AEfZ$a0`}gc(QH=Ckq;b-t)BTpsm5tU zw2fMgeRod|iBJu1-(C}Nm9ip$ei@N#gbq9a*Gag!}_Nm87$N{-DI zK0WmY7OvHo$4pFWlXwPe&(Jmt;T!q2fqfwkUF}7}9K_!`+Jkyg4L6}KkBdvyQl^sD zB*a|pgFZWwD-UQzr*GH+$4QG1l+9x)+X(m_0cCvhT5C|YgpUE9Ms>Ik88 zO(6Mmgg^8j7+C=lF|?2%pMpyHo&aqD#)}oOFlwL7!uhZudpFT8k)8DNcjeJo0;22? zMCiLfKXWWrRp5*IfxxBRqoe9@VDBCw++p+!@r-nbYEkLx@??6GAAp@PqwFD=sE6np zDw;Rr9|)5Vw1S>;g+kD%CS?|B1)X-)wqdS35;Hha_64QhhAxs{kZpYT041LYID(;6 zEs}7UkXfO>Jar0-A|Wptp(qg`FKo?gd8MvQUfAC>=k@);W>2Ejb#p+X zFyIXBmC=9}M7cpP!k-QJI5CEvlp=j)tdBJ~wfarH(kqF<^Ylf{QzY{s)SOF!<+bI2 zONu?U9AJrk2%IMQnx3(U>Uh(d8CIQWHz(quG<^B`m5Xz+`>%PSFb`=3!A9ZM$sX5 zX0w4GIn3ET@t*26LMgTCu(r`^pzWb*Qr85_s!E6R)UI*#?6w=%xDsh{DMp90p4PIy zv`Z?FD^)YB%e+Rsf#+WF5onUjafveog(;jO?I8ki^cEKm0EwYwFkDu^9pjaoJv=gL zP-2KlGHz+~bKB)xHP)lQwCmuk^oQG;kc{#rrRx?>}TSZ%unkx=dCUt)rNyioJ83QgZM*orKPd-s+SR&tW%N zFUll)j*OY7!WX^*i~rGj$ z-TqBo{&b4TKmJ$$>L2G@H}dVv`g&UofBxs-`jY(Oc{8TGzGm4h`~5zT{WOxV()YWT z|IOd~d;cbfV?n;%46^HcjM8-<$J^|4NUiXBp37)G|9*ZtiN2qkfBMlTnI*d|hTZkK zuJ7lMV&Bbw`g^JrfuOrMo@BJ1f6MP~-orhb!38(jEkPjL-S=~S54G#NKbikh_dVYs zcqXH@-=?wY`Ml2dFaGB@^?YhcnP8=qI@QGN@jMgx{O50k;C^OeDBpekY*3Rl*p8J8 z7O5A=r)~J+2YhLl5)}*%_C;N|_T8O3U34Fb@ILBWAwK^UObuKV%u@RMsNen8x9XSn z@*$r>g1>DS(dQ4sJU1CG`Fw6iK$Gs8*$`|Shjnf^`+YQEZg17Qr}N% zhd^ciO&|~zPbu!Z#C8Zk8XVhJ5Fri>^+g@3whk;u%UIiODkmTP6eRh=%G9%}^UMWa ze4D&@wi?Lb_h3SSBSGFa$AZ8LS!^oe8@z+Z<6qcY9gRy z;mOiHztS8&YjB2;%>m!6rSU6D!I&X-yk!%3MbMvr0eEGOQTsF>mzqA!>M-;bMK=YD20*ro0}5F zHo`7EcEZ(|*V}GzWUi@(PQEZw{^p#ptW&hUbQ zZ|$UmhJvl!NNZCOqguICMbUP2sg`J|m+ZeIJj-A?gMlBw7ov4qN>QF5-}y_9=1(zL z?ZpOJDAxh2t*&~3!}4k|ivV|Uaj83#fE={0PC0i6pFwtXW33U195bO=$q|aLNn&!8 z-OP`JBe7GvkfIC?GpsK*H)-cR+>YFiy>orZDHO=vb)1OeO8onJR=XNYD+1VA zF55yv6IQbjU}!}b>5)x>>{i}NckO2fa@wEzF2dU!0vNXkW1{UHEg)bPf+IzZ5Ud7+ zqh`l?>HJ|S&k%sdD#2qKanB-rKrw-9Q3_NdTw$E>T{~BTqj3`kTXa0jRl^#J5p`C3<+e>nWL9Vxv>9M7 z>s;jO4>o$rR9x~9LLUGkm#M39BZbmgNMWLu#{i$Y6C^GWlTh5&JBGu+S3@NRYJ*ij zI_=EUUi!jRH;>AD;0e(JG`vWYW8_=1V?o)RKvKAZbR^Y&#Fo{2Jt$`Al0|#Mf^2~% zHGOfufjJ)o)eK$g%*MS-ed`VGTcMG^?fRkwJ7Q7r37hd!Z}n zU`Ldiiw?vnmon#^Fc3sR!0;?O2k4z&4;fPe4AnLdcXSN#R)OjEL{u}pgAuzGj%6R? z2*&H3ht;`W1XeglsAb1G&eyPBnyRKa*VvZuS3%jA=s7`kjSb}Ixm@ER%=7uUE~GWb zF4`jO#?~uE4VIJOtJ??+Zp}mCT4Jy2p$a&%Zc2gX!rhkF@lamV+19hs0M5d`)0O>4 zI-s*+d~Mh<< zjfZNjz~$#2R1wP10nAq%a20v3<>-*-;L@CHgFR&h4TuK6*x|I8i<;?r54r5JQXPev zonqN6^!PEZe(lu+*fCYJ8i`H2_Gic!6F9YpX5t-Yn#$=yAuLD*v$Kk-Opt@3er=2a zzO*wq+lQNt>?zCkJ#X3pX<43&=mGtxgFEqA5K3()t(PN>dJOo(Om+e{c*J;I$B`SO zQ{=Wx8Y5=T5)HPLd)IS+ z!JO4|T~?3g%~|FjQRH`*9m~G!JI?&-4fBqD@9yJ0vgg_zp?q$|_rH7f^5qZn*&$C3 z+nW53|L_n0n6*1t&I35?rhoA155Fz*uJ875>T-XIZ~ywH5d8J8|N38ppMyz$&a>%? zS>|2e?cdbpzH_j5eTQQ=@A_{4|A9^sfCGI&!6E*G7cah@&lC~QQ63&1Uf@$$wC>ON zI?r$X#s{B%#2&WYuh}2|@fVW%1`vR4-XYLU9be}W1pA#L@^AL*f89RkeE;@+|I=^V zzj)35x1Q|nmF2GT#JA2nte#`%ufXDU%jSMM>6@pQ?zeDuVDDs@<4U|7qdKr7) zAlQ=U{RRZt{!IwX($8LMN7lo}^;J}2sy~N7=a#%3g1mnTKEI+C1RLbfWh!!_8JFg4 zoa}G2p@rUYY0fd>IMok6*eD)?c{}ryCiJkKA-Zw8Rf@E_cSVud_Gs-Ci%&m&_h;Hw zm}Aam_xYa4qb_WovvJ-E0kC|kU1^jJal8(}ul<^$AVosI1_8ED6bnJ1U4=P0R;C;u z#*}PYKWg<@O^)^GhHXUMnpec)QbbLI#g%W*1FyC?7EzPL%`Em~F=H#AnlaGX;mT?c z^TBKO059O@`8sHwNg&}1Jx|k%U)^^_KbVEV%TMArvxW=2fsXaA&%Xesr>|abWRNk% zkf*8!x#|X6x!)h`iA!bxZ+dYz0gRYS($>ma>k9oQ^6GPbCnLX_y3mDNjkeh2uIPqI z(=;LYRMBm}4c)rg38 zYmTEE3G6F2s`O%7DpJC5I)ktiqHQgL!TN&Av6BbN+YR2H4z!SH_kqTw6juDUR=G)Y>)7^DH3mT<~Ja}Km$NEV0Dxl`A0!-$*0C3WQ)Idzy#ZtzPx zCi7)*U>2m}JL;#H#->neU}HphBg*ZEl_jb%(xxK5&q;o=qD)XUph-1L>KPNJ$;SaR z%Wajv>_NW4(#Vxtv5N|YK%qHW9g940z%ll|>3ed66GfQg3d9#cnK617i2*kp;yraN zKmg6DVr1`^NHWG*8Xfg1Gl@{oFrgNY1K4AOM65HdWdlGpZo*WK6D!)J5XDFf$zDsd z&a)0S=bT6KLhz3ifE~&J$GekG#n7L8I6JU@*O?I*4SJpr_+)q6c=QnI#?E7839WVk z&Bt}@*weNi6yh+jsHu6dwgS$6bL?i?^PDJEd=z8|5xAw+se zr3DwLOFvA$@<8F$;!ftVPsB+1uzWTI5GW|M0$X^9oHio!&@b5*LoHV@6Yl9K(6h@MedXdkejF-XKdh_(rRS0nmhEKPcS(7JD%o?bdmk$cE`9zL>}fEtlwaD16Xb3Al1@Ue(XCVnvffRo4Uc6A_O6gCKKv=SD# z%AMiDu2)C~ZI$L$4EAY>rwK&gr2*D-Y`y0+pEdTrfmOM>VLna6kr+52RWX+J+*~MP zRNzSzdsEN~Ml%llNDjJ8ceo^QZ#d!L=yOmKZy5lq< z>BR*aFns5tZ7`=5pL4nlWa<|DA|Cmz-FV`~!Z};??n|EvC5_xPi3qlqeOJEFKw8v;O1jY91{%$H3Z0r#C*-Ym+{c9 z^(CGo#1ld+r*@jzk$9kAt-K+$;E(!?6cQ~l#lX2dyQ0BlB{_8|cgz}#2Ed?kabE{8 zQJO(b?7AM7iw7v&IK2j!7pB1eg$$~42vBtK?|4*=7~#5AWY<0G#NK}c(C)gf^|Dm+m^t9C z3>U?eYxi26x&#k>Ev^@cSYY$rMN1^qt9No*;&j_DKQr(4(s%p2zT@8afX{vW(mjSf zguKXoxa+%lcbC4Y9cAA2yY>4YfBaUywYcA3)@GqZp z$mOORLGB4@DgF9$cRFJAo$bY`@KKSA!H|>D*6tA zFtmH}LOKW`Tw%jdvdA$!>zykuuJLmow%Km8`;acK;>RET(XV{;W8y*p{{yg+ol~tF zKz3fVbfxqJflOb7VCQ)6yz5=x!t4FqhrYKPK7il{Kmg|Q90J_{@*INN{(DJ@H}IjY z4quSzUf3%5I!F8sO!yh+`%Ro#obYSO^yIT#dKD`722P!07N0q1TiMU*UJ#!IoK~3gx|P85Ja6{9`=kV~3Y1V;DqL~{ zdknk&bs)`!j&PE=%J&efSY++^7Is<9I zPAV|Fz~RziW^jG=W2EyvzK?_~;wfcp^*wdeS^(mTiz!UEv-P> z9qU7&FH1H&0v&1g>^;kKkQUL-j%^9;i#d#NK-nv5!WJ=ykDePS zNK~!ufH@3%nv8u~VHW2OAz-5TamR+V}M+1WeFQw4%Ptb^k(3!j;Mu+$ zkbMOJBp?dpxo_-)MBp$2Z+%8sj#@0M9=<0W4G&+{v9=ro?XJ;n=ibVG@UHa!7%04g zcmgXWS5Dw-ZX`Wdb0W&2T*Da-XA{n#W2EKaw#PBj*vyv2PTg#*t>Ys6oq#tdwyJY* zUVX{B;owR{_pa;=^uCHsbq&pe{A{@Sb#!jDTLd{-RIoxWg?5*;stJgUTEkXKD5pgT z43R>#jyGx1Oyp}6=;Ru&D|iOqE?Rx*fMtPt;FHI_EtXSsbBYLBF3_X82`rNMLr5$K zPt*uqh3JH_jYNTvdYsh#wiP3V9HQf~g)kJW+JQw}&!VG%q4MY}%Pz1;D+$}oMeMrf zR@u$dqqs}OpV`}QUE6t2jJLW9C)P(Dtk9-gg4&T(y>!$z6A=>sRUp$t!&Mm$nRgWu zb!a7iPpv0m_V!a~mAOQO6uWxYft}Mws9Z;1d1@pWOJwhC=`1*&z8S;^_O2>CfH!PN zUZ@FkU8Th7xNtA0(%aaBpwebN&>cKQjP%!~8;Fo~f46q-uvAmaQzbb2AaFcmbp}P4 zD@uYmni3uogW41izTl`<){?LbN9Uo`q;dt1m#B4JA`@MP28r$QME!*}n47?ulZTrP zHE0B(a|e7MCnx{`u2%!=4Meo0_ki)b;6ZnrV8LwZJ#}Ks# z^e{l#Bns;lj2#p!P$-tA$EjdAnRsMuSsC35Vakl7zyLyrJ|t|`f`zAbRKw-W%!X#Njpm(%C>#Z{*-~><71&nv1T1OCG9#8w&9Y zqwf-;wg_6R>`z1zna)Tj#x#=&q{(%qTsptP9^traA-%O ztsJojiYDXIK(I2x^mZBvvHe7TKyhl2Z{@YT0-T-u%i&mi`hKJzXAg>r;x0Hm!7~+5 zVZjC$M9;`l<0bSWAeelC7&*uA`igx8V7y0K1eh<~k4DRyVZC*Q&tfOcNYp^G^F|u? zv?TWIB&!Q1*7m~qV%C`(s0h?d0(bT59UJbL?002_dB>H%b2snqyHxqEXMbA=8qXsZ zt{Cni?MIvak7V`CUe+>y2WeyRs~@>`rhSp`bARL~AAV#y|cB?KJ*q=U;Cq7-ydrLUk1S@>COt|`~Dq+m;M&~ z|4n+pYvyP4fGjirblz!oztM9)|Mb7ry`As2|6A5|f9L%k&&Se#{Mbri!zY0qUh9V0 ziYNQFoz+h=^l+vr9u=anZVFhu^?&(f7Ek?WoYX09uKLKXKPG+ucq;^ek|Ds)?!N1H z2y*DJfMfR|Ky)2M>Ir62bG2F1;i61;(5zQ z&U5CrZ?Nf2-+sBD;JV!i`Wa)FVh~Ma)4vFTXlY-70Ph)s4B1;Dpsj5?+#g}1-IE7K zOu@og6k92Nwn32Fcb3pk=X6OijfTK*(S$r*F%irxe-O^}2Hq>8Iq#PF#fvYf)faOy zb~Shtj;O<|FUJ>)`j%xT2;B&h+_2(@6nl{shYZ;iYSz>P3w`JO#0q1w`@o{aJP~-- zl?ubV^Nz@SX2f1}?xJV$eLVNzh;6F0PF+c_unY`KalW$Bk{0`^uwK__F>kZ7_ocE% z99l&Pzecp0)h=sfa?|3!=MgR?n+>~H9+64q%}P1HM{`6%$?yoz)>1{d5m6Sh*Vm@R zA7k-_+%rowONtW_G|75n!maV3H?3p6BdI-;d;VQ`3p)MIJdW}naqsxC+c|XP*JIo9 zz8-Y=Cv!ay>_JBn^II~+t(_g1b38*YP#M+4g$%eKtqkIG7FI+~gN)!CUO0!axtb$j z1F@K$X2k?livv(YnxSI(8QNu0DJgwFxzu=1Z32obM|^?&fPaB9c0K4N$f2w2rGtLp zJ@GG9I8QKYkerTwL{c2Q6bgVl-s&6pG8}G7X=c+bOF{~yDT}(M8X#67><4QyB8>+* z=g=(p>-$@K>fzmAZ?|O$Ttrc5*)lFQxxp6P4Z1+P6iZVB*n%`X3!Y_BicNJ&P3fa< zE;rB(pkEs9dmFbE8u_~`8te8Iq%Ax<|-=I!T90i3Xa(5}nq79#soc*uZ3I9E-GU za?^C=oco@e^%klLnexzD%#{_%Ato~0Gq7M6hz1R8de>`x%lmuu5?Ri77x{|=@hrHaFXVvT?iJ=B?E1*fxir4q$c!bM+x|V+0SW&nsip2$6IphRY&*wheP?<;tml z;>NG_V95*rr%pQI_j3VfoZaGpM&nKk6z`!Za%&JC2NrEJHAAwDekKTNVwk|`owXkP z5O(+E5A~YL!aHaf)u}l8108a*vXed+4A)Q?mu+-`pVsP+1ABclyeBeY_kmFz)GVY|6J7DevKM45ucZ?-Yc9 zJ<|!)Dd-2&bxn*}c5$ijMZ#s8@u)hM<1OoWuJ`so*z7k3|ho5JZ1Z zR?>#3z_KppOiAd9#ZQOCJ{WY4XsHV51#saPS_(vz4uZNyHxpL=$Q!@Y1vWe%dzCWo zJAJ@@>2AJol`#t#OTCoe+`*QU=fA(j; zvxnGqai|s3&QE`y!t)fxk>B@t@4CY6yz}>d_S5$T!B?w4`TgJjlRd<)?+`rl)9<|V zork{b;>hnDaPgM+Z|UNAZ}&V1rTGF{lYx2xB4gZd`Qslv5BW{L|IWYN_JHra@#h?I zalE&?F1`u^0U`*Vhx{hr|Mu^I`3mFJUhgNV@eMb=W5WAh@BL)9H+076M{GNXcuP!g z#{6V9?Iu?)HVjd?l}x*y9kO9-3zF&H+&2z5Wpm#vz?H(`4n!v;nd_79D2u@KlwyCmW;k|X?xi2rg?ixo9!1N`+5)vH_i}HI+D46 zdB>SWr`sS&!H%Yv3-l=a4*X0ydr1=@24ln#N26foTTrgDsb9Tn^rr z#2ODK3~)N{1Kb~vGOs~;j0U2@Dv^nl)xp_$JPAV+_t@KoFBY(E;Cvz$$V}QSS>gN0 zt-}v%P;l~OL7P?sUORrQ_ZAl%59xu;x683>I%X7{5T-4VMm+?uYv%CIM&g1%AIcv1 zaraDWEI)!c$Y*{ISBFd}6I;wHS?oPsD%!#i;jOGE#mpllO1}|53r{4jUF{PVrGc9P zZs5&w$I5#+7UG0R`F$gInYWEi#Ae$W8?Y6wtP+BTZ#8evTfdRm2GUj$=!e9<6}W0~ zE+`t-1Nt`HjrZmoAqjABdA7MXm61JCwYIR^cD;%H@ddh)A#t^MX78&@BdYf2$ z4Gl327$FZuib%y9`SEsCrW%YCmDZCL8^R%~cnL8feTpxBL32!7pjl^0-~^?4TA!y9j?5M zjHq9p>{P=u#(!|6&~P#U9B>j!aKY$X?As?Az)dY}hZx$*II4RX!CGk7WkAdl#`bWa z)@`PRvD~c!c>e;Ebv};12rfa=KyU)0ji=I4y;lZww+of(X&}j{obte_`im8w{FJ2W z#SwOa5v<3=IHuoN4yE}-N36;ztYxy{sNL%~gg=n7sE-iv%HI3d7LpjlSS{tn)7t0_ z!~!LPWmu%i&RGrm81EhND zW6{>0CUd|Xa+)D$>&Uv9k;E+vJz9NKDeg)%N*aGol7?xJJ;;8B1QX*l=H>2=1=tj$ zIlqc4WoOR?LS-twdAC&1X07qC4|}MZi2SdmU(R6T*QN}@0G&HSo$;hJD{&3IQpcFH z8TTZcyE!}J>26k`b$9MrCcp7 zsq)|_gzmG?2Csx%2s6>iqKojMeX zP#Mle3TiSOTGFQhk&mHS(s1V1rZKGpCzru}Klz2ed%mU+psW@}@PVEQA{%Jfv`z=Nyr|_ApRtLxY*xrSM+WVF?6?jb z$lK#NR&u!#G;)fi!$efO#5*ohD)3O$iBG30ph9dcoGKvK64VZ|>K2le75)!x&6*I~rKgQEEA|SK?@+(7!uudN$iT zmL0IqO78l6wHY%`y?x^Hqh6n!;U?`_2pMI5b=RfIJU%yP%cqy{lIQrUzBgRu$&dJY zx)1WBk0d?(@c4ZA343NSOV@eP`u~`dva^|Qne_;_$gA+z5rcaIUk?JU_BROjbJL~k zyy{(7VoL1?oE9$oJ$30jS8#)3OZZJ*`p+{zFOOgBw=iXu?XGh9zlK9?E-zka7w{+G zA9{cE(NEvk!zuBU`SwUgTV=kxE8i-Vy)IM%5BJ>ydur0ZJ_Onc{oyAH@!==BK~xVX zuDZ|3=ehxt%K3W#r})5*_GeB!VVcY%z6LX6%C+EY&w;PZD_UbHlqLbh{*aw#5v}D8GJ&iGv1y8M?2)c!As$Z%pUOpP*tRh-wJRc0?EST=EjFAhpTe=SXZhFNN6*q2i|4W=`j@X@YPh zVP>ueByBhotBEDGmI81I;6iejB-z=N^1@1+A0F>^P%!fMf;j@%nQ)$;*}??Gw3~l& zi`Xo8^JcqZy6~*#@Gl6Xv?W+V!%wlLBYnf;8)DxO1I2Inqrda4WsIb?ftKAAX8&%-sPJME0b+iHu@>~Vt3^6sXIol9VNH&g# zB@V3cS{-0UQVe?6V^efXGgd=WbL4MmZXV(j3nnny8e#t<3%ILi#q3Lts3MSwV1O32 za&f$=p}Yv=*dpw)WhJ;|Ox$o&IusC~uzrdo`hqF($Tm?OZ45+5innZq?ErFCcYJhs zrc^A#9oik?kx>aanv1-m-VT9;ncXAVo{$Dl`JU+)Ay4S5K5ckCQfIg zM7i-%*+Rde&XM=bX9-}BK+A!ng5^K@Ef&HwV65IcA4nd{7Vd7y)`Je93WsmT)>G#s9L~p(lKNZ+B%N>Ou}nzRlr*+JO|G;K#~Rhpc!>M}NH)UIz4eeD&Vf}sIDsUV{X2LbEcnJ>$1OLu7ZqTL;q;H`>z=%i3r^dbl&RaowD6<`OQp)@2Y1C?? zirSz=KI-25-GsegH+xFzi%MY}F{(*Qsm~OGm+wqe+H73{rD0G9vIUu^L%6Z?~z?DkwZbJtkwKHwUh`A6|Xc3AwY#qRvO(O&bPxK*P3xql}DE5 zz}61!rqGabm46fCI=w*cTNm1Sa(i!oC2!oMQc&0U8gmd!$Tr6iS} z6(C8)LGIbl#sCKoMphmI$_d;GcZefv6vfhC;|Msy{uk^Cy9J|w80V1 zcYiJ!wN~SB^d4~wyRx+dq3+1QMA4=McFqZIDbW#VFCtb__BG|iUSVcEIxiyijH=+^ z0Vit1VMr8FFwgapj(a6ssM6XuMd372IBq37U(I!eHj1sSt8No)=BX&pg&JN{)Fb=R z2hRALh%>SML-OpYVCEd)c(a`yQJFF!EDx4WVb12gU+INYxhx>x!9e$YG8DHidyW?9 z{o#Pzc^W*GskV`Zx~6qX{Q5ercr2Q0S)kLQ5YDLBeGW)9MHs=_(NB>o>9bi_dMr&9 zLM|hZUgw?w3q*`H z2C#iN;}Oa_AVgxp>!Ng3uS%5nMBL~k8SB_#k-ufb4+!OYS8<&(2Sn}VZh!UpL$h8y z^gSbpj}=tvrEMd-GVFRGY)`S?&q;6Z=cZ3=5EKV|@^He<;fbV-&Nzp1$lsxHm^o#g zA>P#SC=-Y#mm|Y+ZE87mRBS7zKr~f)C<=+G&%u+Pc80#}z&{<5BZ=nI$K$M^2$?`f`mW-n`*`)<>}^M&)%cfPdv&p-J|mQM!j z6^`}s$HD`JcS#Of_^`~vGC%$}W8-%p^77*!{NTsB`AMf8v%1AX!E0^XZF z?7PZ$PWR6F-t`>^hb=g^c9LtybH-YL=l@2@-?snzCAh@X8S1jz_JDuuTjG505JsHgCyN$ zeu6+I?n^V($PR%@FGBz(Dff)E06~qCKc)6~KKg6?=$(IErXAx21GaZV`&aqAwZD}y z;%uhHf1iEko6S4#{tr(+wvbOa@_zRi-9#PCviqO5f1&S9?KtwAX6=~X?Y>HvO!X#L z4&CX_0T%@+Q=uL{`9x)*v)iO=x4BTbttIWTei;OBqhsx1p6j1M%K(CG^+gCi{7@+y zXSaP71h40Hhe*8o=P7;Jx6ZQ@-9YY{VspotMP=E_LiC00yz`CTAc(b>dshm-e@p+S z5JoC8@4n)0xSB`yxIh!+fgXVE!vXc7(lX65|p_TN<152SlB|sQ|DPy zgJG~ZSV#iQh(rg_oTBG|ufCog%RKmfqG9wybK-E;rs4LRZalbKxdwmqfk>gc*myex zaOUK&m<>(@`}=MlOnHLEdpZzv56)5fZa~m~Xn=ri=^TXx`2*ypb)82fw*f)78lTnT zSS*EMX;?rWJ+*QiJBfRIU-Pz#zPL+swPK-+oLWlQRe5W#Bld6YdXL) zm3_|}jCX4%L^`XPz0-@I$ba4xj~yq8gK4SVfJ|kuRJ5#R$Q9M)GN^M5TJWVD2QTLd z;3;dh;H<)?$qQ!+%YlQ8%ESgqmgrW3C5M}v3f6`cK|su+a{(?CqfS4TJ=bn`saZOP z8y2}>zB5eq@y5~WY8>52Wzwt7hbovGfFl}7)VU`F=kI1lS;j^G9;tO+5=U= z82L;U&%PNv`AOmJu3mEo*hd9?;6S`oLx}}ALMw#h1a(__h1a?2B74+z!gQciSf(fw z1kl0Ujj5ew1>%-4(cE2XvYy?apx8a``XeC78-sho(N0EP`Sy-|BQH#8%t8yP*WrkK zI@JJn&aQL(fl@}(?@Av z4hO2>LL{Axv6t1Ra)Nz+%RVPB{c9+sljDh)pj}0Wt}CpBjgQEq!=G&}u47nYJD+FY zgRJ#7pQ<6!M6`Qc6`2)g@TDZePD1zS{<-_^?~6KEct_(-vn5X@qK_FHAI^7qbfbSE zEk0D#3ew_?*`1`wX$^yt*kde88v3o@huE^;0pc-c|e*N{maN7?oage-nUve~~r*BE??w zjuGtJS&VCQrS%W%SgyT>YE~D^5U=rCt)!Xak<$zm@TqI_Ei+MMdvt+7;4f8=We~T% zO-S`|LeU7(BNRjSIx)8(k6vQWE_q}RucVXx#bn7wu;x95&D@k(H*_juwDHk>_xGqj zLIA6x@Dm5$Sz9UeW0p9@|0}g| zxwq5eM6nR{MpTX+YZrcL#sB!ve(q@kziR*G`~P$Y<-NaK{HlFxKRw`Y{~x>ke>|PO zn|a>KgKlyYR;@fJe;b|PKVI5f%sa>W%OKFCve16=)3h)ij1igRhNI(ISckw|Ie=A1 zM}zY`A?oR+W2a^qtT%kM1#gERezZ*Qx{MopEt{E3Skp1n;2*|d=!4J|)}V^hFW_+L z$mYd^abYxj3lGE5JD8Wy#5>W$Nn73&9PGRI7`$t|@BJlPdKTa}1G{EcJVBk-($5jQ zx6ac$&UHrM%_8COv~Z)+4EI)O(1_Q&BG&xd&2QD~XSR_D73q_bjeg5rmx~b85io^fytLdzRC7VjF4Z=Gmd+&PUk?1G9d z^S(sNu5wGqLc4BmD^b9o^VdtFgwD!mE;2h#;6oW*RY5M;Z;BG2B9&)cv+p4%J7eCy zh76TVMmWkQvQ_$4oRCR27Yoo{Po^cDc&-c2SZd(Ewvk=gShkBape`eHR{gaoVOymr^uA6L>-TA zq|@c1QMNZ#-*s}za-4RuI{>sQD67S+EUO-ra`t0LeMww%UxL}5smdvU5Y!$C3@Btx z;`&_|ppo?fSZfQDN*#EyHYhCzAAldLnaq@Y3&ke@KKT}a*)@N=glPedB#v^a#>Tg2 zxMC0vMP!70Xfqtu?kvm{5qm^0I12#2HgArtxRn5x&MKY^? z)AIYNd}$THjoRMmF&m%E&%U;%DBiXF9~lAEC=vOP8OCX$9ox6XQH}kfQ~mZnkpHIw zxTjJuI1r|-l6i^&92uP_<6q=2+bdGp=N#oPfA?wh{4|rc!3!na1b$l_=D^7s_?v^5 z8{5_iYU!*E=lt)g&rIqP+*s<4JO6v;-sjbpWXtc1%#5tg-IV+0R;z8{fr+F&uw)4r zel4&>;ficcE&M>#Xd)1ODfvh6Xjj7NZ1AeZt6pPSL*2w$Z@Kr*IU_)avvMM77}CXylU>D&c3wm3 zlZ3Z?qPpjj)mZjw59K_dCkQ)OmW;4LbsSK6!*>$V_>bflpK5$MAU9Chr_hSdIwvrfKB9#S@cuB{)~DQubG6ocK!$9;-!i=4v-;IH&2q_d36)9U>ZPcAn2kEc-?6c3`eEzw1Qwt^z zw{*&7edmHuL*(#ZA!?kt)tO6~G~q$Dz$?`mNnjvtd0(R+JD(r*v<;MvB=d!VY~?S# zT)Ue-;tc1%`d0;eih&^FI7(k$!T{GJ24Wq400yn$sX-=NCx1OsuG#;n_xwqC(syb`SN`miuXsQrh@~Cx2so>2!?8)nf`mc`^;Y*Qqj$j@A_O#4-vRImwKKBj=! zFn;%akR8Y)Jm&BuO~9wHxP@!iV8`#oT&O3^a?xlN#o|gvnE`t=`jCmR6DfO(^%#uH z3T=M00+;T|q&}hvjQ46dFCaIoYS$k1Q)O*j5*xFD9|3G=Zpz?O6h6JiLnK?K$dkL!uvXhvRn=3bi&avOHEexcuIe_+Iw!Bw8GPsM`Ae4fVg;sy$*#fi*s zucjj_$22bNfIK*30Df8TjSu|Gu*d+CB#Hnr&-)(fWBL%0y2 zYiokrLpdbal4`|jBDUk+Xt}+1&8d(KY1_-zJ+DTd+$LZ=_ZGA!gt`~bh##gfsVnkmo)QD6os3M3nAJ4 zW&_>L7QTD$Z9NZn<3swgj0@=nV|gz+dYaAaXXk5k@qNYac@9IPKRFJ&K4>iTenlZ1 z4Xms5hv1`;+tFAqH&W^d5Co`7yaJ6v@(a}SxT;`ZUDPlSG6xn-05BG#XqyY|mP zjB5;!h{b|TUN7!|+6Q*xXyvzrdr^HDkg`o3M)M)WG}}-FX$P<@`rkxrzcFe4pG|`z@j)Fc={ z58N>L6jI^4FxWZM@3VId_we2SZ?&z}1KMC)CV)_pzH?DA?_QbS|H*5^I)NzoTagI^ z2&VU7kSHE+n&+JA9lqoD+`q%Ie`!84|IVZTMy3A}?d1Q_t-pu|E6#PsKr17EQ5fu+*F?QD zLcV^Efp*~Uy!0Pl+m};3O6#-eMC(c#Tyg;V=abvCE~T_;1Fa?f)EMOTB$e=pfuWU! zfmZL*`+||0F<>5yOLuGhW-rEsE<>3&b`YB$f5-IGU1VR89VQs#xMKP&PJB)DlGk0z zJ^5U~YM7Y|kF&l$t|7+|LpK)eTw)6nc@|>_jUR?Ez>OK=yeusrCL%>=ytDBbFD7m{ zIR}OdxXR{I*^i`x!zYNtk~_uDPNuX+#~yIgGZ8`arXzJ@sYc3+`;xz*xe0XtZA>uw z-ObnTGfN-#U{;!t1qgod-D$kRK;9DXVjvgNFDKgCIC-*L?GF2pUnZGhIc`5VdB8;7 zYvwPh){MhQeG(7dsb048y7QZn03`+lMb)=e&E|XO7r+j|ZAUvp%t2Q%^Bkpj9g@(& zyVWOr#K*_pCqZw`OjVBKw;8-!ONybN0uUaztke`Xf-OBv`jYw-M^0nrbSLnzj2mX| zRWlFt>4ht-NYxNm0ONWm>jhIBuS|`rahvG7Y=QlwCaOzmPk z<9!UquwrG*DFy=<6j6w73N2wwq{76mP8_!8wi#tT1!{e7wx-xX@`d~0oBQ$UxPfTt zw6_tPUnz=^yKUwf8Hye$ZODoyB8yRUoW|@xoqZClCU{djWuk{)qkx*pKQUgggq}8< zCOG$G#&nO=-*Jc-lTs+*xmnobU4VnJ1G}a1A6y6ZYOIZZWH6#eO~Q~;lTGt!%Rem! zuT6zWC82&8r<>=J;)0t=vE^$PZbBe(kr@>=3u6DS>X;03&eC#ROjT=t@#IXHoJSVs za3sCs`ErHoIeXQT%SR>8#i{;+>zBg?SG*jq_-conNv+V)QfQWRw4~1|z2niNRZH~z zvxwkIN4|{ zKQYBLv%l%s9@ox+#)$)!9NTu&Zt%j7Gi+H`rd{HdLyE?p*Gr<_S?dzbdltjcU^Y9v zXm4>8?_EQR#xXF?D?+~ha6YW(Gom8$E%SAz~mxM4+%XK=kJTcEm7q1jOg-f}FvUN-B*c$y#J_OB}&I~)hWMXMYa z3MdCBdGpe=jy>K3A0%=e@+$iUCkM8Y$Ct|2Ylry5 zI1Ph|*LWre-oKI4%{(PsfkP_SSI!Uzg?^ckJ4ai2-JWQmwMQevjAnUSi>5|fIn5au z1<8i*&_9E}gh62OIK@8)Q-ueQT6ygvcSq=Vw3T}^vc4&M3GS#8=MX9Ja5%7N!*O|N z3pAk&^d*IfudO}>f*+icEq^LH-;?w4Hdx+AN~4s_i$-(cuo_MboBN_1m6F(b!F0^-BexLl&;eIUl(dCnK=3AfT&Cg5G zvpTU}m*QHYo!oQVxC3^kHw;n}fn`a#Em;8$Mu6YeeZwhkUsrB!( zM@T97%Wr;-hBW5UP!r-|?|kgtM!)f^eJ8c}&Pm7kyX9|?FTVWMS4x>U)SK3F`Lzdo zhu`}XZ#|tZ#yt8x7{D=RpEcZ2!oMGbuRuXDmExwg9RKs(;k$q1aKmT*H1U|lzZOi| z%C&P^wqW8v%dfCZV|>%*UvE!?1y2X9G+i!hj@nX zbx|C&lxM#653+_kLHno1KslPPGY07X zN*HK-K4Jic2_HC9uEJ;6!2SW%eKE>gi~>ZV<}<=l-Nn_X4HP0;In&a4{XkefVxXj7 ze4%*1t+~hG@421Vzt}(y=N56+W|6e!e=dX?Vga6s_ndq3@`QWB0_~1)p z9N(FW?{23nu=E=^i8QzXjUQ*@`DR>+qv>&EZU|ur1eehiH=2czg)fpdM>68Wh~}o& z@}W%mtw1{GjkgZEhRLdS3`gb;Dz+2n6W)#?OFx=Syq1n6KHZ&91Q_AQE$M?;$o*(K zochFS5__-#CZA+af$|j;Vw7hs{DO*0 zuJMR20xo(Qw=Xo$7lXVDW$4XtsiEoMV>?KR{0rEYM}@plrsFY8^JOtOp>q z5$9D?XGay$c~+Wr3CD0&}@ABU|o{HaJ(N?&{QK8t;yls!5RV1Flki+L6J8N0*Gskjb|0 zkJ#>E!03k71`D~lC`#MVT2fdy(?QlkkI@PPl(c7l50eJQX*njwbP{Mh&JH;j5<=-4 zFbjn_xh0rUEg?f2c|AzG(L$4BlsMBPCfReQy{VURVO@wPu%8leRqjPS6h(J&OVPMDm;&}u^i>`zzy3Uj?cIDW!W!{@|KEb2X8lQvK zD3*faQBJQiH{*84oxP*>2wIE{jFX^W5jdHg9NEK*nus(+91PzcGOjInbmjw1Y>S>H z8qP01`1ODqPM(I9!!zhK2#As=q1<`#F#ICJouA7#T|KM2ccX{#-b~YaVdG1IAhq>q z)MI^sP8crGaaRZRjmfkk;_pY6sU?#12hj9-AAs3{{s*2VOr~!ZTih2M&4ttA$8s2 zhl+O=svZgXak^;zAd-&7&eK|O@;G7uoFC7CVl*%wZX-Zw=m(2EHU%k)-1#L=MjS>i zIT%mensYs#SDu7va3*#fKsxv$UQbZ<7Q(iKnrBoTj!^gT=y{Bm`EW+0dpY&+@bg2wVY7K< z^Ic1=zLk290qMYCTFxD@+QLZ}8w;Cd;T^ll%WH9MSV1bv;_P|+O~tHW(!1~VM~}4k zA1hz{$-Ha77W~hCCxt%7dvt9nl;c38`<%5-@S6Xd{F2&UC<5&=c#^ zuiO*=y+@SOTk{1x;4k0;fA!_($O=E*RsND+8q&V{>K+48t@ru9<4)gDfBv2@c)v;WI@IbH z@WRn${{6ekwU0Yvkk2{G82k*K>yIaiKgZ-xzkO?;!?OP+475x9yDnj%7mV?IH$Tue zzx=(A^we_-8kfEiyKC3HzDv&E))gLQ@?TtM`23AzylD&g(H~09hyT<6zc04~OA{Q` zE?&H7XYt%0zWd-%5g>-4-?rnV<}{$Nj*KTsPy)gHH>L7vwWlx^OPu6-Vq{EMG$atc4V zz;_a4`2)4Vix{MmbMIsfR^#||=pMN`F3XtIxo1wpjQg4Iy<_}4!>k^d<99Qf5g5%+ zBR{4=IRHiX{ODofJjmJ&{>MyF(04k zJeig6dH9^UnP9@^mc6)MUmcRloZFhIOdTnUM~;W6w5JZRDO~Kqw@$J6s)kevp=!(( zU&sMJ=&m$2rPQ=p-|DBNvmvbHb3PSn-X#)+?S9i?q;W;d??27HOfhBO454*FoSdG0W&N!8%pv9Ah*n$Z^%fcmg1xShW`xe~D>sa`yh4FhR;L%6 zg;H(eEHY`(v}ZG9Zwq6Br6C`%1upnuXjIGkXAkn|LQ9dZm{*eT~G93d1vB={2aYXg!YZ8g@w z>!me;_-H_5@YJ|Y$~EOI%rt@xF+>6eTym?{l|j%!UX1n0;x^IbGBddaQnKli6)kdb z&QuGs8*^=|rggO)&M0fEkikF)TiX{((KD_r>W_y}Sy0(U0|#d=XEv5`J^F|ysf1r= zJ4qeL3}xJ0oM}b^XV~ygeSC-r(Vsgn!5GgbavpKlMdy8*KU0RC7M3tBp3ODq)di$? zI1r#I9qUT$fXEM0Z%&{CU(oWY&b}o(Hhw3^maC*o@-NtJ;2yhW4%1%%#M+QBPihJJ^dKRVV5hR&A5RJx^y%I9&6Ui)h3i0OgH3E^VpmLIZ+ zOH>nuF^V8A>3=A4evZc?cR-3x0K+HUbJ~GcD(a%Y8WT=+0kW)A_NU&1sAs>ip`9-r zmwc!uq-da?tecUTfO>NAg076!+mU6jEywf*Q~e+(=wOhH-jQeIN2eQP9&n`xD#b9y zB^z!%gn=WB&Z8YMMao3Cibt(9lo+Ea3wZD>45O24r8yfD+{(jKZHY*yXH7=r4^~Jv zuE$4XbL}x6ZB{10`b?^1da#p&UB9Les3+-pTzUg37eWl{T(U4RKyk6y5!|@$txAe8 z1Q!A}H`%%+zuEk<1<^tQ8;)s}5TM#?R_@N)eNGZ~xQByh`9{TB8{&CAv9E)ZBzlA- z(2t59wAq|_W2|vx_$+?CVa^zEqP^*WWVbBF$+LVLn2)w zjd?-P1F1jM7YNkzM6mn3fE84*(Q2-oWy%Y@c)rwy6Hgwh zp5+GFtcOg4r_W8T5XQ%8LAM*Ih-oaUytuBCJxMuvBaG2f(l=`Xo!S;f-5>l3 zd5N_@FQ+hI&vh#fc}k*NV=jTC?Rt|N`ne6Ix`i<0=z=yR`-mD!e;L_CwCaHg%8Abj zFP7f$EUlGWn`4kzJ=FW*aDnN+j%`vGxI(G#vkN7|@HHflGHin|;1BvG8~3K`EhuKd z0#MyTwd-FVjmQpzS0odz}_kM4u z$ouW1Ox>F;-aGbpbs0iS}ym`Tdj_aNZGFv)%ySk6iPe zTdt7ZfMklZ^R@T*;?E0)PHLEny2L}3eIN5Bu6?G`lrI;zJ-+OnTQ1)F%@9we?(udE zcAmYsn)wn=a^J^%9|r99P8bM@mwEAJ+%c%69bJ0W-ovZE%VDRHcROibD`Ps{tTqcd zJ~_3ll3e@WY@=vrNl;71TU_8@qpki0`qA$geV{E8n(-!Dc~7x(z@^PB?|0Apq17w* z82kcl^)FzsXY--<4Fhui1m$&UOTgVbtIG0^_^Z+@1vF|cao91}WQdWXoKzFm%a zJ)cd}9Sgqf=O|{Z*v-Ai<|6=M%YCxTfyu@=5a zcI^3hw1I85p*=ekq#S#EL%*~SZSWNf;zd|J?|X0f)|Mn;{oY19vya#^PiD5sUACbx zejSd0+cFctiD{iaefl0~YJN;UC<+@4VdmOdXI(>xb+yZfAmOX6bMD|^3oS(3SVYSB zMe{zmA}h?^r3|?BGhFv@al7^5&C|Z)c@xP#*s`WtRGcl!XX$NA(C7I(S|aNOzYEf zJ~>2Uh?;}0Vr&%nm;^(v+=>GNRNjjIP9)|6kxJ(!XoCg-yP%CHz_kq?1$w?>Vh)$l zj4noI#w_h0?8z6^xr~T7`^p<1|{?F zVW78DO)hewf`?{OX@bmLG96b}8^{50v5!2Fe!p5t%VpJC2n>Yyz!; z?Oe^3ugALV$1)yAsR*dA$D(r0;3vq3rSDl%IbXxWQ1Tdz=IZ=`!?rtgqNmImwV=aG zp|l-Cjp0TZD4K3zmI*p#j>bJuKUx5WCm!2Dl!XUJog8GVQ{zO&=R#+-ZfuvZbkUWZ zDg{e=hSx(GtnP4nATDGcoCeoG+wZSuLc+Q9g0vNBZ@y>FC00+fHGZVl} zl%a@3Kjb_5>bl0e(mB^?Gj2RMGkQk{UHjTQFYI|J3Z78A6ne+@6))m4%Pprh&_WO63~3CVQU)46R7$j&3bE-5el(1!9fUu}=La-|jcsImep@ z{H7>iohRzrp!1EVu4v=ny@}HdoL6ie?RgkDW6WWbQ#~zHvd`stcwjV~%M)@Bd^>{5 z4kdTFcv}MTq3q$d?CN*&%xNR@*)oYus$5HSw8A3 zrZ5eD-^G)IMP6P_XqO8KDfiCQ0ZQ1IYvl9Bnctws>xsNIq*O-Ya^omMDMSQIF+%79 zLl+-n7uMi%vE?cKqx(g9lL@BRLpRw!O;5&Staa z|11B@lz;XD&77j_@RafyXZx1p22T?5sOk$C_6*+1TKL9X^cG5_8Z|9@=$ z8fgm2+P?aydpyzZ>b2c3Dn(@d&Ua=hrM((27i(;_$PZNA7Fnt24n^X&d(*x z7#HivnCy+K9K4cf9iQ2eDXa@}@wr0M3fJI+4Gt6e{ti>*+eWClI%s+m(B=%7?+Cshf(71sq*}>;gxq z2Bcog=x5_0%)$uoglB$oVjX}@QLBW4D;dzyqng+7e*tHri+(z^6kXLHQp{HB|WFs<}Q@7Y*FF+cP?_YSWK2Qx7j4 ziFL0vi)cjKj_}}ACj%F8UNm)8tI$%3?gg-!9XSut?Ld_ugM=p_xcCs@?)4+$?C4u6 zIl7K!MA;VF%p;41#HvoU6_ULq3=p9>*n(_ldebR;Ruz-W1IVpS6Q{l>HpsD7BD17s zTxe&|2am-$1{B$_vJYwlTLthxiBh{TCg7|4lu4<)FqE?x zclE6UfCn`046PfommvOlw^$*#5YX2iDCB%}UI%rdQvV^KjgDEnJu{b)(b4g~U=Ocle24`_i-1SWVw?*cz zo6D3|4or1P^nN$ju7NvWvlPiD@|6yxH&|G3o@K04TuQXX*~3~ai~!zV1*lkyPFG&J zzAm6L4a;bzq}S)s9A@5B&XFSqf>iWB57acoBG#h&y@@9t7p)v1;-7ixWk2zuSPS+h z4A*Hr4bggEfQif;>d~oiw z@t7*q$IIzF__a1<6U(){92ayyu61){J;1Hh0r|26-D7H!o3%a#Yxv(xaLKvo9z1vu z3m1`OXA`oOo-o`RjL`Xd;zC##lqfLB!Wz;bUB^z``G9iL!>BS-xj|Gryt!T^GsGM;>-_qx)ZfghvMZce6d1pBwXM(D^CoE_{!d?35zW@!N`k za@y$p-A;7=3-A$ILOR#AuUA|%=w(Eue}&PR-8w;BCui%Yec7VJWQ#HvPhBT~bL#b% zzq;)j*M9Szhb~^YcEIoc-M;6wi%SnRgU&kfe08?>FOazv&yd~38wMK!AVFLR#vKDu zwl847TETru_ZZ#7_Z{`ZKsDdb6_>4(y}$hG%ddoij!##c=u+u&>P&CJrO}Ee`brcn zR5ro7#Mc%NUEJ#(cF$8+xU{b;TyRP2=eEx$gUc-pK;TPZAS816$fDB&I|hP({^rf+ zAoFF$VCSE!gq^Ry!|r+N_ZSE+`tJ9C`G+5U{P){`b30p{SN6xy6(R|e4e$J$!zYja z9iD6xd9%qSo$ot${Rs`7;4jLX|F5sv*ABU~4Q5sfL}%W~;O_z0-0*&I)yvm2Rjo6F^YyJ&5nWjhdkhvoU(zo z-RNxXU4V#uKMnnRA6)J&;@c!oE=}#38^80?ca82ES@#;;zXyX`z8~&*LrrsCL4wu7 z4THo}*B<{H2Ehhry1;}@KV0r8KKZnCu5)fN#@+qoTbTDb@*Ht{?Af@ZPjFU*;EP9& z$l<%n*DL+{)}gde$8IP9C#`bguarKX_-Epw!O8 zqzg7Ml?sDS3@f~`oqCC%S1D@6Y@R_G&Go`TFhjR5%tHUdWQXw zd_s^J)7Ue^IFF*_opF!{lXq=pakoP|_X|X-`oN7#adjgCwL%G2;BXpuYS}VTXVyVg zM}F4$hI&jEm|DPdmyz$$jM>S^7hSwC-RC4Y82XH`roArp(jx3kf=)8k*pf}OUr}dirOGxGVds;{) zrn_+Uw3x%zJZ`R>qd6ud*c;SS2Y7XX`m*7qZ95m?ORlbVOz-IqIIe?-X0?9wC{DJ_ z@=fn&&LE#Xtx40UB$ZH2Uay^GcwmUZcwzu|Hivf*3Mmlbn3`kAI;S^V8E213KYI?? zu6ilsVM6oS+^+3qL2}}{b_YK{Nc0$DoDqyfL&F=Ry0u(PB zVJed#pv;Z~+d_eBMqi#vXMsg$VJX*+x8ITWSWb+F<#6q6w=S%6lszMy1qU7;R|N9f z^TfGhqIQ68k`V^PPbOoNoRG4Z0&VRq{`4cRA+(ovf*)v>rh$la@x<1UkrHd`T;X{Z zug3NWmNQ~Z0q;_qQCDb~nx&ML93X4Cf1lZrA!=@yJ;}t$SPYWk5IYavtP@0&`b+(z zmq*)r0z@3av6c{SqG8~kc4x>Q?S(;UTYfea?fVT}%1*Hu#n=c_kd2{X^##JgN#BPK zaS0UWUpWj?Z%YCRkuFmehtxMT${G!P?}r0&=b+Wp{e>tWYABkswKO15Hb$9zB2~M# zuflj-L)qE@ZTxUV^d8Ff1V=esBzY0(d{gL2@$gP7Xi3vv)`|8jf$!ts1Flsx#Z4WYtCy9f3=`Sg(X!)#Z}|FPkYyN)?m`l1DTT71TWFvI!t=URP7Nr0Z_#%# z@;Fcm0(Rl9CbqC zp_{c^R~~&R-F;<2q2FolFxZ?lbTaS{=niOUzAaj+z$%O{S6(u0=~Jw2&G_|4=8u&G zI~nY8^S{oJ554cv$)Q)8yBu#cv|0xE&xdTMc#3;7+C`oue{YZ&#%lx&cobbI3 zpgG_M{`%Lu|Hs?fNuG3pC2j5;NrzD{0B0A#tK@#ESJZa~>53m;@}4qdfG#lcLDBLq zvty9>?qU^l?VN8>6MB|*dx!7&eUP0b-AVaT{jpj0-Q>Ujn)L$CUE3w!_jB0(UeCE~ zP5UR}!KT$5)_~r;fu=_?UOTGad=7f=r)*zDC=soM+-wG1UhxpsuIE83SbzGla{lu_ z*WPXRXC#em8H02-87Jo@jC4ZbchS}#gh7s^RUc99oFh(&pML$PA1miiKGEI~VW9Od z@#uHW?poPVTVyHmA2uRybx+N}Ev*{~4^i!DrA_%l#Z$(&g6Fj|9LS@qM9HUT48CQL zhsMnh8gfDDxX->iP-@C3O3z?Aa`z=vYyXPmdtv*aQL#a%mG}#Wb zVTwSLA1tdCjib((i9UeSg506{Z-9=L*Vc@GT=0$4EbpdLa>N%-Fz`%%;qsAqP2gTmV4Zvu19feMw># z@+-M~P!c7iu*F&!gpq(WatyU|p)T#U^BjcM=r?*O$})mI+^3?8ZP{rKQ{q>ROuLZP z(&ECeA5@5D*`ngXlabMqX)2J>+ORXca|bxRCc`EPwqZ6ob_*R``>I{p?#Dk5rS9N9gd{cV%xNAhK zUdW{vMy8-b*~DaJsNmV$*|i|M9~bnv>UL85F>8-(Peao{;8dtMNvR@TNQznWCxJQO*eDUe?4S(p&X+RU30TsqmvZ1+?4~ja&vyJ>dJAEd`|F^ms}2Cl5LFN{2Ra9Ff!qE#|Q?_kV8{ExWj2(qanssIWO*jM)!lA-QfzD z#ZSWHQdF*qYrLXrW8j5Wv0FT%g&$QE>5TH4slhNHdd=%1?H*^%PX438rcYUlW6wB! z%i@nX-4X>dHfo$y^K`0VE6-z=fRAn(JW^Q;lJJsa#hW*$ef+t9w0`%6JGYoUJ^pp|a~~K?t2Q z4IVC`b1dUf1PeT5;)CNZ1bAg6e;PtSv%SN&Ar+FGI|-F$iy>z)Yq;yPJ{`L+NUr&`=3ctU8@l2!p zo|`a0%;4)izdi3gT=vf2-eHdzXy3q_HyMLHRQAq$zlS~1=)UuGl_}V*v`#m`HrQzAT}oUw@*Rm<|Kf|ho}jV!Q=7*&*lFi`3|P{mU$i!o z*Au?_atY%&!J%@!oF{CsHKf~z-ha16WdfUsNK z6wm-D9&4;)PdUdVhEJD1a=Yo1Bhi4I6PrA3U{;YhK-@>L;6lT3 zfs1qpN2MF5m`cUOnoDz2L%eKC!8JR^w%lirtIlD@^HpW`z*Hxv75G4$o)8cl96o2= z%<2S3KbJLAA+mt?6%LVY1B-}^QH7FR^)be!d&2|-zu(XTws2W*f&~pa1*7Hur3BAV z#A}G?qBfedBhc$A-%?EL!hD4-4)T2Y=f$n_B*Fy=25*nPg!T=6K4^jBse-NgvuW){9mH z77Bm|p}H9x2GAXhYOh*}BcR_%9CegJvq`aWE-Bsue8CrqA$QHZ%+=1$6WmqgX(Oqb zvqBOoH#S!{k^UKZAkw3!Y?6UAaT*$;d+9&MvYs}2GllBLs?+9H427pb!dcv9r9XBJlh|uH%*{w^(V0!jb59PA zOw*8A&?}(asL{pGIXSm4K={_Q>oZv1f|+^ z&eP01si-vqbv!&@(*ae0yys9zSVvmM6TdUvf|bpjt&S`-B6ifMw&`<_?qz;fch0>t#vW6r@r`NG)n35k|g#drm9OtI%3KkV?f3`*G?c%x)%9d%few!(xamKlv z_Qu5fgUp-?V?9DS zA-Fydc42X79L8!5$V3cqzynKb^JwS59+%;QpAYZY+EpFkxJpA0$}l(!2L*-Eqrf=- zKEvwOCPkj9>DfSyvrBy6HO_e$*$=IBfEpha4l{a!#zx@Z(<^b|$^}v+G%;mIAtV&O z*D$i6qAj%ZD4Q<<8vHGG6_Eh4Fn3FxdUjc_B{Vcr<%RU8w{b0svGW{7=29on5y?2+ z6Xb(RSL{OZ16QAHsOu8nVCRQ}2*bnbM-u}&-};$&Y&xn3{H4`y@*%i0-NVlZ<&{j} z)g#yk8#NXmU86MEDXkQ=1ie_XV>r%d;Bh!!4&^1m`7DPrveXm8T&V;2&B{Y2QmbM5 zfbLJRxfP>92#b$-gt{(Az2x|Qv5FjNf~B2!I3FsOKd{FF-U;xoa2IH41L4fWs*FC$ zxxLQT&A-EU@ORkcu+z>PZTvo|@nH*3@n1ug>!tR@NP=6l*eAg6^nCOA&!9uHp7hV( z{P+JMZg_^--r?`F+4uRy^US67Ni+H9@b~)@0^2Tem8@OkyZv3e#})6(uV4)Sr5p3_ z{-^(Y+9#ek;l=;>ysl7y>?PJ-?+-Pi-ldg%->>~CQti82w!-eiceiYI27L9roJ?E( z7!0(kpv$lxvAbpO80;|lcerT();Pn5yfNDe<-7mGIpO+Re~?`JPWA89{4=@s=WX%Z z`L#AAYCY4^BJ4HZdFJlma>FP70nfk>!RfvvVe&o8F854l_!Ks(%KPjUfI?P4I8g8@q#36AoiAeW>A z-CJWV`8$c(1vGtCC__f+w9ju2>kwZsj2t z2Z52M5*N=HYA=u-4c+q)eHl@4CHezhw2#W%u z21xK~`ra63k$DFna?EN3Ndz!Znf-xUwUi!>)4oB`n;oIfIhp$MFqW4p4uPpgd8q5i zLAk8(xPcMQtOa()LOzIS;*PW_kn|IcJtq0&r4(dJvD6TB5OU0FTHzh(5S}M|YJ~_B z-qr)mWYqZbl;JWDur-Jl46wSy+QgqdGnxq49W2X-9u#W z9C^7t#CS(BRF*!ejAJ)(EyNv!x-|nU2I$wSMwUVn3$+L(RQQOJyaE=hCx?_bD+tt% zrp*gS$~i%3C1d7vx5vC*p;lOc@SqZBWcb0WvnPtA4Vmr^rROm|+L0XRiFm6cv*}82 zgOJtwfU!--vu$xDw)sShaAgB?+4|xfavbpUb3T1aYl8M@QCH!N{c zHZP~4qZ*zLB|mIK1*c?}#+TVovl_wCi#4bLC{*`>y+*>tHKTS^U#KCtpZuGtl9M-pK^YuxL&Pa$ zg28`yfOUF6Jh}6b%}HJzIFDv9qME4fiR#k~t%p)3RcBmK4DJ|W;K6XO@yb?n%eZt; z;VC8!ZNlkvWjL-t*&$;1&>=wpIau4at_M`89qI$(>Tp@nhrm8>UqT~4m;$@#d(WHO zv>q`iK3%1^9x)Vby1=VD9goj~n-0Ri(DbaE4z&NEC!Lp~GxYUk9!~YVM&5TTBYmu= za+!zq)SGL|)@{#6W$gQvvYqn`bXs2db#%Ss<#pkS;)zH@92rQ1k`G zV;Z1yhuizc)nF89(Ye2rgRfV{Sv|zAuKYvYS9UEqHhm}`*5x>N{=_zb5NI*&5uoc3 zY$N?SjHV|Kqagf2w#SbN^2;$K`}PI})(eawBpDZv59V}QNdeE84I1HCSMdxl-SC2I zSC$aRD>~g62g$65j-pZaMtcV}NHSrwlf`JESUw)IjSIv}uI-+}5+K9Z6CWlXp-_T0 zJZ&W3k!*q1KcFFwSIX*-W()%$S1!D+4h-C~Ei||s4{CZ4J<1qWr-l2%GRa^V0j4Pi z;%jQ)@yV2KNwzhfA;1@kd7nX9U-eM4LTn4c3~K1h>lKCg(R9PWt}(XQM`i~FNnd}I zTM#>$-8P&5d+mGr{hy^QNcp=t=RLwEYhsaMejq6f5xTRVm2)*fbUWM)tC zFVco)0baNyNv!ys$izluSiniDQ@Ln|_?tJ6JHh2=ukKfZnYX|CmE3~u7$^^6pxx3* z9#zHh2Z3L|RPqkf9A3YSfhY~RmCJq3iY>p+h)Q&jcxSi;W32#uq;Xua@=yD=l@EY# z9M-YR5peQykd}+x?FiXqUHAC`3EsC0ngFmRl+1*Ch>l;^gX*d*9QYDA@bG+-BR; z{hhXnd8PWvF7Pb{yTm{G_`g85bp|ty-+UDP+vC2(rLDB{AHQEGa@9-aQ?|ZnF zLz2R0*Eo{UiNl?^xyWzabF%YlRKi0keliTq*E5z9-w1`y};k{sJ&a=w}Qhb8(LWK|GagWix!4 znHNIg!1P6D24Ccfx1>Y&MqwRqZC3;57YP{wFyVGi2RFkNHhe0@iX-#L?DJ}w6SY-W#zS2-@aY=pZRE}?&oS56>RLgk_mByt@7n5E zb^ho%O~e$SqU)(al#w48P>AC`QCk@+P=d&QW*mN9*#y4Af#)^Qq@tG{;E?P>e?b0+#~S>$v?h&`W=K>fvX@VVw;AUPe*3?(!EHT zGiOaj$nWH`z1OEc8Bs$wL-%V4-29tEnKR#q=RF4E2U;hT%fgBA2Df9kstMkCw61Z) z?1Qs3qn+8VUD}C>is@ajlWUfWNs57JvC{%+1DA~%{od1(8NVupy_oTdpEL@L951k zIXHynJcR7!daOP952mJRG*t71QEg#?(noUPh4ragN{%gvQZdUH-uS~CvzKx zs6#xtv=HkHKWJq_8uE*TU6>zl$$`tLw5KNEt<KTa&QBsGZy%@u^n^o4aItTM;nhv;h@3Js zb4RMm2c!DHiKirl%ylQP&Z)AU)g#3F0PS4DN~;oD67cL+*CvS)k%3mS_aNu4jH0rR zRnOR`+yk9_n3@ONlWNHVj8Yr#avLnTP0D?c%cXm%TFWfyu3H`pz8-14Nh5)y$4yXs zuy4B?4R1(zlm}-lD?R1tSghn&fZ-T>+b>$GY6`by>Vh(S**LWU*d?6g$_6Q)-I?vQ zd72IS$RN8vTAGo_#HP06Hg`# zA+=~=&jsBO&NX_^niFSl2Xk=&iYb^*YgiCvAi&rnf2GaEY`G|>&1~@9`>w4QxGv?8 zHL3F^hwKW+X=IlHY6RFOZl{$6L^KI-a@4Ut8{76_B-K$AB%D$fP#qnxa7E>HZv^9V z_pr@9Ve8tAN9{Yy5e;fc579^4w=9SFmCv_ZFW8r&;oHl0ZMxxVy3w0u7>Y|@@TKpK z?+nFwu06k-4a~j-CqkJbsXZ|TcI%vtNDD!Ax9jCn>gLP3GA5+X zo2dTb-xg4|WYIstV-s)BrUK`(9HJOOeTii_r)b9_)m2xAD51$zvVH z3(IG8Iu9m?(f6#?z-G67J-d_Pt&xBBc}WRgME@NAj=zXnee@;yw2dtt-&1dAM&mk_ zZRG~^pfMg|4+r}~kJ+I=J@8iZU2h)n<$^!j_|VUD|H!<44oZIF%e+qabbEJKcmlsh zzFZFMKUN~_WS-j@`lIh({7SR?FTVKv^S5Xs)=5Zj-`-!}z#Gq(Xt#H-A6@V*;jg|B z<@3cCZ{ECx2P+KFJ9T$SgPX3GN*C;X`q1A8zZLv%kppe^zuwzf_I)4R`}g)w>jLzL zY+XM^RfNIb*5Y|T_JQELf5$-O%AL#q_7#+pF+P1$o*?r+_>H5zak`=Z%^w==(SAym zzw3wo_~_%0;c58FCTgEg!^gavo)r6AdPEzWPj`)Lub@;#xk@Jc+i!>eLlmo4ZC(K- z)iI|fPi-MnIN0%fzv@(cr@gn)r4+3(P!ewXuyI>oIg?{q@j$G^#T zQYEla@wp9@+uOHU{#+|QADa7z#Cs6~m6zLM>xG=tBL?1B2BNLU5E+vXRkX-wA6L`$ z9sZ7AZyd|)d}8pshj?;xZ{o<@7r%@+tSWnGNRtHpz9p?*c1u%DarOWn)%?E5;dHW<~m1wXBHSnh}i*+ z-u2OtJ=&h?vpDTu?8dh0Pwi?;&#x(`8LA#FZ)^h*IIgj*)>x*j2o z8(ibkluzpym_&}TiUHX{>(ahyf41h>BK~v%^O~tOX|@JYEH@CPZMj4H$b)ZZz(FV{ zi1NthR(Dtv%o3F67#uF~Z^Ka_-A8E<=dD^Oh0k-RRiI&#blFF-qiLphODfV!C58zgH_MCvfV4 z1oGDEjb{*{GK(1oTo_~LrtDb~xOOuel@EuO&A}ZU(9TXhLUvG%mFINjwyJwuElmyG zH#e}|CZ;Fwn;T1=)of}Wys8(jY>R@oK+|~SE)%f@Etuyho*-(%1&FX;aO;geq&5=Y zdjM{*)t+N00*-7{on%zsM{}aaGLk$F$)#JsFJF#Z;}V{Vm5pqd@M)I~lr43ZjOSO% zZe5>~dV#ZX2{Pi*B}2`8a_FHK`#gE#29gE+2@cU*Z?M(7(36ETj`I>I7Nt|dX#q21 z7S>g0WqUS(`3)QJqYT=UGxP)-gRl1f?IKreV!fMlyPA zWYh+jry~Nu27iyOmAl>SuFNw}PtG1Bn{ni96LPHK7NX(vjw$q57<7?G<8WE*g-6{a ze`F)5;Jks7O$TcaHxQZuLc#HvUT;~(t23n<80J5wh;z%@8@cZ&_?zpN2FEV?o9ti$ zwyN+Hei!d9WJS^7Os8yyH%E|@;*)(0;fjOi0ORx=-alLNWC1~n(`)<2{y&Rb>#^k0_CzhE6_~ANJfOOoT>Hv+?zrk>zKU7N84=v z5!rRBH#?kGO^~4|9DL4{hryIZ8OI`8x|IdfwZ%O@6p=W3r|FGK>i|^TY$hy{ z)5kFDA@;H9?X&#!AtYbkyX~>rseN3st^UQuxG9d#_Pts6{G`!s)X0qH!p&9lMbzHyJ;TZ~h)~Sxl&v@C zS)Y{E_S{v>g=B0ZvbXqqy1>wqJ2=v-f0O^t&S#e;c!KUR!W!*yf2S)4zD>r;!2(h; zj!Dtxls`n?C@&bMQS7`nG*eTs%!xg(l1q;)K+*8nuCU2QZYT*0(Fuh|Yx#QioLQm1 z%%;F97Tuh(!v>=FbvB4U41S`z{p|C6QYf+SgvI~g{6fAX7u^{-x{ z8wJ}-*z=W+wr}A=@9-z~PmV$6-)Ft#lK*DAIFzWk#2{`(l&_eL3u52}01Kl$vpEcQNm#^4@ubH5J* zq{lz=hF=$XZMtSL&Uou_17q6F-8wS@b>uY}C|W}Kx>|2d<@C}|GYTRTOU$MbP?~9o z0OT{kR8=2iv92tiFg&+>bgCnFb>(P~VYD{X zVTSf|=8&3@M#E?6lbR2#6A6Z$5X~rPiX@{D5qB6*^SejVF0Cp6R?3-cH)(lp_uSQ; z85GYam->If&IQOJ-@8WfHOtbqBeP^gDNy3bV=1zqag)Z-$cPerHf0|PEh))S787w` zRKqEs#KrW`Kjf~x%S7U@oxHoDMSy32x_eKI)Ii8FNPhtNGL!3ly)b%g&xoje4@E_6 zUwao#bmNf}8h(t1i40R~c^smdRidcm0hg}aF6tP;VzPBt<1%B#-e(tH@hg`g8ICjI zc2|Mh3|e8B$Ex|{PoQIJf!x`Gs(v!UuI5o{P3^dDkUjh{MX%_0MlrZ@g-E%lV5*el z2DYd?k&mTin8(U9YWX9VIJu7|y9JkVTxti}YePmQrIi_MOoD0zIG$-`6ccz4!JZSK zq@f%xs(jb~#OGNSA1D8K$*2J<#Z*sX+L^pdGeR?u)l6SQARzq0T)bZVg#N3@o29K$ z%Z6mkbQs&QV-s~ZBic>7)X)NtJ-WBj0_@&o_#XNgvZ3$@kPD^oyvEYPmiiBa+&u=w z6`x{(aNWI&I=s3RNu%u1sL%V2qkV%r#r1c6+B?lD?u~qPne*J69GJ^>=u=eX4Kt8e z`Cs~G!MElQ{+}y*p8m*~pT9QdcmC_hz_Ec9ieMe2>f(Yo9`ePnNO=Qu4xjUmc3?Hm z1nx0L=QZB+tkc#cAeetz8O-lZGKJ6}A(d7WIeytb(CjuPG)3y4b~rCm7h;{SjMb~8 z9}cc}wMum?{uIXf0)3mKb^77!Jg+KOI$n}mfOZx7IE^`V^tES%vg1l?&B$OmzLEM? z(L->2`Ev2qYFPa}5>M_H8$llsguF@j-{KZbxJ!+jCX&hUklg8(;A)L>g9RFkCJ|h= z!j?;>fK^OiHsJVjK$pK|7Y&~UM5-6 zJHLp`$Ra)dfyKIz2il)+WaZoSzNhfPtzKj~^^3hC9tx)Y6>H5S`rlbQkv5Yj3J4_rX;uKp+y|IKjRzvEl z!OiuQBESqYbD6k++K{*BK#9 z;owwguC8iZam5(K*UpsKOfY!GTp#%X59?l)*aphE*1}pND*L&JxT*uq==2!+e6BlX z&x|tj8@Yjnkto-k>W@(?O6Uz7vE|GNIT7!?D-v3DdsK4O!1~7UpzOi)R)gTD7K<+H ztqCY4s`f%>NGyR`p;)~^_apl&O~p1#=PIKS@!^Eytm>GwFf%%amX%b4(?>6}9^9oz zlA3H(G0#*gUlo2Nic4^Dh@N2g3i}G;&5@BHncJ9Dwr?bt-%e2T;TNiX?ckc7YPHStxoLmp5mC{E)_j}qu zf)Czm~HWmVxQb?b6u9c4|j%ZskM2F0geO>~4+)!DwgthTLCz_hBHj&vgIHys+X<|?$ze+Y1#)R z$CuC4C9>ZAl6LD z|MJ7n#kVB7)ICp=n5*RW`t@f&)(NG}PxP_dnI+|MuS8+e^F(Dy_c!-U?YT+lT&a(5 zAP}{8%O&f)R;sWd&?%r53PEr?v-BjVocjkrAj+utiJt#-J#F-+bU*u!PFLmAML2G! zjPj|Z+j*!JBArpv38{Pa-S5h-XQk$Dek*NN?wRflh3$E!-inI*Q|>m@X`_1xbizzI ztI9VBbSh~(52aRdPO;Ky@w<*px zcgYai(K7!SKc_cjGecT~>@q8bSTnWN24g^DCzFz!tFsYy5#kv;`_aWYFbL}>gea0k zbo&@Fvl?LNW}@ZIz|Ru1EzH1X3ug0y0J!n%_yx>bufyYmiwM1>PyUSgbp)~E-bPKG zP(R?r@z}JE4=u}PNHUcOGl*E^w`WPv2p8tGQXAUQ>CjQ-BB7W>v_@Z-m;uKXkCV$c zQt1{t>*id_zg6FC#m!ZIn;Qlxz~)!5q|y|bD6MAf?2)G%N4fKYNp9PcO=j6vyXA?8 zn(bIzCt4&YsfhTrG3!3RjpU7(M$AG&Xq{Ww@}uaRDrb2u?qPsJXos$~Jwei0jR9`FnnI!NUJE|K+>X08GB3Kxzx zxN_1=EsU=2xv3mo)PaWkNeg-f0r3t2VF{tRDDfClkF*i*Lg6N#=tcV}S8Z&W#mAwp z=QG_=HDyJuW$b|pIXD)LX-Yz;dy*85Ot{^Qxya^~Y8J6eP4dl0#d2d%qjg^c>WJT* zpS6AjU`&*i6jwz>JW)NMB%ay>C!PF>cebjpLt~3za%9HNc zkx>9XUQ$s3rPMZ31IyATo9MIX3M=Y>Dpj>I^X$_M!Zf=+x>k1fD@EmqmjCp_OVvwrO_82)QWm$Z@W28(iM3Fc!e&iHWlz8c3w^6LLKsM`h+LI zPt?_*@<11Y9Z8N1pcKPqmw#_Rr=)OiQ_(yr;!|E)&*?+1cu9{Uc?rjEuSz6Re}>7Z;) zF9|hqspH8%0=jm>iJEFxFFbJMm-OKEK`OZ|@MXFnDW;>-@e8B?x|b^ag<#D}krPVp zkW%B0IS2Tn_5o)aM7Z(19tZ1bh(eG1Kt;eHeAsauC-k0+jQQfKex3)zH5PLXcTqpb zc{XHzjMEZ=?|1z)hv>L;5xibZz7h5Cb zllxnyVxDS6|-XaPcb5JtgG)cPhfu_TxfeJcBGZhmbFU&yn9Tv*x!__nw#3%Nu6uo~`!xy16X$ z%zFr$AJ{YZyX?F1mGF`1EB|`cx@Yc}`Q#-tgwsYE6joEl-~R#Ul78?T&p!IyC;EST zv!k!!9WhDiqp#2GeZDuYHE0*B)DU6QA7$RDr!iOV9$}g*-`R?Fzu~!h8vYyFR|s;X zza9v5E6crl%3pTxx0zRp(PrDQ?@iy_KU9AT!FmP@roOLHUk5=$_lxwx!3`GmX5qlB z$)lf?MsVXDvR%4dw7@VB3=Gu(#{j;}5|+6&CVA^@3up+7Z%srMUgix`$QiE5M7nV> z)^(bW#7nhPnz73eDQIB+-?_@wulQQcFq4bEcqDKmfp5tre5srgyG|ujmT8`fVoZtzORZo6G?(t!Cc223dvV zXaYUrSWs=ZLgZ~eoF<-`pKz5xy{-#|USZcgs~mz*vN=t@3&ol-#yk_Bowi!om5xQV zw?;zpqxI(OaE_z~78k=HjsY%=Ko|;lSgbf73yZ9TrAsyFxfVu^by5$tnaz?lBR86q ztosyv(P@I<6P8?O=*b^_icO#_0e7;ZB@KCV zn2CvV?VXXT8d!`3x9%*AbZvWF?i3A;B5>rF7ng$^+(hUpn_wBqjA4N`FO(IBf`$O; zj{EXJ*{{R7xD>HK!W@i*NKUAB{)ALucQo%>1C~8daZ-{8sACv&su@&4{-7Re;k;%H zOhJWcK}%XwS0r!OSZOEMX56>Dvgy`=BD6EKoSkQU7~)V(oGIeeZZR~Yxqj(LgApZ+ z>}5Ta7B^JF9<%AD!}}jqQ)G?%gf2=;edR*W*kghS&=P~CH~l45A%>a4UBR4=RN@dB zEE{!eq}>>OBA70J44Eh6c0BP(Ya2<@Uc7JZ^bTE>;~kk~AgsOc~+j?X{<%rc9pSRCV8J(ZWJ@%^(;3wG`1+shKblGa3vaYFNJ)hJ} zL2{r_EoqS>?|HPb=x2YnUgTD<)^Zn~0tta$gR!Qe99H4AgMHJTOgDSFbI&0j*>f>g zmy}$$(?<*#-IfwDaL(x^YC+gK`;~HRbJU!oa$QxzgZ~;{`n1^}>uWeDQ6S+te2#@> zCXc#L)}tGU=HnIiXMG#7uaTE7sM z=GKt-MvVx42HE4Zk=xJL+j*oa+c|wim#0l<=%ZsFv2$D|_X@RWnCTw~&sGNj9}k|B zX}-S<2sUzy0?-7FXJ-|8p9`|3>xl~*Og+Ke=?wsDWp=q@-hH>^B7b^ERVzGWt=jpD z%K=H;Oc;nRvV`o-*v(@)cBgWIh+K(tqdBHAHQYJLEwc=)Wjc^fnN#Ri*JkD`ax!I zx*elD6-_fhoYqdZ=xvj97)|49@8u7*ng)X0VqSK70Ci5N8s-Eb;FCX^qhy4rN1AX= zpGl46J#RG^oJGCdaeAzteY0~(FSqbQJ%*07k&$@F%)}Oc&?~VHWr$2n| zIEkZEMmme+UV{}PJ@VD-AAbJHC!c+mPa)mzmV7uhlux_JM|VqVM>_AtvIy6<*_U5( z(CPJx!EEbt$^MrlkZqsvehr-=kQ_wP0}BVQ)Zw2e5a6HhrBu#@%aD+jAOL``K)}%p zcf+Ap^a{`f!Iuw``0pW*S-aR@eENdZK(AkH=Z~as=aY0oNN181PG;%1b3_{2%vxo3`{j*reGJ<}JBRFG%xci!x1 zJDrI7)>Z?*i2iXte|4)tiST%lPY30~N_NM(i%TCyefj)L`pyeNXunu(vbz8KP%y-s zkb8axfquV1u!4l_j{BVc|A#=?uChF~vsVZ?rSUfy*FL||3*OvHaRc`k)b8ux4Fn%$ zst8{uJ$U^xq<x$@!2)>JbLR@P`pR_#udqCW`qemfH$6eg!hUSSDFJ{D!93$J*v=i3vBGN%S5kz%Iu`J{0vTrtqAPGMz@jbOT9hPKrBN{h zq7JyJiHY8|vPBBmfoy=}2^WTIJ5GbTOAT5aLQ!*z)>1&P2ykJr^cj(^7Pb3X&jsQ^ z>>8)N?Tzlrp*C(fgncnhY`ffY609?|7kN|@J-SR=4fGGDTLeOnc`>b$v1W6?P=ZcO zq42Q_`=*(d?qZU3Zvzd*j=163ard|ZzaY49yL0UVfIas z-eG2uyw^{d+5-wU`$7|@07pp~LhsJjTtm9jboE5VXhfiQoFj-*%DE@p%=ufU8yJ-8 zi5gT>M9#rCzxw2k>~9nM!crWy*l=QXJaUy>8m8gkxk7bkff@oOC|hV|$nvj>s^#8E zZl~CH`?2OA{JzbwY{@>>+rE9|5I5UQKQURNq|DT1WW#enc#MEY++Z~8V~q49?h={D zW@>uxW}~MWYt%^%*wY~iY~ENyvvO@+hxMDOBtpiuyV8&Y{eNu3iL&qAat??%beG*B zxzrABXZwn7IX6eX4{t7X+&WO~UG!}B`RD=ko2jikQvGzMhALoZmxC8}F1*q4Bgu(* z`HW7*7ZF`s0B6R@p7}@a#-n@(z9!tip-O9b9g9t^bFx;v^aqVK*-Lq#gwG_Zd?jU? zGu#r7beAYflHs99vb;y7NB(Kg1tx3aA`7sax~E1C)p9y^2^9LI0|2Qi>7`Ybj+IWJ z(dNJv9l|1byI3Xzm3x%z*IKdw2daKm)c@c;U5f5hx6aQHJv^9Dbzq14+(N!^p6Sy= ztZ?3-T&TW>37z<~m)zA31u1;68jD9kP36d{*pJ}E&`5lY{!&N)SoDkY1J|4Q$~&k~ z5Hr>I!=Ti4S-^&OMJ0CAF=XWfH5&5;WDG>u!pPE1YbxA{|m)FO7D# z_g&RWPaI02VbZ>?Dx>Wx>pmd!TaffPsJ6SaN&+tNRh~zf`stw=tdv+P@#W_tW)}j* z9HAmGmZV7ENYRt-6`f#J@Qphc8C*v=fpC(Y@%eVjM;i%a4hRi4>(QC?W>6>khU(el z@Gl?io9Rx^6RzfXT(&YS=HAov)yLI9-S9D*kw-j?gV4zBKmqi=2- z?tbG#X9t|+>9ddiHuq_0Cmj3V{{i;8`AOD)>!!C)?4P)wY$<+oUA=LOrF#>L~>I1i%df(Ubl0op?k5=)voST8c6FLV_$-ot98X)_E6f%0j=M)GWlwMP?i2 z^Pr4{g&rKx(VErPRvyF7guUo)ENAe??Ge$>F{Xnv_5|!=NVT^;=d=Sz;NDt^?XW$5 z@bVhl0GOCoW#{MKqVmTXfk?k6stcZ(@c#3yv^knr?gZVYa1a}n#wSV%!NLzJTwudL zB-dIJMTELqAHuy=dAI(acq;dFLxEPW$#;ZI4;2`3hEZRLG#CVzo;e2t!OlB338Vt= zA}Ms>4Ou~@QL6W)!s(`_&7h_5rmV)fNbF{Y0#l%MDGf}BP3XgbVAKxB*zBp05fX}B zLo$dLAn=?pMQ|fRVnphJ$2VBC!F`|-Z9?l2x`5!PSf^@W|AvE=p_u!L1>)h_f|t~0 zjkBtV1Glk*wSoK>l(Ra;$s!JcOWR$c+Z;ICkj7i0Poc==jmk)>g;O34LPECk5_5Rp zH>?4(3&Q8RqbNBAJ3rcXOjXASj$pjFR3K^i^@a$)~9)!|IldlA^eOph$so4MshfxDaWYqjX~)9No0 zZeiQU588JUclvqXbDjNJsUny-B>U8NaH0QlRgE8Di*{R{8L6wmcE=4S;DOq#d zu7bTls6)rjThDoiVC;9bVC-jh;<@$(m&Q7> zbyE}emk2Eo&S#Mc9EGy1KfFkIRcSN4(NzXmPug93xbmIL#_bt=Xhun*pB;Ex4JC_q z0Z$3Db@+kgIx+!`CNGA*W zsB?8ix7__Q-m~Tf9`E>Z->L@+b8eb)@W-$7W9wtvxLhD!@GX_#4YW#e@JrXa3*vES zcED?}I`oFS4lZ+O>Gr*!3ucGW{LBbG_msb3K%eT{KKA3}S6h0UV|JtMxPh{wKQK55 zGe%82-HI90E~l7^MtZ7eC~s*7(?)mtx!r-;2`}tZj>(Xsprr9Zs?@Nghigb}S`sF9 z952G=$B^I_Pi)Mly^$iKF!v#)(x$YF$cC+|3-oh5AV29-+?^+|e-fB1xOu|xNR7@F zIG{aIx3+06Y)hXO!W7VpcGsDW(8+c^k|G@^&<0iW(4N5Z2Y<9FEKBdMbdqI0yS>%( zJOfP|{9=d6on|ZNC;4P6U)5Wbz7x9B{gcV)9>n z(VzNiS*{#vVqlp4VVV{tiU7iXsi+GV18NRh6FUL-=EceCR>15=yaX@%1n_Y_lb94DV$?~3@<=62rP z*Qk1-886(N!I$_~v%5}Sc-({Ek=C6J1Z~uIg9(k9?dm#Xm%n*DI2-*`D z1e-1hw!Lz>KegZQ=Kt{XKllSE5pPifVa1N$;^|(n|M?>CDvKZc$De)cTiTue^o#F* zU;1X3zM18|`E72J@BjG^f9==a?1$gy^-UqzWO+(g4x7H2Wq<1S*=O!yANxLUnV;gy zcm9u0Vly_B&p!HR&A;CE#^0&u9dp9{;P;A;{*NtuG3S)oM!w78Jx1+kOVTM(ZdjsB z&gnkJ)n(i5U3Kvw2)9@bz2&sIn=IHsFtX{ZuX$Z-<}gEW)d1BQzVaHxgp=5;B@mD|ckHI?m?}a? z2!jbBdi*fuWFyJi?6`L7Tdo)BQ79{yWD#K~FpvKZvrPEs|n2yg@yj?`t3mppQx*I5U2oc{2 z8|M)BEJ8w2%$y3eYV7Jg%MyP05`K*pNkPAxnxwZk%c^k@{jSm4A@}~(i<((0T;D~0 zZXJvYMW#-d;o684)kDE;jeY+v?kx}I4pS!8$c1B`-GqJ36q6Jb12&jlW6s5DqKzHO zs9~cn#8}=q#MrV-qS4re>(v*iL9A`h>zyh-0Gor*f-rk1S}6N|3%+QO->j zGr%AOl&-h-34nuR)gT^GR#W*=1qICgggP_AWf{lW=dCY6b317;IG8_^hrLY+ovvap+5R|sR5AmY`-m>>|wR| zMBkH2fcBs~COugt-g*>H`liHSzpDABZ`zOzdJDc4#)rWv`%#kJEdy$JHBpY3J>8T$ z;tFDq69WpNbdEp+$&lx#JV!IcXqE~a>^Y%GGJ%kKplZ@It6B3qKU5p=14}Q>`8W|e z6BuiwRgKGU_cjwT2BSd?0dHfy-#0~{3Z!PjK?al6pjm=r>M(2t2v9LvjB7I}(^YNF zV%b^)8WV~yG99YH)Q}y!-}?Ug*q-qxB}z+S>xqsyKurc-mtvZnhe_sOs>=i86l4rc zP}S1WT~jwrP<4(;S;zIcuWS{&PO2`K;2A$-qUHK@U^%1{=!e`yYZZkAK{??>94Nd_ z9c@ax#;7)A&f%(`Xrb9e2y$Z1t1G!GJvU{ZA62|MI!mh-EfGQPj5w0r)-%M}J~S?k zYf2sISwkplwhM=x_)(42^H8@`zB~9TPVPur32-1k(-9K2wkh`TjY^{m_|=x3`CRf~ zsGCYcNvCT?Cgu2G)m@uZ7?KE@NX^UzDH8ALRKX#E7Q!XFwPZ2Xal9A&`{d6 z=jzI+Jn@-QpGb%2ETXIS^FWbGb3+hO=U$~d%WBq}GU%XUOed{QP_`3ovTf_$u;9?y z-so*8sgk-m;Fe@ig=4Zf1 z%XW?_b!Wr538%>>(CTROW| zY=`Y#SIOU8wNw=sDU(ukiQ;C)89uH7KLZ%*!TH%080&JziS-S3dCHxPr(&GX8c_tF z!{0#=I-G~Ozs|kk%6IN(vvWV|&K{!+kivvdF_0oGv)scsN26{JmzfE)$F!DKf>nD@ z6^h)YpU7eQWjle;&vKnoN_hYf1e4qoQ5CMpUtkWYnE2?^ND3i8NPKI9oK7LJmK$=) zS4K2MmC~J}79@IzOcwd+rQ^iTT1t?K6Q65hL3MQl*>*IGB7pgjLE^UTe$2$dCUz&T zElxEO%p12P){3dJF+q7v15{rYjA=+q+Koe+M*OQL)wHQn2j8M%YpchZZd4cq(Ahb0 z!OnADhEk!Bp#w+Hg3qjS4(8C%ibl)An=fJ^Jg1A`&4Rk?Cp)>Nr%^d=HqQrdR__9hX21^WHd*RG9Pd)YY6{;#3bfu^xUpqNMmA)a>pmbXW5mBPKAj`WY7DiL$KFJ&rc*_n5LC&=ziQ!PP(0oB9ub~HY@a`ICgahbh|Uf}0;@)0uo?AdN-W}dGn z&y7y!C7is)Q@yZXTC(xkYx+8qk|#F06sMI$qkXEse725N(xuRU`=)O+-7d$IPfkJl zEkyQFOoh1f|IwDhrvK6>pCC;HnrlAe^yB{Mz+WS5TC_Oad_3V;pu#!i*(Q0+>N|f9exUKwAFJ6eIyU}ZfZI< zyis{GBsU0DB0(U&s_kB-2YsW(-fmagIH5NEDNPqT@yp+PmQPA;`lA$}=5Fq{6iZ1N z@y%R{gX*_5ufIis+O2d+otM!axC)cGz_PybHHnI-c9Mcm?Ui)y7W#Lh;;uY7qHNO7 z#YPCIe5KBj+e zJ0-|V$@K&zxojI17e318>blYuLv`@_yU#!36aG?Z@G<7JWd5ppKLo3+UB$eEASd(& zg6BV7A&`Wf6@s5X|2DMsHxOu8oOzhHdD>){rL=0+FdVbVWt*1dk&)VGAH#8AfQ@+^ zoSZRukLA20Ks|sbvts3d%DmEjwXUc@r<5EgQipo3K)7}RGt-GObVVfBsQ0Q8njTIo z@Rmv;X?{c*Zko6DyS>K>)ry~19GnL3$eBo@8s#4ZCt-D3q=)Q2RKg$0IRQP7VrFH$ z^=KVnwM?s07E(+n>F!5ivTJ_hjv{1X3L|xpzQ)~B$nG(}RdJgN&e>*07bZk4NLY;`Qg9C(hY27(<)AB2GLnyRCc z@o+DJo-&bUBo(^@Hr|Q#Q|DJe%rY@`ty8dzdQ#NH>Ro}gMxG}Ksj#O_`@L_Y%{ByQ zBP&JVVXaee=!CU5EAbFd^WK%ww@N}vJ+E*f8c7*R0jo=J!38=DIb2!ft9ot}gG3^b z@m!3o(rUK45h_{1D^szcMb76m6_X1>d|5Pb3gK`vNXk&Y>cHZo=gQ-7Dy^PsGtS5< zdpD7qs!|>G;y_gRd6qEhQU|v)&=&0US@W#IJ^IOkmpDw^N4mhqFIUA7_Ss{SN z?%b6yGpGtx#?Lk8SSyp(lqlLrSSe|&VsiiF8w$8@zlbS%)eSCuh`d!9(x+g-h%7V2 zZ28l)boE3{4esa@;mB`vC{qEYaOL`7Wl3_OO4(7JV^ttWM37sAxANU}A6^CMx7$ zETyB1&~m^jI3IPI4q@O-9_wDPD!02plWNz@^|8zt2eP^HjLAj{juR(5$rmEr9=+%t z!d)Ry0KPkrHjB-6s++dJcjE=7G_o;uSyvN@gndi$?%1gHL&y2T zRE%{+iTFG`A;9s0jrv5k4_X3TR_w0oc&%!VGbNt+$k?(uuQ+sZMwY|QSifh9An`1_ zH#Qrvb1^miW(Y2yqam6C)(>EzDrx@nK$^;S!~u?|+4b@4obhDEo&_a4?IyZBWmr|6 z01NCFB*WyAkqwHrTTTa8>ON?7&gkHSny4c9pc3{($P=AA@onys$y!`o{s3`N3*(d4 zmflzCxTdY%%@xzjX~a8!Da6CL{#@mbJ%T66^^(AD!#GdEk>Sq04ErTr&}DCS<#?zU z^0cNDw@I=E86b=e7NE227tK1fAOQe@?ys)r=16hoGMVerR37M;a8BJ64PZR9{b==h zo}0{49!b!DMqirWMMU&QyT>81HFRR^9kH|zFsnpJeT7IJ}18*(sY!5J;MhDo$z$5 z$5@e}=Y$g+P{ndB5zHf(a-gEz0-ZotCNB;QF2FzM$!cQ2l^!(k*8A}g@xQ6^p&3Aw z`4B2vAkns6wI37J9x56fR;!}i9}@ysz^#ikmOxNJN=Q?`RA(2{R8#n5ZYmd>TR9Fl=Ufa$;&at4nrcMlNI<}P)Ko&?*Y;xKdrgDsa~2N}!JmOJ&hW!LWN z^KnW6Y7D^~g}&?>i1`ccQl~?7d-@`u8FU(wlcG%t+}Rg_!(?OOyU4~}bMiLnaM|SPYhcC~KLyres&-fr!Ht=tuFZ)ljEt19 z_qVT)NkYo=uxaS4mH`jAP8GOyk-fIpv(@nOnR|-)zWf!uaNjWDP4CwzcRsz+ko`k- zrTj?!Oo-WcgiF2#$B_Q>pFg&5?pgm*>*d#9zIgSAzq#uFfFzN zu0_07`_k9vkADC6|48u^?p}RIOZuNZCyg(8q<{O{kL{bgbO6Y2r6k*nS5F|gojm#I zqpw2nZ~xZc`nQVrH(>AWKmCno?7jWh?>g@!Y+bTRKJ;-`#vci$p13j$FWKJ-~3*h0gx^Urg$la5k*3W3sJ{omxI za8jbm^U>B%vGsRyU}fCA`PX3cH`A&Y^M?0t(n>04tJIPoaAK-Ms!9CM*vWQ(yycX$ zfAre0_BXs)E4R_g2lWS?xP^qTZhTR?cO-XbaOD-h-+bQaxdk#Q<@xiUf25ROA^3*( ztGC@m{8k8naZT?A!Gj%I*CPmo-@nOOkjefw2qa?$tUTl3kE-AnUTr9>T{20wGK(%D zUaeKas5q|HezfJN7;BEAWNbNadcOUI!;{KQLUxeVBdI>w{s9mu7BGGJ@jV0^Hvi2b z0G)sSg7g@(7-B*m)M2^SWRiig6(rDziNU?a=a?Lr{!$zxmoLm=WOZADSIiZWj*7|q z2qO+`SmM@VaX18I`$dk*f;f#u=*XtvvR#BN_^PbCsw{=mJ4A9a-{t(YJYUV0?arBx z%q?;soMjcfd1pffEg%Lhz4Ag3e^LRZ-wmxH0FVeZ8?vl?hX7c@29$^ps9?McICt1b z!;Q_K8)j^Li5AFNKu9D69}DZ(tjH1_P?*PCnUYMa3$AZWYZ!%CLy=Z2SQ`-{E9o&m zn!MKuT=@wzCDDW%PtFKe+2xC(82K$Tb>YH;JAQdKrZ6m`iW{Crh{8e;Z;0YiRAY{I zRF_RAx5XM8uv0hq5N;0>56?&_!sV^mAtcVgXAl5R^+&F`ixl|OL07ycbBJ%d;lVBp z2_B%LuDRncOl*oHZ*@@HPc(u+uu`HU4BQ+OTd$ltgNw(pwA#a6U;5zpZI@f!c{*8p zh{kS1=jtgG(HrM5I_hWd!u3$Znxz_&$MU(QRB|b}Af8I~ov@ zMUlu`hqzF@>0IoNLNN$^qw{ub53`yiT~@yef+8dJN8ECjTQ^%hAKpozL>@{gm19vC zG9-{s*1`dZq)4lxC~8>ckvlrDDTPTZUy7RpngZ0Wn+Qp569fl36v0+__$XVd)lpxR z#JSl)JfO3LsZPgK@%32i0=NR1%y9^OYl+aRmUM8jQ<7GgJVJfW(5hpnX#lidOAR z#Ro8H^k}@m>s`z>9G$xk-h7BjzFGD8J%%3Apm$wS#CqO^r5)ShI!CsR;}k1;OxS@s z_dQ3LjAQ|Uy`nmVFmtGGY$9PMs=%KIQDoe}(tEV2l1!VSojHhiZlMRX@N|jz)ij0W zd?S!`{34jF^Ykqm&>)^s>tvCWa)I+4(KnQ+9wKe8n#%>Vw&|LgH>={Flp3S0B7;tA zFn~1l5YGHGGjjhtpq)kN*^@2E38|ezks(mFQre zXmDEyM-J!Ray7ceb(twEM>;?r7H^y#8Op`A&4s~vo{TguV1DMV=h3Yb4@s&!DbWX= zLJ4>Z!6%k=m@7CXk_6MfJBhFc7QcS0D>k2T@B_G*a;^znJRJ{9GDouTu5OCXkksAuhx(jU0eiqoLl|wmb z21l1Y&AJ)#OkJ&)%;)~Ch$r6eGs?8Kp|rH7)^ z_cPf1G6v4LLH#-PWwkh`gIqm?g#8Gk`)!x*D(^2oIrTlXo!sLU1Api5_C%Dpy;94V894s z52c7-;>a8+B#1}oDJ*r{QKQIL9qF7SUPxNq6>g?(D$X^{E;Sc*Ydf?tU6mW3kTmB1 z$Q!7gFQ2~><}C>f|L9-* z(|`E%)ROj}x7}suWV#Qr`AE;H=gfI&U4H-E+@C@KhVTf2fBFyq(Z9Ig*WPx(w|rIz z?n7)o((~^4dmzwhqc7M+*50)Md;x}{vrNKR^lUrdIv2I=Z)-<6?T_ z|0^l7<-bZ^RQgJF(^t^$A&{3ao2L*6n^w4Oe|t+`_H`$G^V#$*#Q*p|uMj-W`7s3V z!hAOjIPY8k@7w95JH7k8@Dc7AcXl_Kp^wi!{eTdcVs?`7`OBQ3dLj{2xOMdrgD_=Q z&D`t4l^@IT)IPT==(Eo@Jn}g;yUO?ct=|$p_d4UkoX5+xEc}sO;s-exo3x4*)67*( zjvKc3wGaq2)K?$?qm{d4k*pAa{|Uo=4+JZw`Bey3xhIby*~+SXw394|CA(Da+gY#M zp8v=B=8)%Ke5(E?TzJKywc|}~=K57CQu@x<+LUdBWTO|n6#@l(3k35l_gmdQK%l&- zlp6%v@%|79jA7gOF)Q$nGVL{G$gB-!w=>t{>lnYKBKuv0ufyJI%P#olRWYMq%V65! z$PyfL=PKT|j*7Uf=beP*knGG(H2$n)Zj(e#ORo$Ax~3z-U|&_kJVL}go8XL5S(!Y~ zOjU1G%y|h5)U*AR&A{#z5AG{a!eIF6l@okOL-wy%_ZhAsdqmLRrn=Q(o%XaOExwZ0 zmch*@m-QAXJ_@WdX4t-(;W~54^X6v?$}}jYsznV^{;S)Trht#kVcGYdA5V%7jrkz$w$HOmz}t9ULg8g^5rgc{>W3FSY8X{}`d zo0J}TO~o9}`>C4f3%Ll9fU%k~=>n6H0^N;0xK1tS+QH`C+|A|Lz)cYhF@d5DZ0!$7 zcu*M&jt?}5sX(G5cFwgR83gXl>g-_ZjdaE+P~bWT6m@TPeLbMb1~{aE>PZwg!5@a7ET{sglj6 z)FQW4l&w8}ZLVw~ob=Vj!B4C<71PJ6;H=LX)+0rpvg;(syPH3nq7K;_RJc`5q?uVL z47<&IU=c$E@uqA+!8gcfXZuLvt}#Kfs{D;ZvI#goD2%Q8Sb>W{WWX+-;S!yq+H45XW89dliFz#)accCK@aj&}xseT%Dv@?<5< znH@4}Mqxz8Lg~YbGSJ$FPgt4W@lzgf1n+4PfCy~Ks}HR?Y?BSGHRwgh0?ncQNF05ZlN3Pk=#4T_Jt;+2_V6yFd=|9bfy*86J?!8n&zG1GLJ?5(w|qJF%K& zjML#j-ZnWrFrWsP;<2UjC`ls11wvv`&O>RF50)aW;SO#Hi9s0dJkGtO=>YExy8%Ws z3`wUMf>L6sbad2WSY+pqIZ;aqQldZ7-H!O-f$U-iFIS0rN^h;%9LG|nkeo(b7nl@a z03ZbqWyw@G0+n(L=oa6YXvN)-W&!=6NE?4+6r(xP>KrH^if}b!JEJo6j@Azl*&NJU za5*rI8r|uNKGdVNdDLP8CKAK73$)uriVpG_x1&s=hzyenpefbH$KW&pPr$#9!$j0b z#X>}&F)t+9poubl$33m<;+6WhaK>|17?LO$;H@B^@j6f~DGZ^a^7d!b)u5yz2S8X)P8iSsO)=0ncj(P~$K3w$=ASA?8Ar@!dy)J{E@)`cq#Zupf} z?ROSVY5_np1%^@^T63yQ&V$x5Qd`04EEwMf&eFR;sfbSS6TFAaHXcXcxq?_#NcDk% z>Ieyy8|yhjR1a5@I~t(U`W1jfVPZljv*|*KE(fZ4*G?6zj&u;0Z}L%676(^W`>rgn zv>h97q3Fi$>b&+{&K~xcIjHHSG-^M@D%&4;~!}Pr))-iVjA0@NH5@$n2iUWNzT<@dj&QnW?%i!;pxEg4DNaydjIT z@p!<*##naW;mMyvJW$>CNP*^yPi<0%Sf~(l6(JcxsM$m~0uw{ONsKp?a<$XUaDUNo z#3-TZE%>P(NP*x zw?#D3i}%C1=cdCMK~ZF6gF8T@_4Qsi@;W#9d2##3QdGVztr=I#R3H;^3{PodAJdK4Gfd@bTg36?{m z*3Xv|$=oIQU*VTS7uBfHa83-+0f92x*_F!Ytt_vJIibeX#Qqg5Yo`sOZc_P^O_ne?s+jPwX#$`?jOH|8U!8f$Dd_-B+`vP=7fZ#9CP|4 z|3rn-oDNcn!aaAW+gP1kwfL<9yx?0RV7o z^UBNhZmuti`X5}m|M;_g#vihb>W}~6W0OY&m(kfdBQLHz9uBZCO<0W0xL(br32y*r-1hh%cQ4+9!x@Pi=LLh!FL2$3jygeIz z`PYBIJyE~w{&?fG`qlUw;iGsbdD{KPvyXlU$gTJ5j7XL_Ske**|eNAW-XH1msPS!JJox}Jl=hQxFQF^U3 zEiWvM^rwUajtf&HvkoR>d1eg!K51l<4;2aT7Jsc3 zR+Z3-$cyO`B{0_{WW$Z*w?tj}V9Kk0(qjwQg&guOmalyxjU;M{x#<=Fb1;JP6ae8# zZBWdJ19sd{V{|Dra6e@mFC(;pmxt9KRFCX>a_u{sDlqM%c4kVG9H7|BA!Is>E4<03 zM1(@--y9qjb!hAgd~yjVmY_EGpemEQ6o#Z}b{_)U0U-jA!HW{W2ZMND?Be492HX@b zy2`+8qH792O;N?*r|WHy<2(ZmAx4)@=J0R`A*XFjU6Bkc840zx{pH>;|)-ywx!-c?+tu1Z4>m6)@Ak`?lfJFzT&z#>okoHXpP?AC2hoU|Ni@Ru77Q(Scsyy2VR$FAxqJat>NR3j$*~bwn;I4)V z+%HY4Vfb`>HyLoB!Z5Z`byG!)NAHXaq^NeJ7fp^fE1c>EbD2zAT*A}Y+@LQqq|FVj z-%O`^IJcvlhzSve^6i*79&@x*=YXJa6?>4GR^Uh66XDg9TD+){_cZ^SOhU!=vS@ zzH^BJReaFrzB-VNiU)V3Fl0~+XV41la;5gcl%OH`8S;GyCLNH=6FRqAkJm8N1b5Zk z;_eE>9G@ePr_dCf78@rwQRAg=LNPG~_!DU1dFHrJFtuuhKF+u=C$K_p_#)*1r4hZQ zi_R_f37)SN*G?|$Km~P$#sP~@={i%sldx};aP+L}n~_SRUOdA0W!NnsDYnW(!3wum zk~i*>NS;Wk$quA9XZMt&kARjgU{u5%_LjhjcbA3lZI9@-OS#Zfs>|8V7lC-Gs97u1 z1!BU14qM?*w+nQu&^ah892X-cF!u{20;|ei_PW}oeqK(|x*-|8$1(?3XaBQ58x(?BUvJ;}<>;(3IIhU~#J1YPw|j;ibvZyF zLJG-pKC(yv`SYGms`FD4RTeyz&_IAH#oX=`qN4UHI($^7L)rc)A!|6bZrxH6c z_QfQgu9-^&;PpaAwdV2(*&-sD?rb?o7!Az;zuf5d(=tw^9TCO@`G6^4;cEsRE#Tvr?QiD6OehNnRFq2sZzh$L zstaw3C;b_nH3x&-$?cb&rO8bk?Y!(uvbvqG(s-m*0S;jA>R4^Xc#)i&An_r z1XrJ=m#9Y+J9ASlo-SpW>df1f=4X-Tg}DX#!8z?aUI{>vV(g>*-%AX}CR*tig4+ znTHD&6!>BCx;wAEfNG;j;(6*eO+BbLU8<&;vQYHLv{>^LmfBza> zQ*c`8mZaA|eeps!9zXvn+&`JGXuWG$IGX$c=lOTPE8E81wWYXmb!~R(Z*$xHbr45o z(|NHC0wi#d#R`EWjd1a;a^&Qjs2h?4Bl~mRyL^LS*YW|8Tosc*BGV@mle=kZQNbGOFoU;EBQymBP2arI@Pc z^}oe?KLkqWITFRc3W0N9g+RP?Z(tMzYDX~B=PLwjI|_*Va2p1@y1%KD^?qr6A^Pgx zK)&M43b);io@)z=!>d>G&-ImVDSi3nul*S;a5o%TWzij1TS{-jZJbtHyscHfAp{yf z-U7kL4>)pemCv6-0E)##U|JtjFDzjgb3JaM;$a=A_w5%~ESOVGqp)E8)DzkBO#DDO z;L5LAR*Woo0}JP(Vj2ix-m?lbUvZlX`yBLMkRs+erdafzcUB39B$L2!fpvLeH!lsr zvo66W|LW6#-b_&6lRptc&^r9CMPegnGMR_gS*t{##~Z6u_7cIfDM~%X)K1Rwn$aSY z32Q_Lz1;TZut(xVt$br4FT=Vj==srQJ6jETx(p}gqF^hui5PBEfJ+LwFv68f07A{C z6thZ}2s2|2_>D#4ol<{X1In3z!5jG{Y%CI4p&GBqLxu7NO%3G0!dA_NB1e`~5VSD| z0uPuyh={V(N&?)#vBqx{i_po;y4JBZ$Z041Bg15lbSw2 zeQBeVMWDM(ETaZ$f=!r2uR;<+twu!tFv|__B&Z9f??*6$D{J9MZ3Z`0YJ%B| z(;An^Zl;!u9xJ#ItOjJ!uo7a)*aIEt9dO5;V{L6`-u2#QwJjv0kq&mOg7pHm3!~t) zb3N!-S?+k}$tbB=%Za0cLxgNSfyq%mO+Jhjth``0nT09lO>UcLz8$9|N~i`yZR&jE zjF8X4Tzv_9Tmm0|Di6-ciO$MJwt+i+0{M_mncLHOM|fbP9|xKxOyKTR=w9>!aw_M- z7;l_*;RD}t5v^_^+DJqN8M@ZR5JQWAS)+Y$L9QX&f7F#VY=?(h5l7K*O78%71V(4iVTy&4M#55axMW2P?p`Eee~-c<}G43oq|=!x?w9i2-c zDm(&eccQuylE_nhL zj)?@3Z3@QER(Z`SrXCb*4+d@BvKz-S0CG9br@nJ!Tp3FNNPN0WDn_|9;?kmHkCB$Z zPDcZ9bdSCF3_QhwxyYLaH&A9rpks295RSx9K4%1tfJ>hkI=XVHl1r&X2q;5|+z$xn zN$h~at4}f!Y5fqpmb4U?QqELKC zMBrf-mA*hOjr-}m>@N(r2X2AN``GL<>-(aUT3)g26WlP$i5SQ;VSp`@mJghd%A%Yw zo#>Dp(*i`D(S>y*wpKA*bgAcrpGzI`xcHQqSZR*P!)G%WP)!`IZX+GMbk%g)&A#;z zwiu^pA6bIU4xUv`7jpiLQ%;aciNVxqkiBtYaZ6{g5brgFFw8BuNDON z_BYZ@?xmN2HX`|Am1FYhr;=p3W+n)l3_kUq0Jg(A9v=a_{0;g4k zC*AK)KQ(WA*>eBGKYVp3uS71+{plYev4maiAOBc8*CZxNK&E*>2*~z#waf}Z-kkh+ z+nauyzU^hp?>B$*#fvSB@PA1v`Q6{AeSjeEpx+?SoxEz7uZLjU31{E>nNC#w^LOCf z+!M9>Z@BqQ&nK`rc~#86dfO{!Df;-<<|ljS4?E{;xSyyespCrN^wJ+dPZ9m-XFuC8 z;uY@{-DZ#rSi?e>2E`?5=3kmP0{Tjox^ zfxsan_f2|=ze2FeCV4*uO7vY2RFxpm$;SJjcoU3dxiqZ#Fi&z;SHXniZ_q zhIV3V>zTnHl7CSZ6HBs@c0TvVaAsQ)rFlEwmYs z5sD8Sgk~w+fYVG^V~8!dtA(Kf#q_c0C>O|xvt=HjFR&gjAscXPKd=K?bnpDxRk%4Al%8HaXHoS}EG$=-lKy=E#;w2$EDHQidw>NVhfI zPc($qLTfc@7cR^_K9yCxNvDC?>@AY zrjYZ{R_#Q=t^<-wJ20gf7{r!#>}S4air4#MG(JQKl5v5p*7Uiw!^B4wH9Rp0&8OHK z$k|~|^qxb%qk`BxE=m}XiP_YXb5vPhQx`}p(P-nYVr=g(z4b`dt>xoh@eZ`CDuJ2JYGw}_NW*mFx(Sy0{QW-@ldxVD_? zFJ}Od5^jD5IFK1yr*T&L%Ge|AZ72L?afy*8-~ZtW9XrH_asWvUNO1P4jV7spwum!{ zElumdll)w%%k>wpNXlb9Jh?wz1$g6^5n3B$2}B=4G(kF*U)25UtDiag$ahMLha#Rs zIrnri*Me$Nfl%6J>KL(7^K!Q9JRkEN%R4uLizliY+`OQRi1Bxs9B9_Q+tCI14F5ms z-X~O&B+v6R_b|_jn5|ZMlzMAN+M30jZ9y@IAy=t6)J)ctd23ap+O2_sRbGl&Itz(} z)}=w(YtLGHcq|bDLByq@g#;c91w*l7MIS_gP~+akz_2QibkkKxpgq=U4o!)t&)3ZT zMLf@!m6=tU-6eZ3-2S@R&wlpD?f0{v{fua0r9l*-Y+Pax^%@6}u0OyJ7{EgpEL*7o zqwuBPiF@gEeB$BiGRhTbS|j!VGclTro4|)CEPWo>^xf+%Nud{rP_LImiK5X{Z(#vY z7F~Am<^HrUhkbxi!lpTy!DBm?(DlHt9d0rELTD*w0Vl+Vu=ZWpRqUO!txJon55B9A z>@c0#l5n#s7%V%uFHP3vQ0oI@%ZYk7S>`%3EH+W(WYEs@f%e#N5p(5EL&Yp3%B_J7 z*$*)XYdS_3Z(=Xm3Ez$WL$-!ng*^MZf!jGW>#RkCNzZO8gJaplh2QKgd3-_y1zU)?xv>tI^iG-4w(OWs7{eQk-^pvAOghnpDW zsl^8viVnITD*f11sPN>Cwx_HGd+u4&IQm;pZ>rr;ZxKX7IZVOz!-owv{Y`$(s#L48 z9h@^X8#grfuHUmGUI#-DL@eAC9Vy|}bcUw2YnmtEUk!dqc+IUgF6@kU!W|dSY*qto z7H8J*Z`j2rT^LZ8)4)O)dQixhfDac33*Tsio@P1N2Y>6i_(?Mrej);d;@6@J<)Ac5(tHZ)Mu@B#IsGb zhs?oV++~~=$moKM$I6)8FyS<;g7CB zo|McgTmZ>h4!DfxJ+fo4;<==;AC6SJ8SRH-{P(AHfN`0=RrrmN>CI`!d-6xR{~>$s zWBNZG?S>^d>vs09FLj8>ukbIw)cqdXV`EJ`+PcR>yT?i&(_P==G3|g)_9s_AyuQVG z+IOCE)P7{#!9xP}X%Gn1O9*t2hw_Vi_|m%)>o93AxwOqENXe2y5KRzhjjkNEADJMK z1Z=v&7!Ck?+37xIg~^==l?M+*lZYPSN6l}$$78<3{b9Vd`3l`9DaXnF_)*djDcNPw zh4D8+Ao^xXH$jkSB|#v#6Fh=of?BpHmdAI!rICf!jd7Q$AHnsI47OyKMHlKf?ML5k zpY?b~Df9Qw`Z^;m{j*Vf$y{s6tyDH8m{$+vILfPI=S6M)QaU6>wMCp?{Q-9(3gKLs z0*+))x+$;T{a@ql9Fmh(KN3|)Ym#V^B2RA!Tk1KleIaV_OwDmRBkqXbWtW(KKqk`c z@!`iGemp{8?%q|269j5(`9J@B(oOl=tBXiKe;4Xr{pfKB(i_TO5JV}D3kZ;1h&$qE z2y~f-Adse(w1+Rg!52`&^g0Ts_()f(&&07qi0JC8Q4=UQyFcB+k#_0DyGd{AZ5h=; zHu-W&MSO!#K9N+pF?YZC^6JMwMlTLEO%SL@MF=7_oZd?isL#Z)qnv=?K}=`9c=gr$ z*Y|)V2qd-fWC-qmCcXiC>~{r$K$svv$QJfL>wh-f;@{E+<7{gO%QZ?YYz?P@mql5k zb-UC;x8{${aKn11by{6JXI#e`Ra5@~cXrtJTayLT+Q91Am|&ghNYBa!A8(MSGpEcD z77U${<&6oweqF8gs|n_PVinQ`z7v-15rb)218UjMhsVhUNw#nh zLx$y~ie@PbX||w1Qv}|_X5-RW-p_c$;%CFt$x`azSi&liqaf^H`T8jlSreWG>aZ~0 zAy=072BqXpU_-yx8&jq!;=rNo_y2CUm#61&H{#tq(W3F4~r2j>jBd6 zpA4Ae4=QSbUztZhgTvS2YY8iJ#X9^waI&bVJw6`nZ$^`?oMF9djq1Qx zzc7tu7aTBFi0iX%UWs1q`D1;6X3uKj=ptEO7bq)V7vwR30AChy=`QUnQ5a4yCk7x@UeC&`?ug}9y}sctuidhj z*49N+bMo1&T+W$m7djMJkQ?pP1lO(Gt|5PmIAq_Jm22ICCapS`Zx4B(qP}ntg5~jW zWR_vg|LL>13KVHFnU;wGXw%lKI*1%Q9*Omn zH|0GsG6HkyykD?Ggv*O?r9}3~ zqBtfTiL25Jgm_|UbqD}bRUKm1h^GkqrlL8RkyfZDU3A>uysR4AFf1Ix2M0>@rp3Kl zbtVfApNc_JH**;jWu6~B6lCp~DYLSnup<)YEmGoQR$>a$q0iPjr%5YkdT3%eK$yxg zxgKfVC>68CzqTkiYztFCbW4iFKyw?qG`pdOB0y3w41dFpceSy7rycLSV=H}NQ05l@ z4g%u<0msf;4msbuYGEBGxP0d!Qk4DlJqN|2byz)74xXXlpj(9A&E6y7o4XB(d#ng(=lcMz9rhu%(T=fwgle%*2^f2g~F zECRCNIkP^!qgb00)9kS2Tz&9}AINrspxDvW2pl*OkL-xUiz)&QSi;(3vwC1Vdl6ug zfCQ;G8XS8B9XsaRoE}#6G&~0n;F$z_RF60yExzKgK3tO3%7%?O+5`Ip0q7D<4JE8D z2OZilBdA00LCLWeLvxH>uWq(=!K79rBY<3s`71IyBFjTzd_yztt#6~jD zfbX*k$th&Mvnuo4CN5gqTZR~Z(-F^k|4u5MveSXjg4z!?p|-K?>ueCn_Dp-v4X!prJHPnMDI_3%1S@3rFD~Hl0tuc=R2;oBjKsl z`^67fuD*Em=xICNAAk4=dJ<}5+!>$jxPSJIA^1X6^5&mKL3wt=p>k0b(QC3L2Kmzck;Mb7)uoZ*G*ACMYGK8q6ciRYp7?!Sn8 z>f&O(`>u4Be);8tPXv8}KoINwO9+1W!^`|4XcrJj8sMuTxc`~zF8coqB~(j;hDCO$*4s{ZZC8EfD#uEvH7PV z$VZ5et_i*MUDa+w)>hpn5M8Vmo1lI^@E(+No;$B9xdl?mHM`m7P z7oaDC={w67Ov{x6?6H)VEq7{O^paj)cNT+SZDyApHSHmx{Q=M%JH|)`2Y1ZEi5;@` z7_m^ZFV5a+T_TNlVu2kH!DMH8gTivp3HG*e`;sos{(ZGAIPzH74))XnitW^r!WSX7 zbzZ=Ot3^vEM;ex9 zfjITu+AfTD!PqK5HTlBuB>mW`xK9wTCz^`>Z}7m6I&hNo!0CI=1#5Lh!7NeZ`HdZ% zf-(;bo0=D3fB8MC?Er;WfoM@5*p0*Q4ynIvk%VOXjZPR?Dm&Z2sA};i2=GQDo{HKZ zI@m+X@;v%&gKWt%bBdLo$<%d(F zb!F)|CtY>@Vd1>aKUBw_rdImQ*ibsNb(}>%**0j4t=w&HF zcyDbrK$g;DkvDJG^uomnM$~CBES#t~q6}1YZ`)N>FpD|SvL5X23cC`E$98aYa_<>k zE4Kir>^B`tyE;@1`;~Jm=osJlU4v@W1*7y!MTWeRF^`g!_4T)a1-sJpQjy*F> zov-``h7dj3Y!DorB3C zo`aW62>r{EqR!U~Pj5hC-UNC}2Q_eV22%iYD+kMdS{E`e8&s357sIAJ*!5}wjf+|L zfW}yDU^dXJD4mG{iycxB5&dGz$)M7$Cj!?&F`GW)1-KwXSNqyS-*LH4-oU+JjvLdF zOj7A+Jrfo0H=(RoMWA<^u-rjcn%bFl*jA390I!rNBO*~$(MY=!0EGG~>C}skp}>2o zLdTfgBXCT#cF27|Y}gD8?JBUhzU)FltyB=^xFW-RfXH&9*3rr)sH4N(| z4MNUam0!>o8aU<^O^~&ngBAy-O_d{^4)Fz}C)>1j12G0yHG}b%)ph2L-I~GzPiuV9 z@Pf%4^DGc*W`}tc=l;xh#xFAuv>ltS>w&LnPS{YS&bg&HPLdIhLqJJ2QPLGhXGc( zDZ3u=svZ3ys+(j2(Y`Vg3pBeUMy=>a$gPBgL5 zS=MaW^Y!5K$45%xOg;3T4ZXZrFnQPFv+?!JW3=ZUSB{79RT?s&p;HeaGv9%3I#dUq z1|~-i=#<4ma|{7p#w^h>o8gH~Ktl+SgGM-k+?DDyK*)7#jU(60o0I1=L#BK*v#hE# zdPqq+*(TE1zct#gkM7=e&0Y4W7EWgqIG{j$mK;2#b zuYy3>Rb{T@xg!PK;EUPd))>w?m+l+z}TM z0`*ZrVD76=0YQ8{(cZU1AP!A}KspSRQhL|U5HM0l2((&8H?G-PxomDXJOh97*&?uV zm5Uwm+&~-4x3Co-2gI`)WBJI~fQ)v4>{&VEf@4=|%^}mZDcuGa%B75P{4`Uu;Ln`t znAC#;2r(uEvuRor8pr1<7^0RPGG0OFztI`f!>v_+MmV8%y3CLpE z?UDA1>Gpob*Q0fon2t{u#}-ZQO-3@l1Ezu&-$PwF;{cuI5imKeSoy9b6O=P~lPyaJ z*~wJYKL+CsJ}4H$vLxC(C?aIBMni~U`U((648I#@?l+EoJx1)oafFmTIlit&NXwl> z@i{tLDFqlZdjL1Y+4u+O_%q%ystqwqcno@Pf=bv2aK|rx$9rrUg-^!I!Ai!M+WCxJ zTm@0UhMKQjW!Z5pUAJJzuH^E;{NMnhDMP1k+luyv(DWxmq3D2OV=;3E6dzf6oy05Y zvKGnEWt+383YJC9j(ut<5Z0Hhr#M?@f||4ofL<=j+H;;5!WD%&K-^e53|kWmj`KqS zs1qWLQMHixmOW;}U$>*GlBZr~CU`eoG` z%l_ekI<&Q`(eYJS(^s6ddHgs`cL5T*y*$bg`>dpe3MUxjOD>=>0syu=ZO$jZ<;2;%+ zyU>P1*mcG@;(0VHN4{m<(+$f-Z|N$&fsFta%Z~=)t$xvyxuMVUtRW&;uA0ge?B$LFX3hWGJ~Z3#y4l zq_z#3UjS-Flk75@j}fpS0~J_Pq}n8!Y_x`x6PJ|QRN_>z*U18#v$}m(wTCXA73ZkG zzhG zet;L?S4i<1(9eu9lpRV1@CT(2B9)>oVgjunr0?iLO5bT5D2J6!5wBJQeVj(d&&>^N z{tflch)3xc#=x~<{GCXyaadj22Z*v}G4vd5*yer+esyHmcL;&urJYT9QCmD=FnxEl z!-1?I>xTwt4)LtGF`eHQi(!ArSL~6~n|JF2ioC*c&kZ9UzhTm%$iTQ+`=H4r91Cnf zK_Vmge5;!)`#eLW2pamxti#ynSa9P`pt|i=99ZakW?sb)Opp%36=E9JATla6{$X8p zI!N91uI;goy;BYg7~vclrtFU1s-sk$Khof|0E~uSC^T8tX8ri=rCU}Uo~hwW5Ni%)BA_e`9PaC znN0&}Jm?BhQjhKUw5R3-_BsF1S>gzZ4GOU$D zwUTQ6ux6*b4jXAX2D~$ZkvKr&A!nE!Lg2`QvNmQ2Fiq@k!HEO6 zsJClLm3O8)4u^t{0?Uhz4FYKNV|u~U0c3LQ!27w)x*a>-rUFOobc-@Z+Ft?)$Xpm2 z0*ME2>4}+dIFi2`vb9GxT;Y;OLCbl$?-&(?XX_(2v%$hifMB<-7>wC2V+DoDs)%^C zgV8w4GUh@>h=QGDzP~^E%}7JnGyjxsvSYfKgZZtL6Cg+bBsKo0qhA?~v~si`Ui+t= zZT9b=VXu(6zO=vm@?Zb{@Biz^!_Voo-d!BC zxaYgz+VPeY`8@p6Z;X7(QDZJ-v9Z%fAz0KZ?Z29Rd{vvZ~jf}=@FCKHx~7t_MJccShgrxQLIriUe=gg zoSpNVDKzOGOO)yvf-hd1Lm+Bj5U4a`E+NpqF)Zd5WKt4 z=txw%DBg*qafUR}_u>pmTY8cbK=0yMFXjh06{>TxAEDn*{zn*`dUwX3V5ZwPv5OAad%uR3HwCiqfcEzskjdiVq zc-q-;YKy=(GKZ*eV4 z;m7hVU!hwEz+zsJX2GS8pb0xj3_ zf&!H*_pIfvqtA{hY_iWWPjI=TFO2P2!8vDk4p*J~%;c^J_yv)RZ=&)ASE!=ba~oGi z3qT9Zvt^Zp@#A-S9($|bfrN!^ZEiwwvTeUBf;m}MJY|=6ao4uD&i7=Alr6-Ach5 zRRc|I*BLDd6hPjD*c*Zq<0J^~%z1NRz%C6>cLtuNV>@WAh4!J|u;;Nnag7*cruMR+ zlz3}pPeHW!I(WxIERY>9lrD3bNyyJh#j;ONZ2mCnr~*eE03#U8g3|QY10u`G9J%!v zUJ*@+$p5nc)I)`nT|LId%o->?s5Iz)Ygp*F#FwX%QQ<0_01;EALMVr-S*&Oj6JjX= zf13h%ZVYs?Va0D3i`q8k=ud?9JGVsugfj+9Z+!38zA>GJ!e2Pgrl{JcaEOB|A3BHY zOjoN$h=FOV;9j?$g$T@+PMblz}TX$fide2%x_DO=SY0u7foDF<6?0y zKlLInMBT&vH}7=M_ZUlS>yQzGUPZqLB0!rpiXU;Yc!f{%OM0l zf;mLDwogq((;SFvc^%1zzL%Io)-~b}U^F!on+(Yh^9e~uk0l;jZfiN5lC0=BVkP#hu z+6h;trgn&6I1o%_I3vGtux11L4bgnz_G|cDj{e*&x$DeuAu}Q@uC&h4Z+BtKbX}`` z0{Gel_PnW0oOKSS>jvX|OXRRn4#9YaC+TRr!Z@6~nM{39=aU2gY$eQ*85;bW`gmG9 zRX}|XgW7}||FYh47R+#B1eFat4%8VwBBW>nV?$qw=npCk5cn%SY;QsxxF;Rq@neED z89q8hd(IAM{|o^e^vZfzB%Ri@U|Rcr06E?IbU6U z@_<$Q^?tK8hXpr(aBqU$!W@C>da&P_A@lS;@4Q_~Y*)l?)~K?yK*<3ZW9| zgm_AqI!7&D38ZT*D7RQhJYjo=KmaIwDqZ#*f_oSP1kmV_mNOZVvm19q#YMeQjw)`(@k#Pqxy@k8OU*tC8~6eBf^A zO`rTz8F9y()8wL=i&xDuo(V_a`V>E%cZP(fzSNR`Nk>oN1X6c2j3ad{YEb8)udZJG zQngKcuu74tb+#pkk#@k@HKu_g<#wG;y+e}K$$S>iXc$gf--O_N4*DhpIu8xk;;9gP zB54R9gWkLq0!daUlW1YxQ1I@n?CW3s^MCrcZNve6EA4ovn$fH17@vNsy?ki(82Dry zsJclzoo*VNOv-8UUEAXx{;TvGWJbQ2Aehd>PdFwPryvk5t$pl-8|5?QeA7-4be&Qp zGsRE-WP9tGP(Aaz_k7V--r>sIwaI(i89a5Hr<84)Q~Byj6fIJs*fLD9ang()f}G~q zbBHrlj5$Bt*@-J|g_Srclux{~h}h`>8~Lya+2swd$5}nTg=`o$Me0|?pn~0i2# zQ)kOCx72t1IpHdX_^F)hGOy9Ckn7MX+0*Uas-$N!r-u$8i6Rj%>ILUsOH4dmHp4wss>Rrl&a7UdW+hbbA$n03J98}lb$8?^s z13!2SPE4OO1XIn4yfE6|ulPDJe5i#byFz=E{c^V^kHwBv9JMhlCUuq<2?3DI^=8I% zi>w!kgQ@5u5EQfm`H({r=G9xzW}tZUMj#l_fdPImJC8NN&+=|bJzJpwTZK>z1D9r^ z*f@S>zPi!(xK*_UTHwI~3sv?(J;%jaGqWb;uc9xQgQcUb8hZc>c}4M6 zPmgew1y^j_k|XNhtS!-iUhRs$V3RFks9(zk{uUk#!QAmN&jl4(33J%V>@53P^y-5` zsjccOCj@meIs+c>3hovqguH5>UKcMX8HJ^A9Jz6ji~PA5Nvp}6J45sX_!L4x96WDD zLwI@u&^zx;54Q!ImTt+Yu^tM^v7+YFUlX@ z6H^He%;UY|Td?P1Pw%nk^2f&f=|h%1ziort>|6%|ya(h(MF*#)5UYk+26BqVA`T4k zw1E2;_Rg}m2N@U3k&~QkmD3=QCr; zr(7LYM6IAR4Io)h7gSRHrT$CI^#kc^sWsQ142M01%gt?EO_E) zTCP*ruiz?~3W+jNz~BN|Es3p*1g$iI(T>g(Aq&WeGo&==B`PsN))SJxTR3Dk2x-vm zR-8NWV8b>ZYM#Ow=A3Au0EVP(VYD2~#{CuWkF1rI#V=$Y< zAE=uXx9yyoMU`i8*jm?_-LYjf_$_KmRsR4MkT5Ypn4uCYbHpOFRn9njz{Z*zCiIdD zl(1d*eIRyF!}3O823R6+ytNe_%`=;OjStsV;EJ5clR?4@9HHP&K>17uau(uS@i~P} zNIt1dPQ>BFWW-}t(UTs{($;3xAX7#TVA%mN_;k+Y3pg@vSJAAVrBc3JHlD*DrgKf- zG@WtokmgaU?s~!2f)F@-WK3TQSb6LlX!R%WX!_V`lKc9ZS$!;*e08$gxKjWQ#!tTX zYj@1ZjokoR8)g#+C$f6w=#hr2SV`6RMVgii31MU!bv|2L$^(MnQwa-~>>^na3zKuS zPZ3dFmXkA#yKF~I`W`Wn*5!TEOP?n!MYNiDmk;H7Kdjwh&4~|5Xt5;$mv;bP=;D=L zlg9Q3q@{K=QD=;g%rZcWMon|8so`}Y0>pI)m5_y6lGsd~QrZSSM%a5kJyEMkSfEIP zJ0!x;JBI^ zwAOZxSvyLmX|MyX8z%Fcp4oITneVb}*%a(~ayqFm4}+8B6_ty2&pnLtqv*5hkkDFt z&h_nvfi*~e~iP2zJ~+OryBt~=5xA9w%Rd^W~s~;TixWQVQ}XQSh|b>h8tbJ z-J^nI$Z}-05I$NVrqRxpxR7Q9(b1=xM@U(G&^Jf9U~}sUcjstroalzMf6@i&~}p3M10I*TY)>& z&obSB^Zk~SWx>&j@+~Lg@>L#ai+$lu*ldQCWV?{PW3mb>t^`4eucb2`;jLD@WK6vd zluCjmn-vgR6%db^4Q<9Kw2j6&<^ZC-0o_Z{mb%A-zjjBn4B5#ek+rwTAMZIIMWaS? zYshT2SByEf4m+doWgtGw9P>I^3iF=vc}oirEAtSUt)C?OTJma?*}5T{MLIKvPZC+t zpK_SqVDQBp$DCz7#y^gM*N@0t6u%|?o+hIu39SbY{`}8B{P@rRTsFML@y3s2CtZSn z^DuHeDTG{~W@ZpPO-4%+S)Y9Jz3+Ye;rG5LJ3(;PZ*upp3Va?2z6JEd-5>pLk$(7X z(GRcwT%2#|7@2JGCMEwg{U+(FOZ(0!ou#lOvo*yU^H2VzIEUuyM?Z=zxUo^-CBFNl z#+gc=`Ty}|9F;9XZ@sTI~viT)6 z^(1+9LpJN;q}CLFmf1=vKl&&6tH-=AywgOnoK9ja z+CwG?Qmjfq2k8?a5MN)?Ny!!j@$B9RfzBv?%()}&f+!p*uw5?VTmB|h|%Adn>7vq69i17C%L^)hD3Ez(xZPb-@<*p7v) zg=-3Z75KshMm|*m`HE@7jxDjp$*H6!-aXc?fZ$z>1CGV7=J%+5y zdPKT8EAFL>6h&|7iR0+aCS{;!CkYV1EK@!{=1CUgA78E*(v8KiW};mEJWoKsY4kBm zhNv(__m;3TBS|q3R9V$diIt;1^Dn^U94}#I)_SNActzGlXkFpCC9Sg&cI%08h07hJ zQejyph}nVCgCN&8H(@Z17vmos1+eF}9!9r~XG@+F>!x?mbQBXPjim*0C~aI?>`Q#= z8YZm5mRv{?1U~5kR2wlY+YC9fV&WIT7_uk{Z!sN#GHWO8om$zTE)#u|9OxD`?ZYzn zAq#bFbzF{DEyV2JwW!oUsU$hL9UV~xtJ~6XO%iph-c)u0Dng1k^EHNk;3WB|;$ z5m~i^YOv5+F+TP~pYltqV^7+3*wIs| ze@HRYGXi4HJ#7k)NNOF}43Q7v4Cfe#gzK3tQD8y$GmFk5QdAk|mYi^-yDY7RN2&FB zT=7u>jIb-*wqRezxoBNKM(=VKtoT(YaZJ6;1MMqy?~V81y^Z@3oTaj*?bpq+EIQpd zLdvFF!!tSM8KH4_g_b`-zUHV()(WQ6I1XTG zigs)O92NttpB|N!10=eKo*gompA5q-hhFpTW-#cWP`A+FPwk&dIRmr1wL55hsUFcr znrjbjPR9||-h_1w=a5T)W?!Iv0->N`(=lfq!{F-xi9^2}Y#DmkQ1zk<)CK~gN@XWR zCx4{oPbqx%T<|$6145LpZ~PFb-&W_OID*%1xz_{AtKIowZ?+56v<@4mI!7$f{>H0eN@BF1>bTfJLxZwDN1_+kULtM*7xZVsJe z%nv8i5ieT3s5mLaz7%}Zj1xnqKvGo@^xY09hFx~Z9F5R0lN<_0BPgf8_OYalCta|` zZeqa2gPKX#C?Y?WTQ^GjgWnC<>(1|5h|1O4LDgfQz)=+siLqYb7@B$mM}Z6$#}Iu( zZ!n128my)>RLbDDv7}x-60(Iy?&w1W45VNNM{&i0+cs6g4lP{X6d{(z6DmHAmYs_smyclM zi7Q$!+Hi={$ttLJROLXm(@4y8$00B19h>cN3?))tT(Ro+h+k6Nu!jKw#_RQl6~lsF z0C(typ)bmi&uym9s*G`naH^!VWuGkU6!JxCS9fMD4lr8KZnE#T-tWqxaM`IGpze27 z6+cMBy3W0UIlgx~ZIyxYY1FmGsZ4^RC3}iWLF4n`oog!Hj5K+SA^Zl_u5>aNc3sUw zZuhgtbopjXG(D#KI4j&tPt_f5~2uCL@O7x_{61V+m9 z7bA5&X}L^!BS836x-k}i3j`C5Jkf{W{jMPR=}#pmC(1aU5yCI2IZ>W(y_M+uNy}w| z013jomnGcR6Jy_q5GWty)8OeSU9w{x$>JBkNPbQ=anHvU6su_Hq`y;CwKRGrNw=Fg zm}B`_Ac%5)K#*+JQFVXot>o7oILh_YPenr~2t-v&qi2$IyNQEmfq;cv;rXWQxUGX9 zA5X^zh5&5V0thrXrhIN=SGeQHw68}r`qf3{F4Q0i}M`P)TREA{V#&ivz_8uS< z%g-4y?Ndx4+skbMbp)uF|LdCm>%Mrs9{c(k@Mh!rV5uFP<%14oTr?7jiL@SdkE@KG z<>ZI35r0@(bYWurjb{pfu4hT=rSH}~XmxS@%|Uy<|HviPj<`}{LwxXeHQ(&I^qmE~ zsGp2TzRA38I-Wg0839FV!n)X;u`)5Hi_cpg2wos+-a&FSH3v*M8GuF!GU>F@Uc+6e zc9pM8wR1h|fffm-mS-;Fjw3B(h%`>_Rd6zWYMOZ`eR9`qse`3JLBHd9;J5UEmCL;( zRHkuwNySr~1{s8EplG5?Ayh%`uVJbk8L_^ToVs_CE|M?mpUrDz<|WZpkZ(z(MCb^pE?c=lK>7#3U0G~j;HYxSB_sNcSfykJH zPOduBtlRaWYI7v-Ov~c9uaLtkdpc_F2`@NBQ%3yIU65m|TB#O+>ed+Qp=?_Zt&HR! zp>>;}2B?<8d!*e2gN;5M(Wej!B{}r)*uE4k1E<0?9>yTs(FfHvw!UiB;U;gUA(||A zgkT)K0%Q4kJSNAXpA5{$(Zs3ia2-AcQ;Z`+XWY^)cd&vw^j;X;B&c1Po32J0i}}() z7&2=T<0P4dJmZc{Xj1Ve5`C1Tcj9{rywM;Ec%u0h?795mU;IDpx%?y6(+?%HMYB>B z?cX}%YqtZ}T!v6V$4h3xxs2UsNhb z%%OJ1s1Y5v3NaB3<)wSj+`1Gu?_2+RvmvJXb5>FeMJdJ3(&AZGjf?wtklocAvTes+n6Q%kR z$yQ74t)s?*&rxfQ4-ZqObwR!?pu}WtQI(TSKiWc4`dtT14z9kXT*g<~Y&v6t-K;o; zWx;tqY}%~$)v2sI&!-}T!_)?EssM<<>~}Dljo~GDVDuW#(>MTW;eiqHj0P#@oJAQ` ziyQYLi1D|HuQuvaxDbpPzCxh=>;9Yaz*D?c_;H+fjqt&mlDG(AEJudy8Xj)qTb*+0eY`0<*bU!>esf#(lUPU34&z%AT267>GtCh3j8I(dyODW^90$ z-lH^%LOaJM~>%8i$Ll%Y3ffJ21cxnIIU7X3unhFP^yU z1ZbAUAxeU0HE695q(#=Ipd;ZNa?b)1cIrwf*|}lW!|QF3o32Qgj04H_E;nAKgQ^(H zGk|_pdf}Wh#ysUwaPrm#WNku#$I6i@&PWo$(QDLHX4fIvMtSaJoWpJjBj(x6DHoTQ);_Y=Q!j>YYwDV|fW?|c zS*`{ef_QZR?6NKxVQ!xE$ecwyBdr~ zaaMs4xwK^MSisZw1F~73&QGVW7hZG6qK4192%7>3ayAtM{X$(gtl+ZBzC|{R;9E^V zTX_gUvZKL#GC}FU&Q^>sODB2V(eBtA>(-4%z zq?FP543D0H1}6a|HhIf|8sq^xH6r4f8Q&VVR$3Z+MHN%51&z zPbXWIiZiJ2(0=$swM}XTq-CT5>Y_#DR(x!k$ZecrqkRMCPxnmHRtbVX_$zT6 zf4vY~@R!Z22X2xwf8V_7%fDVc`5vk146zxtX8VmDPZ1NtO;YJhaQF1uPbtK2zIRVr zv$dCzc&hN7M=e>2mz#L6=yv~Dn##nN*16fMcyCnF2z|+G)d-q$e-=A(yg!dxvypjA zJk<$;&p%HPjJ#L$ubm-K-nz9ZX@tJywLUKdsylK|=(N6n|E*sn9DH$K=b^RZBVE|( zOt9kVkr4;!B(St-r#iy*g~y(9iU@cv9HjT8(>g(*JSPY~|NJHdlEhLgOb|$WmdrPS zfctzLb92X9*cY8XvqdltO%3culQrw7%x9t9mLc1l!0EieA2HcJ3&dM~(Kv%Y@S#pi z$SL?)Y`!pjv+E$4gxangOVlDmPjKxBxBI$aU(&B^@UUxq;9JfgF_O}OIs%d+Yufh|-|%{Z z&Q?nTK&uypQ&8Y%offaK%oZjOxk53Wnm=UD(w?AHK~FH<~_Q zGAL_D*sFBZ&mH6kFHR}L!hr_HcAa zOzZfVP|{1ZK)~ZtfN_&!^grFAO@nl1Cxh)(*V5|n3=34&lWomPK1B=1;S`?(_z~Se zHx8QsFDEETuE%ql&{9id+w_`CvUWX9-D-M)chvEe+>D<57M@Z#hyeXdZ&0XIbEC6g z`pJM-d|0jD(-KsUrx~UZ*oyq+aCUTKQ{b;s$J;q={Mg1C?{sXubOzT}EnNAJXH%tS>i<&pIQ>Lky*Rf-pJ`V_4=1mjNv$a`ovkiH0{S*@b z*|$Y@qT$V!$qx(7&$4sfD;3RvUjPuIXyc7NMn{T%#%IkZCcwK{+Tg?94UWFm;a~VN zKki&zvpsj%EJ$Vw)+e>K`uz~ zIVK#ptJjS$jI(v@`71Z+zOI=Wn7`t?^g)?PD|Yfd^V7m0p%D-CD!tPcj!z?~Eq0as zi!7@&Cg>{oqYIn19>bMJ>7zvqsQMb_;l=$=cX%Ja8SRJ44*yR^+x%AY%X>}rpIv(M zxy_kA)GGhBA6aVc{iNTy#3{u&GF-0Qux`#O6c)?=D8qH)>kH;6nO#! z&sVrob6#npWhXn*wCNlZ_diYdJDu%2#RuH?H2Ss5iu$j*C(Bel4JyzUZxBv0SJEej z7GpFni?;pzUeY!7>8DYKjho1X1O4mn0-dm$a!xlw{pweeJ5wEn3!Ms@OH>J0$a+PMUtgEzRUKuN6ME)G z?4}!`x=wOux*;m!0tn_3Rl?PafnXP>O3P?YAI7*R#tO2z_T!K_e=^Rl6!Q(Ad>!b` zanVXnHgT9$26H(29a8QtLB}+o{4eyW9{-y)=6BxyZ^kG%wy<;TH*oR!WYOL6q(9WkXsHKA5=cEi^^++KC+CNn7^gIkoBr{jCx+Cf?o?zI z+g$C=O0*RbP{nIjD76QuHi9)_psgOyk5pgJKARIZA(dG#ao{^*oBr6?ieF}iL90=U-sK$&yWp-gkf+gk5Qybo@X@P_*maD zd9Gf;T$M+^p6NGEUAZ4IlH&b&<>@DiIK`y*#!R2Vn%;Vbq%S_nx1eX5|AD(pgaq}> z%Le(nwbdjd{?Y%BP4VCVi>Iy5!B~E|I>*1b;RgdVe>U?MGyk2L|CO14HS_=0%>U}l z|DBot-12M{BO_v|IW<+@6P;xcjkX{ z=Kq}=ei~@zzu6v(89dh9jJM_{eyzEQUu$mS*P5I7g(ZBWd~0sv*P5I7wdN*%t+|O` zYi{D#-lP_D`&fN$;`h!h{*_t%UYXVJm0A5>nbq%=S^Zv_)$f&A{a%^X@0D5oUYXVJ zm0A5>nZduB`Cp&a=k-~AUZ2(H^;vyhpVjB}S$$rg)#vqDeO{l{=k-~AUZ2(H^~d19 z*hs2|K5xwG^Tw<`Z_MiR#;iVX%hs2|K5xwG^Tw<`Z_eO_uj0&_h$9IH>>ZxS$*%#>U(cipL^zJecztd=j~a2-k#Ox?OA=^ zp4I2=S$*D~)#vS5ecqnc=j~a2-k#Ox?OA=^eylz>zb|i_o8OnW&CT!2+veu?2KoWy%|2i5B{ejm*0 z_ra`wA3O&C&H8;X8=oJ{=8F$z^Th|V`Qn4we8Fbc#LZ9pqc4kInwUCI$=?#MADhzA z1-s_gW@1^Q{x8h9&2Y<9OoBRbk9%r4l9C!`=uD?RLEh< zn-3))NOdPY#?G2{=%*wv+z#Z3j=IULk1jdaVYETQOEuC2erd95^kwRY7{1g#I$ql2 zu{M5qCzXX=%k?mMU*eOJzJJahK^uvv{F)8d+FzIW@Bg3w$5VH<@)>2En`^nV>|dk` zqzr!tqnxj6DM0l3+^q%kEk6pyOD2D)HA3!A0>n=?AeR&h4A2UZig-Y0UHOzxBOm7)>|%#C!oJ zz7HBujx*@=aY!GCvul>*1$+H0KlS3&lh3YMk{9dEBJE18cVJ`5$01z*CHFHIH-1=B z`iMn^OrfQ8L|E?hvBo=&0P(lW151M)I!-vfjEG&w}=XPNb!43M*Z z2}O@ts)`sFpX0+^a_rEZ3jnP}SR`Nj{?t0+;2X529gjcpyqY%8^mjEm22 z^0?WHCf^}u+3|F+ZX`NP10?EU)mP}%N7M0)mVGJ^YSrg72hQH8Wa&+bgyKnPUWaXE zf=(jcq-CEo^JhtpPHW+QDoKvW#bE+pGj+?4;+;65AE!zES@L~)8s$d6S{oN|Z8R33 zlI1?EIQ4`kAe7wnbPdihM?a8(s~2d-+;&}@|5wbxnueQeUU(8?@|; zgJD7`n-53z1IH;LO%0fhx>MkvbE|Ug6^dVX>AgThj`TQm`=MK<we4)<4>lKiLX={b z)q^ikBNU6Bmlz8=L-cYWcz~|%CqgiXDEePy{gu}beoz#jVy93Vt#x)QdI(Fg9sX$6Ej{Q38vlg6)Hj_LB|Kz6Rz%l}EnS)3G(BGGYucN^ zF-$>j7XCb5QQ$X?P&^Nn>Ssw6Oe0g6NLq2z+LU(DYx=!V1G|X)R9eR`U6Q2wGBRSV zrJ6zJ1-F1BN3VQunvtHPmfRS%cB|Iz92Xs-u-f?uLp*WnZoc%5e6hu=DGTnpVg&a5 zjeD6fLgV$t%?a0qDR0JCYEo`bj%AIcNOY@JEG(Q3I~p~@H|POs>9c93-d+=?pKIxq zq*-?kyEuFcX6gUnFaIZIX~2^F&v*Vu17Pu&o@ZaeT~6%FG0W?0GhF(m=h+v7el3r{ zNDY+K#;1;*%IuzfgibSw?BtI8X!H-GmqmF&|9b0pTse0r4@+*>BKMXtk@&O@TlWTN zgb3rJ-(;%Mj-Ka#-00T~^N4 zd0Ku+s-wL=vYjxnuV&lg}DrBcwHRb+%PjnVuQR7#Z|Yn18LXR?zrEwrM$r4#R%oHOnWpsEA|N$Ny2o|LEiq#dug09vt;{HhTA9>S)xt~*9rqxvw&+mSMf2Mb z7#72!0}mTgq`F@|34DID=4P3|Yf0KAbad=f%%$eZ-3(Iew=KCBkvkhV-O!oCZd)z~ zuzD~d4&9jg8$5C}F_*p$!7|(KsU;w+y)x{LH#HY8meq1M49>7}bKHx#@Q(Wt!O((c zN(fNuZ(`_9S+?6{U0L{E?Jqq~9k=Pw5E*K%mb>Oq4h!9j*rUICZH{HU2QiijB;VX{ zix92OZMl{!e0usq;#9-QZjDjp!>;xN8oo|^l0ZVm_N(qe1VbojQ~6KrY)bT1rVd;j zod;TA*#=E;VegG`i<}D#>!EQg;+kPquD0mf8}JNfw=A49?gbP+0k&GG0{{b^;1~nz zrV6Yb%3;SIpYB7y!+LoXzPAN{k}>jp;%?sB&x8C>(gzWCz9kFOp~_aRRAB8$lDl~7{5mH9z1!d&yFL__1!Hyt=ykrPQ{6f_kng-8Zq}PhV*LnmCox z2Bs2|uI5i3q`Uv*7eD*OL(p|o$&>x#?`ogm!;h2B`srRobjpAB;DK~>KltRqCtdfe zUqwxM-vj}?OQ+K~(7r_MfASln+b4ya*f-+cj|zYE=(GFMP50r)0PsK%NXOkb1mKAf z2sm(KwNln6AhWlW zC4Aj=0>fe>#c=nImOU;n#VRu>(9Mkt#N{}eJoiiVe~X>UpVccYi+HA!LkWiBI$L+` z^5m(%XE1@qpyrT}MUa#21Jn_8=-TW<=20un3MfOoK^TZwSfT^BFT6>$tfB5j*oZ7M6AeDof)~c2!Wf?9>Gi4~0x$Frop|&1T+gTxuaO;~b@YUs(I4t==xRN31KwS*4(cLv4E>4Z=coKYM*Z<{1Rb37gbMA#XP%l zss{%HX}TRXGd@j41{`1nC;2Tl^3?Phlkr05r1RLt)v^-MePuJTs2YmMGK#>{_Y{DNV+8@Dro{?3V1YHnVxl`ef~(QSwH@;zS3mO zO0+i&s(rufnFPvRY5I6hC(HI|!_(Uk(WYD#S>GDlx9H}kSZkS+_FN>0yM!dQA-VgX5nat5t1jDtr!jYKttO=OPfBlGz1fY&~431D*uA1m!hGK=MJfZ z-No_I*NY<}cMGn7>sHgmp&a;r^1w`X@_p5Fq=^GJRs|zqcw%a^IxfO$QSafVcp?Cn z6j~Vs`Y)As+o1zFnM>_x90+JZn~N0gwQZhivMRpOXO5dyO^MCYq|@+PXR!2Hon*?R zr|-qfXVomU5kU{{DB>&ahcUrZ9~U{*N__1f-+XAyPsZ?{Tx-CVyu~DQp>^|@cfY)w z(raOT`AeZ+^-QV%=HbnHIoTI@eS&j* z!=sv5&Cr~qqX4bt?uYI92XrF(6@1E|EdKv`i+kPf$_VxywAQ0cc zM4I^~1eyZ`;g5eTNK>6>m3U^aO1Ld?DnTPL-D4$M{lkx$HtxUmR=UAU5PbK$FB<~Q zM%<}|p8GijS`sjsd!BJ>mG?JdET!rvvy0C@K7ZX0KQBG<`rT7pVpT=&T{nI1PSmrQ$hPC-w%6PPTLW8K8LkWr}A$(Q^j z>p+PRsoDWem!hzcR~SC+faZF|KLyoF^UQ|pTfw5#hcAl*AT_+Hy0`Q$mD|Mz_NL*! zQw#t^800?aA!uHR8MDpfF?aED;u1E(ps1zu#yIwXx#)%MXHM~m@`seJh=AEgp9O8q z4h7IhP*sa(F@4m*-H}xFRS$@-HT8m*#-Q+z~ zeg|RH5wk0hmq1#stD9BxEm${ZfBc`YZlo4TW-Ie1n2l;WBQi=7FjF3|2=pnlE@&)D=HM=jw$#ot#za9dQRK!Jt{OoH@A ztA1$gi7VbVs{qYNIC*;pxv_!nZ;AkK&ul|O$>RA#a~r76wgdltegtllhaR9M=PNjw z#Ic^is19+2@}g*Y=E*U4eFu;d1q+6!$rSI%)Da#s&UV>`YSckDR@D(U9(u%%pioKn2=&EkSs$bfJZ~b%!q4qyU};2;7d~xEn$ugJZ$xj z8oy-n$TafVl{qspHBWs9U1`yUjNES7?}*ip=G15GwmJjllg;#b^_1wHS3(B@xy=h6 zIPNw5q9YEDU)|A9yn!dp<_M^Ey8xRFVE}rEF3T*}JqZxbpg`_f*LRcz{~vSj^D;@2 z=J}bqo0mL>9MWTFbP*ab*NoU+RvHRQ2OUzBc{hTQ*nx(j_Ry@@?87Fi)3VSr$s*kC zx7lNlnY-Kb!^|eRK4|W1y(X(X4qX`Un3pjH>5TVag8hI@TZCxJqTs{S!d5O=bIj4{ zcunJl3mu3mg@HA{2Cz)X_g~>=@A8PdWjq)B&f*#hJPK8HJr*iV&iI7uS2Qj8e9qgd zD)`A9U~bw@4bqIYfv<+3!4zZ2P0~CDLrVN>OYtxk)^=YXawWMl*Hm|Y%FAv)XG>Xj zp7|J@&7&I}axRt-41UlmX8IsCP1h{D{lc%}L2Kyp3C?80vGTAUsXTZGx7hT-%#?LC z=V^0&&}Ilznv8gq6o!!>&uZ*APq10h4r@JQFxdq7O;hX_i{G60kvW|)u{v-+GIp;0 z&tnn^`5tqfTEv_zI4?xf)$#`hvPy=vCm)=>1y_rFBN9*8Y1y5?h33)W>=XoGIcvTt zmyBBYJtk7Ey5Y_^6NA18j^`^xx_3CmGRCGpO;fp?OchyU48w6}rny+6fe&1Q7?lx* zq+)cFR|6xr0{a7IXmU@cHA#krm%mCF=n96PbskQ1+81bt*=1oW(=s#?9n8LiO$extT48iH zT`*Pi{=j~cOAKfA6)$$Gi@YMKUB+jw@ka~;UU$)5M?y4_tWNp!XIBoK8hj?AkA{6P z@T1{yqZvkchr}k?_Usyv#Oq=#LPi_8u>JL`f&0Uz^xI>&etq)OsBW;Hn^0*3+O*4Q zw9P2mlrC^+iCs7+vtY%Mx#tdlrVQCH%-)5|{bBR+=EOy%#GlP=_}{yR%Ql~x;m*$v zLrkB)`$A`>ttW)%yeo(3NiNYf{@zlmEhTC;S*K(36ZPunfASM)g-H|avHgQTd-W%r z?groNZxVu^0K%O%m`;q7M%dLB1UlXR`P;9)lHSVOuWofxa?@_wa^L93E+txXQp8s) zLe`TQwU+S%0xkDgXP-AI^u;ZQNecWUel3gH0Q3wSW2tpFR6O zvUXVZ3uodxY5;BA-RE!c6Y%Ekn~&&)ZO1S{C4Dd5AMT)~8mLElNYWluI=Gv^|NA7( z{*~`|3jqMYZzbUye&1_uAy~)5(-4>pK}ss{9s>CV2|JQ~`Rv*JmosNd{whY;dW(o) zH9${^Tgl?-3nv=$8x9ovPyUH&Ub(z~bqIv5?}OmieoYXpIJk%4H_=|puW$uDG$UT< znumcO;nNrLU9){)@;Bq%=qE!|lD&TKo=ou97wF^TvStGGiOjU}^;@SjVgu=}ug-LY zg%(6p6cuUDW`Gva93cYrc&R;~SNw72duhG#{={c-oS7RGOwDa?FJ|Wr$yQvG^ww~0 z@_UUSgVaN_h(ss3qSA(pEm2XLeR{9ZgIxMGXmUlWW5%V6U(=Fgh$Cq{QwZmc4aOwn zLY3QR$fmYxW36o3)w;VEcdmd-R4}FRQ~k1e&GDNI2eK2URst#hKxknugmu0XG6Uhs zmNsSuCJ-2AUW@SdrVAM&m*@JRvBX-nf$@CG?mT+BmAh&^HH4~QMo^pm=g{vURuR5LIkk9fD@NC6)*u8bt7 z2Jj3ikUNR=dHO0cJqVVB&GDcGI!Wk$)FFi?P-{rw0~7LHJM&0$@f{t$ZT~DKy*2qw z2gMo2+6UuDG$N>1!#RK*MqUx{7YxzhM4OhR3O)j2pqf4FtWZtShILI}=G}o9ZiO+C zu23gtLyMlU2Wp{RXc{SAW6#F9?I=H0p=(+3wy2Nc>+U`AKDh&XGtGW57cg8qw1fC* znw+rdI^e~~nGJZm@vUJ`-Pdd-`LYWw5I4KIhXicrC5e~rOqi2s<=G9{hFC-Y=EU1* zT$rkR`~yYDwfL>MtDoW6K2_~a*fk$1FACjBXOb|;l99TN!+rI~JKKOg7PK$`c2bui zg~o`+3?^PLxiO#%Zo`L6BvO}y4B~=;(Nk6Q#-9NqLY6~Ko#R~lbcQ@6Vm|vA&tu8U zcZnpQ4xTLZ2c>|=!gvb92%Zd+mG3fzBG$KurQEz8Q>*hA@`+zy-30N+pXU4Sh;^8Dz%+fzGF!`(}A_x^6st1tI~ zvO*1my&Wj7ir7VZWWPIg-t-Zom2boto88h)bde#xI*&2zmu`qB_{`CWM&^HKGR-WX zUX33xcj;Ka6xVc6?Q|x<># z>5dN>>$h0j3U6jWbfd)uu#TR*DR37d|O zbn0&X@9z;}$d@U-?0WS|x>4VK@#UAFe&u0st!jhdgEeoFPeSnQ*%#k^>XkLDelOGF z4|KJtXw$UX>U>~BvztmfUS^}-%b%9TvolosPyb-EKg<4?Q18<{^?tYJ>q^zBT+y2+aeDn1;U*`-cEQ(&f)B1Yx=Cw*xzlnl{mxNv&tQLMb z{S^XH@$&y|Kw!Ro06_-h@l8YYaoE%THKkZeuP|?)zkUAs=PT!8v$d`*k6eZkRBVBH{iZl0U1=FQf+wuca;qu=T!z~LYSFrrR3$3AzUlP^lN6~Yr~w> z7%g2weoO4wEc_xXuMa`)Kc$x?%+?1z$aS zSRPH`P3t>VO5J6K-g`F^;s(dOv8)D@K*|c5?C&wqAhC=`Qpv^abESXqXQ0iIabTIm zTKp$!na!T*iV&P=7~KH9b!qo49eK!ED1b4fJEqN7MMOm_?lrA~DOnP*>Ry#za&RVH zlvt};+U2VOOfRdp0qnV-q1JxvA_3S0Wh;69Gki|S07Q3}2iq5|~ zKj|>i#2K(X6V{p%gX>+{9{Ur$zk{M#m=@Asv4(#0jFA@T1o`Z(v4R}PUBPn9Wzr*nyOb2LgUd z!;YFAF3R)L9tbcuSQ~OS(JP4tHv;sJq~Zn|ASu z-Wy`tCze-W9}}P-+rgK957zXo><9Hs$OCB`@9Ptw{>T`hl{W@x59$fu7PQ0`L^a63 z9pLPJ;OT&KWdkVowuQOmd}3sr=(E;0V<;x{1n2VDkry$Mw~}z?JA~om6MS4 z`wjI*wmu&6S^=U4nr&k+UZqqqiu~2^jkh5jpSe z237#!A7!Z1%y}eOx_Y9_G@H~@ogEmWf-R&bIfzL!Amz6}5>SL-9S{J9o#|PZXQsi5 z=8kdNEk~?4qH7lT>Dl`p(T=9`k3=5V6~rr&yrGwsgEOq6-VWt$jQc zi>KtsLO7PGmE9b=<$TEvIX-BtFNmivc($3LfOcQmVJ3vI zyg3ZRL+V+xh8w04WGV25q*l89a^YUIU{o|`YhkxIVd_P>%Y%c6f-k;%`+Sp8Z@ZR!gZZBNuT8&~JHLJV?YC+hb@Kx;Z@&c} zy0-Ri;qrfhk*##!eewKm_mXYblCLqJ3W4YmR%&rqO)C5;2(FdhK_LJC8UDcCQ?^Ai z*Y!@{2SKK}S+#UeJO9Q{f7?Clnm;dpdidMJ7#MayT(nsWT0Z2Ib$Yz#31<#C3RUoO-8$MVsfl;hJMfl!bZMYf+YXs zJH*v9d6?&yDpEWpls*8mW?C%EHpj?h6D>RKBuY`Sh-Yu(&hrhX#3D*s=3>IVr!eWm zCBe_UiVT`=W84bKfK%5Bog7zqvy<;|9Yj*osRYmh=eehoX8(C?v5G=-1c$$GS$_^; z-YF|H`N|**c1N`BJ2Zv&OavSg{LwL4CA|j>Pr?k0a=gbt@5Pq3-}QX7EqL(5HRl|6 z=Y6+FBaswr2T41M`tLlUS;^>I!V{Gr(~WKg|I-mB+@D}7VVH8QB(NiytXcv!#O6cGV?;Rw9yw=lmCYWq8Rx)msLq>oIEDS_0 z)wE^lc^s+JdWG`Npg(Pwv^qxlr9-c$H1yv1&@v)hYI1}I&l790%>{oA)VV&dAu5Vv zGjc)c(^GwsirP<5y|eHAV0zzou4%#{HZCys^fXT9*h$Xskc>Om?~88ckymccB=QTx zaS%X;$Ah;G27oQSu=DcecJ??Rt&}H0Lno-6cWmX9vCDZpA9_e6-U{022Ic2W60?P{ znP`ZiSQ{vWlIBp%+Sx~#Zg$Cf*@XMOH`b=nfeW&&hR(~sF5e?;K>6?OH5Cdc%jt4X zYy%RbG%g~Y9T`7HE~sP-j|a39?Zp=(HRn*)PsAWsrqTxxv5O!u$2HLdWL$r$c)4*a zGy_B4Y+`>l7r&fRh#?@+)JPjH#-!3AY^_5V8h41jTX++uy+dPnA9mAmzeMkPUPDVt zuI|H*N_Md~K_!UqfHL{n|^1(360IK|G6CHZ@Qc$%iQKdk+wH3ZW zz>ZgQT~3QB%h6rZLIeC**2f~T9tF-Uv~u0EeTszbg)dsZpVKrV2~QzGb` zNxB`9aL3jy6dmEGY~uiqO8%X%_IR~72!_H)6s3k`(zIu{A9y>gPuM$Cs?a+OBewFwb+T8k$|f|21&+Q zKE%aI5(RA`}w$gu^dGqF5w1c$= ziFQ0dhJ{PlEd*N%>8QWP+(Pi;TS3aE4_AAu{YR`kGei5o71{Q~V|z>YcW-Nd5%DJ0 z{YWzP{`TiT*Uls&P}V-kdw@X4za`b*|NF2qzLi8=RP{gq=b{qdJV&ej3#6`J$o;8p z(*7dS`M!k!TJf#;0k;qs;6(6v1A#_|Adqw!h*()5e#!GUS#RC)=J=`q%>4fPt^5#F zo17c!T^oA;UwK~>C)a-((0oT0uKiDL&z`!|V}HOWKoTy=_y6AfyIdQ@He0b)AF6qa zyW6W2nYHL~Y3;v$Et&Z@UrW0}Z4?Fir+=zv!My#q|Ms3vly1Ca>)JPl9{hk_j?G+@-YO0P5u4=f(*ci<$NyTdJU5`WF-;_sr zG9dN5<=0Gmg*!B{ENl!TDS`>-(Rx1M^B(DSQ{bww{owgH9&JFj9h-_x_I-6Mj^$j; z5CUD}nHEpHYDdy}@Z}LsYK|_d!a#$GuBU|e7<&nmZG!2h!5|y(-dlRAn!PEbq|Ye% z#=KjY_fSxtNdvGpmqhvK8bRLz<;F62!EHe>rkbxtH^vyiDnQjqq za|Vbtc=~P|N|ss0hJh(WM`@_sT2DfBVKWA$a6FUB^E>G7MFpsz3j!62o&6!(9d{xO z)E%*vv8{!&vn%O9i9W|`rXxf6jnNpgrkaiC?u7*c)E>%Fj7aDcb6|KL`tZAj(1lPa zA6(wF-e>wifKz_d-h<7EhqMmnj#Lp0UuiA} z9crR2xDST8p%*Vh4T|Uwi-ut?hCfnw1eQ6wxvj>=tHWRIjC)^0Z!poNzQNCw1HD_! za45ih4fb$rJQ;emQ@)mInm-cO(&E5W5Wlg7H!g}Xq){U6gJ94X2j3H6G%R0*_rQRZ z80fC1xY|K>oJ_8xN-;DueF;yb@$jIQm~mwSaP*x1urz1*GsKt>g7HOam=SA_nkIn{ zFFf#!P0}NPhrwMCgelvF?|j|XKCn|ENOp*&h!zbI;7w$wbgfrcwjzNfPy8~*j0H5; zq=5X2kOnZw;4j3ReX5GJJWuIV81~j1`lAWE-I;=tv4dB7JJZ?+W;B#0Y&POJ85kgx zq=o8xS_<}umc|8sy7xtxLP6Vccb@k8-~*skj;EF+G>g#QxVELt6TPJt4MI*3T(^pOr~}< z%D3||fp3Nu+jpl6um!r{ zqKHf;N9-lek$2H9dVDb`WH(R~^w&J!cf7>|l|C>XySMI=>p~UR?&cnikCi`PVsH!XIP#hTf{INo zqHC6}2=h`_OXbaAPJZdAiZV`J&tS%`Oyo%{6aDIVQaZiH;X236{WHcts2&frIClrS zWXnW-abOSB^yy)atmKXY3T{Zt>Il{c42~fTpQaAH*UskbTLxKjr{Z!tP+RnrK?z)| z)LerAGre1R4!5T^+s3YTug?@b;l&;-2dw}_VbMwlh08eEBVV}XbcQ{~+V1liK8>jJ z$&U57Pqr9#bN0gw=7TcwSo1p57`&R7pKY#(HsK)9h`Rsr)!u0E2X;=eg}l5{>1$C} z>YNgWt%R$6S0WQz&8=6mVa;{DE9mBW^(S9{lUHnCS8{I>bB5^6i67euAWEt5EywG( zUw`xZb^4_!6s>q_pgzxkK{Qj+q`mbUo&*LSaR((7M~{lA}GW##vaWOMsJ z76Pr}{^sl7{LNciGUE5I?_T3xph^7@f^~iL8ulZ}sax}l)pvitqmi#?B9}icAFSNo zbia|E8))G5#E*yew$@wrpD<}*b$O=FUz$AhuQc|oI-jE2?pJKDy51XXKj&19mGXsN zeWPEo|7?tM+KUq6GM)CgS5eF}mXT1)@-^Usw?qhT`- zA$a}O1_2W641syRLa^5Qu@Lls)+2j=Xx?fa`gN812B}(-xiIhw@+i$M*tEc3+^ut$ z-i3C1g;s{S@FmO3HymtraC*)<#;-Vd3WDcq`4tBr13^k}GuBcP7b)|Vt`IzX_7nu# zKlQ#BkjX6>I-l>?o5R-i$UG7BD8+*>eaP=$<|2d!ewr0LW!w|xZimA@N`Ln#6{`#(n+`%nF{xwEn`eC315GHzBTp5otY;Al~HU0u$cp4o9LWAtcIe)5{lbaDL zIYKc8slDw3-?rK9qzO*JEg#_6?3#Ix%*c)Y^;(pl2GLj#b zKQuM<4#bW8EOvmhnz?I^yl`PON$;V5dOA%>k#Q(}B9;kQ0B-LUN()i!nJdg|at_N{Uf+{3nsHM`7u^gBOIC>{~0Q%d=T6 zPYk-eq|g8f#Bf1uooU4tRA}h35SRzhHY3`|C_^6|P^Xr34wqxvBLqe87?^HG(%zRK z7ekD6vCS%%~{ZoMNSiN8`(uNUIWk z;@)-EBY*>+9-r0SUmNWNDd`>>K;H~apqGNrmgNrqnhB~mbS4Bq@)aSXE13fKO0|v+ zxr9sayd@4f9$X(A)v3}Ez;rj4+SgQ*js;g^N#n8Z%>W)|f$Dv3H!*EO{>!*)m1W+~ z*d*dH>X8w%z!F*U)LIlDcBcyu%v|pS^nMX?3wGq3Mes7*Q?3(y{J6F}?Q=Z)e=>ckP*+ zGqn$N(QGtv2R3e)fg!1R2Ayvo8lKTbe{8MrK$aEzH&>*2kNhF^@QxQ1uSH*$)r z-oeMXQNkgIgNLh4VyNm^P`GU6J;$i$(gc-w(`bfji(e+@``vBzl6(;e1XCZjH|GFt z!`6T20ZN#25mO9=hdA#Ehovcj65U^&Ddbv%om;?P5GPjA6*ShAz_!i4t=^ zPy@LzdU^m|x1KvlWjH5@nS^{A0l6JK(#gIlXHmq7;lS=^DTx)Ma5RHk%1s(NgRf0m z>bm!-UczkAgU?C{Z~>v^3cDW>2CHKYT}55$Wjzg7V^^E9e3o#-Gwp|lmE6ds;gsxa zXyl3&+`}rTP6K|l7)Iw#y}mK_lsGjLz|+{zNvjZ!uC5n{h_yLD`IDlMbd{ez78ojA zw;vE7_=55L5JrQW>kn-5!-OVQ2&7|x5^fkG9C5*;7ULnr{Q=w`K(eg}h^~njj$w&$ zH=-{zMHCNQh?Zd;qN@PlKZJ}QEls*@2_6}#mn!$kYsC8X|6PlbzeUDtdIf-k;XX`EFC|7j3xlI|N|{uZ4qjlDz4*WY~e^{1@d){a~E z>%5Km_rLr6IhqFWIYjZldL`+f=&R?iWN&M_-(gs2aqQIyf=^ksZOmWnu(BHliC9CK_5Tfe_CWD}mQO+vL~al#u`RKmYIC=bwN1rJ~oE%~mh0 z`s!Pb$8~)_1nSNALqK_dMF<{4wE8z3Y_z!K*NWM~CCA=m_-l+JZ>e(SP}`M9d&2=H z|Bp9YO&cx#Hw8iBeV*PA7DBfiS#Wocl4)#xzj-_{e2`028=pu0Wb*w%f*kzbn|&A~ zAb%ADuQ-3X>QF%ZWs-=kub3S|zAA#rIXcVCf0-wf(j!$1#!b%Gp83D0TEfdFFkhFx zxTek?I}l8sYuf}^OGr-4J#alVYPgN-#cUnf1Q?GQ9n1hJIgUAlC-j6H$#-m{kNda~ zGQ?p#&YJhF9Xe9)j9Vl~;FrvOol1eMFT+Q$3<7;F%?o6#Y^Dg@nvydzEfXqyLk=gF z5B%P=wlGI#@CF@pK^c4IKahEpqY&rNI&`+fubOB#@v~rTFmP}|z4y$+V4g(q>X-(x&z0fM#8pr(8mDCHkg)*(A@LY5%oMG|X9`mF`AXQ* z@K@RE`w{ETh*nog9WUh4(ng56b1i78l1|`9Z0Vp>Q3-Zc=D@hpA=$JowK}(RKtXc! z(~*}4d^>e2aCY23(s39nQOo~!w6}M}^L&!y3Xbq#i*ol_{zC&&klWAnv9WXQn~j4@zN=M$zan{!^99T;G|86A6;pHaJ*48Y_;7&$K zlPyZ^EtKway(8x%Rmo3*S@xo;G0-pd>va3*X|%dsEres)7UZ&Y@cco*rN+HYv(cj9gW zwA0rwF{1S(qnGq|(^=T$)5yLRLkh+#M)+FoO{%}{f__u%Cpg!gzQhe$A$XJR*T~rK zCFl1Lta{LYy%4CTzr=`IUnycyuIuh*ck=736RvQj7OWc%wvaXaSHI7ghY+Ns(*9;4 zScwV8^i4)WLwB80uKsHvRwg?5U}d9kgy+{1&R5Jv=mi-v<37RijgDCkc^V6$XXE_` zBbUQ>EvKiH=&2L00+@2-pRpdFOw9N7rb%ZuSF2r%GA_XR4~5Th79fIt(KBTUYR~Ei z+N-&ilT-2GgCOZc4KKYP?V*T=bJe<71vk_)6&;Gvc6;W%l7JV{4a{P^>&#r`^5kMh z)tYq{%-oemn5(Z-g(0Vt>mGva;|5}^2^SJI`w|yAYmkz@9wzpKKJ(EB04{cAn|))M z3__*$WziZNXHyPV`qi2O@$=*>*r^2sfReQ_Pdbc_s(MFlR4-lzG2I z5_#&(l3m;Ri^ta2>J+ zL75riS}K88NN_2vS9oJUk%du0&$q_$Of;@z12^~puutMVS3~6{f0}?HI zww6W#MTAae(nN=@#S#O2S07Ms6P)H}}h8OR#Gy2*4wUXhEK@YrI6r|6>0yHKMq@-KQ*iOazFq!{Do&bFo^nRreePdho$J_V6uH4}`{?miwHxJV^Rk@TU< zVTzY22jl(Zvpe510CG8(XpefR-W|xdoDBM)UigpW#XvS>-0cI4LP=Hlc~CB{no+MF zO+g_zpqZ^)A2j|ZHoJMmDAjr6gRc-bRcr-)U}w2Y4O*6>^K;+#@KRXrdorP~;kN*M zD$fOz_ntu_*|)}H$0#jw=@)e^015vJzj|cLD1sf-W@P-5S{vtG4+UU3U2i`(Ax$-G zI}6MgHyObz*}evC_pGx_ zZVaVh5=o`Y;PpIUpo#!dI^kL8#OylR*6xEV6rbsRY;7=*Yq4ocePZ&iSed+bm~v7< zDB;&t3SKaPH4P}u2F(<_G1}>sju8{oBWcNCNObpjx zA4wBAu5Y#DW^cK=Yxw4u*8oBhTr|E5`(w#_c;P(WG9IWN-HLcFy2$G=9;ziW#A5{8 z2Bh&ka6h>r0(4aGinSDxk8?Cy2IJ;V{AzNTXl`|$F#&~;WK3%8aX~a$@*YlXCC1ZV znC57A;_@LxK56JA2AXV!`$&g?btl(~=7mz$en^zI+F9%=|Ne;7lc7x~~XIu(zPLJo&%6!w;9)!^r)qzpDlQ)j7FHA;TOesHW=zP&#dw1?5f;yeXF|8*(dcuwM!Wzg) zymxuvDD1+cv!D=uWFNKT>1Z&;GAG$N9hodpX1_tfAmyG3mANV*mHILBSeEyspoUMUCsTT&s)fc>{|$+6aNk%_z-lT61S}y-|}q!$M)lRb%K!0 zT5eWSi$bJFB%nsiS^Ir`um_a!$zIrMd7u3i<}NN5A_&+|?7G9-1_7ae3lMBnJNv7d zXMz3J!ofZBPwcn0?X$Mo(s%!_n_n37qpRk7^U{CD8=^L%(s@xf2LC@oPG>x?*?W6y z`xd_WH(RG`p?&>Y+UWF3K2=V8lW%*JuQc_li@#Fy>avZ#Cr75T*;_!poBvzMA8S{Y zAb9=N3W0h4I#cg2uDhh0Z~x{Y5Tq|&{N-P6+URfd*!blyH^0_=uUY_la##HZvaq6E zIahsj*+RfZ)hp)H43hGBtr5-@W7NET_Uz6NL?41c`skkqfkY2Gp0QXrMlM}ZFy7t;=GuTgqqTDj9r0H2@-K4sp_t$Pc@z5|5$ z?oF_qDgR8Q?!;Xt@>o+32Fr%*9&*{N9_3bkk?w_?~%QQCG7ghBexN%;$6l@OipW z(0raLCJ{25=p4DBZM0ii!P($N;0FyziaXPJb^w&6K6~~+!fU*bq?M{>(vAR)3h1`T(nTUEw2KE87wZ*JubVk8@M=xj$EvyMTo0ccp!z8 zUqzkkY$3HYsA!Dt13s0ru4+};d*-dRKjswF5pe`i&@xO0FF=o>wKP*uAnz}9SMMTl z?1SqR)@y(?zMdT{6g~fN8fHc(<*+wVPOV;29ubR;N?;SdV}^e%x`Puy5=>Cr5g_iT zsHd@UGrnZJ_9>7VxYQE0TW*~J#+AQcm9fmjdFp)6S_y!n2yL~f=TNx9c7#yVgq!AF zY^DMz2R6e%s7m!lPn?P1Qg*I{z-VNU3w5&l)D~UQ7jQgiaUW7(AS8|wNaucpQxO79 z3H`oL2f7j&DDh;I&IGFSruD)?53V|j_JpH~!NPN7-wD||tQ8R7#38c8!ttn*m6HN= zhZV+cW^h%W-30(>@gsDK@lZsOR_?2&>%wmCE%|ew0qImJGajU2hq2dz67AkkK091sC(L@2u zQff{L^hXqf)vLb5*&p5wezeq-h!H*4Rt#L3?}X>5UkyxIx< zbO!@rH|CSaj+HqbCPqmjT~jROFb(4fX^6T7Rj>n_K7&Ekn<3W-OOb{DSEnq#Mvm^c z1xhoS_vUjTC_)e2LAA#r_DHm&85is$Z~ZtxLc>9hJuABT7LRq7c=sIBupHIf^?nAC zTxNzJ78pAFvci|&M!Dsq!)x?C<@iP>0g+a7nVsh@hv&ZTr^bYUR1t=OW2wS)WGRCD zMkkH|qgza|qfLb5aOCkSLS@_;eXFuztSM&GU*8g3`2=_2z$wG&4_3?S(#gY67FzL- z=&PFwMFJ$>!UY0^R4|jeDlk` z-yMCUR8;@ltg9-=^-`eB=FjtZ=)tS`*Z=sR{hMv2_9o%JwN=woeudxfj(!UP^YuJq zzpHVp>H!3+q*77aTJBA1{2}{KUj5c@>Dtn~`bH&)YJ2e&R0Srl&y~HB+}WjYR@;8L zB0-c5{1J9fQ@D2d5(JV}f6TQ(u%&(VwMw~#z+9!IYVKO&>UxHG3Igr&btmDzmuQ!c8KYnKZhwF*qzixQ%nS93Brahi%X`a(u1vG0w z=N9R=8+swPe$j-X!5rhS8Wm38F@pap5PXiw)m>}M)!tCJ+Kc?lXK&dfLA#i5l5I)A zfAVwj)z+5YLvT+!-@8=i)~3x~>(PDPBmQytJ&mos$iHO7JOzRFZ~vQu;2y${cCGLK zO{1J_w(0m%@h##-%-#50RKU$-myZj-lO?nx#Pf9&=_UGEzX#@#H^=Fn&vpHb-&TSP1X3-G1!FuYM2q#l>nX`n7g5wF z41ni|hY1a{4Ju;)u#lMd?ZMkpLQ4SE!9aQpv@WMi0PH-KGyaw<`cFP+yh*F0=&6%( z7#W-zMQs@)(D#S|IOufZ+_IgdNj8|X^Uwp4mc+4g1NM0q9(I`{!c@jHGF{9=R1*?h zM%}+k;N%OQk&xPkskgA%)O)kE5OYlR_Tk8$0sC2#HXHWLcLAAcuUEecIDgH3N6v6{tAK)w`JOruJb6ZtjdmuvIv>PC@}bPo&0x8) zvo+;JD3RpA2#%bUyGjb#kL|SwfO@;iA+3sY8X+VDw+aVbBai^Dan6SpALV(Yj*+-j zbnoO#U${UWj&Xu$-b;8%Qd4bpK`&D7pf`ZzN);Gm1LdUhtD-FwN@ate$z)o>qu}@rMb& zODYK!4C$D&0h{#6i8Vf;TiyY;exAyEa2@Fa+N@zLBWgQ4IBuHm#A9iR^%fNFXU+n! zl--v#)G~d(*E4Afo>ha|Ewnr|Ftuy(gf;c7=Y_IWc?JNR&XDO9f}8n1DYW{s>!kO~IQBsG9~_2v1-to(8&+4ImS9a?%q{>)AM)bZpE;(RySt$tBaL zWKdd`+>+-h6$wrOR5m1>Y=vYSX)eDdDI3EgF*?vI#y_b>(ZB@}B0x-(U1isaaDJkZ z70!xo`P8D29VSLXxESmN9C?AtCWO_!6Pm@RE-EHQy;}I4&I}t$FoaP^WJ!+P=pGja z&2SQW7Gt;=cik!WEGnK+bV%$`PJ{b_^c5@GeOOE+jd2Gu0cyyF2sns(?gD~yy<<1S zZ6kIC0$S-TdJWX=Cn{%TW#9^^Kfpsh-LtTIU?;j&=evP*Wfs_VqnCC#{hGrYyKk*1 zWZUEg*K`ib#EBu93KS1=r{sOo`vj+zu3-In_DkgK>0~Ti0`j;17!AP*`XYUC~ zDUnid>#dauyn}i`FEf&}a>EKKDfd!Pn;QP<;nK=D4Joi|$fj)uObe|fck z{6jf%36h1sysjF5!rs!ol!N$eu>UBp*nW;s?~5EUC`pS}4cXR9Yv=E6^)~W6PF>TUMNb%CS-1_ls8&@quSrTU1{+R;o7_H>SkEq@AW1MR40IKlKo*x4EUm-q7PZMQn{ z8P@m02F3H|t7iS`Uw3-fnPi*UO5PyZG~&1L$9AqWW9e6eAoD=p>P)iDY&EHuG)xxtouHDSIw9$4K2-__&H@NRIp4+1uwi6+BoLwu|g$n!aq8l%3E5JB~i z{8q&3E?xCCg(p@f<@?>O>vO{j0#xqR1lenSpo0_2ui;qQW6BXnY|82Z#FN(JpGBKj zq=9B5jLDyFIV3ys4T<^cVUD3%$(sw4Lk;n{`>_E#5ZH6!+{*)!%EYQxPuo4=$;>Fw z@`C`LNi`GJ6g66!6Ws$uGHZuuPY#>NHweryMW2bA`qwiI4?n?x*xCE7;!^JLJ zNP`rYwJ8Z&yGJEeqelMDWr7=5$4COy0=(u;z;jX{yD=;Wz~o!Lw-l=#%vG7(_lB(u zd6FnBgOgjDQ-KkT_mEsY*zjd=l;B0NlQ=(tlMjPe9fSH`Hpmmw6jrwmduWsHiYt>R z-h<9|DzHgA=}}z`rxFllQsCz3hAQFk`!Sf-L%a-*t&H|;30%(YSn@{N4}|B3lgfam zF1#9`S3&1wmqXx=8FOnrvL)Od0sGPW9D_ULE4L>129*<%&14476kJiL@j)HBkrS{t zLwS1!?5?SLKhtw_#cJ<%#)g*@7!8bv9>PC)+QuamZrOV>_bRAbv4@Lip3Wis8k3=6 z5dz}++4scqjv;=kaH&}pt==b{VDfis%ggsz8 zQ}Z$^f`{SA*8-uxzItNgX{D5ye>L5}aRBgi^)ldV2~aTwRLeEQ&CJIK*|ok~`V2*a zxb(VEqo~0PtZ617T%t!@?zOK4svdAzM~Wso(bfoz-b%QyySX>C(36IMf=dSUd$BBY zW$W1R1S^^kY`_k3b+7?}!g^A|#H$L*^*E@X@aGeEI5vzS?nem9iL|RmRysD;$RFO`CtGe30wLe^Ht_SM1t)V)TYswDs?yKJL?XC3vwS3bK zp7)c1V}2})3t#5c&E>qG=Ak)7p=sLnaV+{YvscP)=XcE6jJYU)0e!(TE)o}o3u0OVpLg0gxrS#l=`GHI33xKZU*H z_0i=Db&x`Iu|9Xyq*$aPQO;=CorL!=Yq4Tv*co4UXLo9&uNL(g`;Ee&P83&BLw!Pf zaVg!@_*1>0zPI?cSbT+FI$0N&yk8buSk=JW$LQV6(?pH99aB_$g3Zh5v;1w*(@l7L zUi>3+wS=F&Jq;(CP7FO$2Jr4da?hjD3|h*Yr9dUDW^}Y>j_BOGv1o?HUf?tDcT`O; zPvt^w5=~jUk%OzSECWFH$dhbJfnXfxhNy!9Qt#?<@(x8apw*Lp;DwK_mff@C%A_}l zwS73}aC($Ee^HpYRO~)-il-fX<&GH~h{I2z#c6RQ8;;B+*-6{XWw)!q^iX!h3V@hx zI#U?O-SL%6421?=+epf$?COf6Ela<2Jc#Ub(xH(%bj~kz#VIBn6U6bGJ36gcLbLD8 zp90Pckj=#gYCyfWuuL@_eiQ^0>I+#EQV%eF!MLZ!xClfsm2#mZ=9z>9QDGnO}aEV`O)7VQ>89;p#WP zwuZ}ln?Cy=guHy5;*TEY*y4XE#Hr>#y4AV5)z!Mw!GaYd*3{!mD^K3M`TA?|fWG}U zpF9tO1oPuW=q>xtS0jaHtpvf`VE5D0`%g<#XU zddmLp6>V2_)OSS!o($64SFme!G6K7kXtxmYz`=pqrj!2inWJLBU9a0VZ(&<{)htf( zQ}(OO=e&hrgGi@W{&hj{4wu)Ui#4H(mASp_(`ng<^Y=Kh@_IhUX5aH}vzFJhCY4&} z^bc-UMroy#X@2peZAH|Rw)ut5s5EcCW=~bwuP_=*oKX3V#?e==Up$w-(u>!^{x8HoT@+CJ2-aP-B zKGtY^K~KL6!K%CUeGmZA+n@Yogc9X0-)@%3k#u^|U?D za-OxGmDj%2x0d&@5Pbgm2D93(l2+`$dc`YTKVE)%?em(|i}uF2dw1BJIpz9FCTQk; zOfIHpGAWmn@QiRy3YBeAfnqN|)RiN8aAruF&}FPf0i9aR?TmiCY+Nc=_*4 zc3;0qy#A?7MX&A9RKkR6W@^q1)9gG{B@7C|E21(=pTD_9k1B#gxcI;$=Q2;7>< z1RqSuiyb?Yv{1T%k8w+?;6^mG93`=KiCrl&cqKsbiLUtS2WFW*;1j`NLQyzaPe%k$Nf{DJ60*Eab32SxF)KsA9S- z$I~aZ2%>X5$pbzdw}#4nUSy(R%JPWfLk^=MAdO(fKq~uqiA)Jw<~wGL)Pxy2Zq@`( zEv)3ShITV##9^MHkk?^FlBiVDyE@|$<07d+m_fy}&lz`PJsFKTw#;5A5i+H3nPD4s z-bh4+te>e9?@%|G;EqK{AlJd{JT971qjxf>6;$Zmxs8@!dooOME;9wzHoBRLYC8mG zP^9s|DHDl}4SW_lCP5#?+MDR38=Ic-6PYvvx}yt(q#-)UH5+Hh9972LP!|1@HL*Od z-gq<#0{VhOS(Dqr?Wkx4V-YR-!k3}t@$#mM>{38pgBy(zW6jaGruAq1)`xR_s8Coa zW1d9$0VNI91l1Oe4_@v?N)q%MdtNZtF1=$OOSq)~^o8mnYU)LjjxaCynS7T#J{>LA zXu*I5jE2obv7ELDqR)t^JB038=UL)y(Qp#zwp&5M>9zndKN1&TXS<=J4u zQM0{ffbVI24ch=sBT$%p;`!i05M#NAb~0h-s~K>C!jGh3qapa0i8hUG2A2IWPv`=6 z^{MX2!!W|YR5RAYw1V6nD4LV@)WRW5l^MoM$Uf*_wUU8kY8Ompya`qXPz{544?AMt z&%`QKpdVXG_71%8e%p!CW+V{BnU9Q_p7Qr4=iM@m=WLJA!$j=C{}ebRL5;>#eaG9b zYQ66~3z_nh$g3eid-EK#ILg809cbi!M+j}6l}Qy*F;{LI>Mk(eWNf`?et-k{DE3v3oonFUck9Ye8O%9)aC zG^>)R=k>)GqhT1UT(jQ68$A$q(9nCD0RXyXgDc{I&XJeaUrpVed?Yz5ro<7+n=MA8 zGcSR3Ek9B-3xxKOgC1fU=I#_0qE6i*BvUNn5iX(RKnG3-%(d`lj&NP#e%Db8hZ&k9 zMH&Z9Tw*JYu+vqBMn`v`y2G-Y;iu1LIYjidbfQN@7x7F@?wPQ_B7gwRA@b>`*7-!FN zV$I}t)3WgW^QQz851Eu!PBmlg;eI9B!Jf4ohMB4XwXY@7JCvJv5A1@KPF4fM#X_I+ zv_Q;RUX5^0PC#OYnr{gah1e^jLPBln1`pPV8I$FcwHV&XhX5hEB%O-6RPb1MUF#$m z?HQVCj=15)xQtS1>Wih1;PS|Y=3->sR7bhI#Os>nWd?r@Jq+ij>!^thV?t)=F$zj` zS{Vy1Qi^2J3nsF!Pq*NVsdftl@&RqIg_syAhLR&>@^Rt)9e4>Z9M!tSBRbMlAL)<1 z3$yxoHTwk~!G7h9QWtdgseju$FSoBC2_u;ma5wP1zMvHKb|=aTnc8Ogx($#+FrJ0X`^(syq#;_e?}B+|HIY(y+?K~lXx(e(K zj})MgmseYs{9DRiMc_AoZ?(6QHvjOMG^C`HrHl^@bjo*}=#Vuyh+gRnv^kXY_Il{M ziTT;jM8-1v{_1OH#%QbLb31gl<6BN3e0cJQsO<-};}M&vnjdRcZQ+0OcHV)+h!gS1y#4muSFi5d3=PL_5bKT*!o$`(BP&box-^F9bNFX#>juU@HMm_h5)`Bp2f1Zeu@m)Db7Z*j1t zyQiX+T1GJ|KLGk4YJKH>Z0S~Xh|Mj*}?s02@r9dqtMw1vCfQv>FwZ{ zAHdz_(xlx-+qF!gnfmh$WU)CydkA933T1~q(GJvWfWx4W<4IaWcpxoc?qAQn?#nM9(r|qXMPThhMUwZuES{nHmrWxLiI1(&f*w z?wv>(OLuXWNeIgr3`y!*qP_$GBT7`Mtyxu556tiCzNYkyD-(_bj1`jl}}v{9Bu0ltr^;j_pyzTqn;V#1jER$p`Ef?Xq~*O2{6(Na+fD839c z&xi`$hmfR+2T{=`MnhtJp>o*zeN*;(ThnS}_-LH~!g?T%b$d_qrCq@@=TjWOZp{cA z*Jk9}lBWkj9*5spJunCLGghI^!M7eHdP+v>jSCDAT8BNF5H(6u4lV{ZZv?X- zi7T}_rMS3W^%xty@wjYT$xGL1$T8Fih42)#yo<=HUdXU&TwwOj&z>%Ac94FA@CIRE z=2C4;vFQMik_=OL6D5kYEn8h2Js}6t(r~gkPeFB0NJ6p4Sx>?Bd7LNG8NzvT7>3KS z8~4368 zLDobn5X~;zuQt?f%wv=wBm5u{=gx&}6g`kgJ5TuHbWiP{q9er2lBq+dAvKM2tct^q zS4l9j*)8)S_Ho)RFcp0)4g-hVge98FL@JNQA)@s$dMNs;AiMy#pkIcLkLnc-j|F-N zLH&Uaf^4puMPG&^lu&G%J)2oBoa{J{kd3iw#yIOBNY0R;dUl0^?VDgg(UP+Okzb0 zgxr#(cYaT1MqctXg)2{6#$bC9xiD&LY+TUH7!4OHvW=lJlITqh_%fz%AyiN>AO>bD z9Lkdi@@$4Yiz!^94S5V@G?!UeyT%wfQjLWfdN~X2ys`HeyfJd&>)$`}LB+7fMEmLSn)9`s})~1tb*&WfQmj7OXr0Y z=-<_VV95fN=-5sJXz{Jt^qgE^+ROh|5uRRAx5p_q<=Qj6A&|tL;T55eFbEhzj#@T8 zevAjT1SQ5qsxblws*x^05YcQJd%xlz=?_a?)9aZZRWQhkT2JYQGFX0X$=*4K+FaUCjk!V)*(=LmZ{R4A4N5_DN;`lP&x< zGZSuV*!D-Wjio;Kq!^~6g1qARR4pa5a?8LQM0;|@xLnBXLB<2C!N z*zdw>9e)haYBq%Y>1Lzxs>x zoV<^(zUQ)^s`2YV03L-#lnnx5B$O;`TG>;1*M1*geGl`%`IfekFX4Fan636V{j)Mr z_)XWyr*ObIucQ6cKeONE18m+HEc9faHXQj=PV#t~!k*GMZOgp%JC9x3UBIeT&Gx-N z7oM1DHFC;i6i=-#2?FZtXPY;eD4cQhyQrNqR<>c7!i5z{eB&J(-+Ai7rG<41pH$2@ zpKRFN9fF+juLFTNReVB^j2 zrQ0!F_3}+4i)w=6XDJJX62`joujPJBVZ-M&cK15a`KqcG;lx|Yf=u*;t!}>ldV|0o zYIhH(l!1VIuGViru=B4KP7rMRfgT~)>tN@tzYT%t30vKWBfgn-v$l_~5F?rG&O^#w zGW*%QS=*fuiKogfxGLqPLzDzAbL7MnSRYpAao?Y6mNwrDh|)(Y5tV zc}w#wN0Jce!_q9rC9q;6wZ?-q!=;2rqdFcllo>?N+idd0hnIqns*SDP^)Bd_v}wev zY-}%w2eS0b5ulH*T*{#zgTm8E*&CuI$9ROQ(T`HFQIFJUYAm!wRjY`5qPsFsQDt^p z_(JCVCZu}t_kK9f*^ciRpj_4G9p8aUd^WcN~umcJ(a>aU=`q9 z3g%!)0Uc>z=ovkwtOwKDZa2ba??Yhsl1;#SVfCcv#2qjL7AA19($8T$&%A+tq!G{1 zcVjO<>vdBt&$U+wYSUA|fH-?l4LDUzTOx?O(Uc84nMU3&8q%E2I&yvjt9}^vQ_V9Hxa=Y7T)mVNb+m|^Tmm@i3A6-e1v#kf z4i~cY+|@foGvOI{<$v_l+`P?Mbe|_it}eTw0?_&gP+Vfa`zf1n-s#a9AiWz`ff}n-#Yvj`R)mHKoV z%f(V4NTSPOdz$FrFPvc8Rsg~u4K~5o?b)AOmAG!|MU<_4kSTpFe{+DVH*#gW8cC5n zH=`Cx8lQgnV1^#}O$8ohB@n7S7V)5B=;J{_$u!}PJbL*IT(OM~%+MZ8%1`Q<3OFhg zh-f6o6P53cP7Wc745uy=n+QOnC6^{mIRK@y0@)WuzR&(V={p4th9}Rn#u2j@)!@yS zT>OA_+@83*hbKPs)UE1p;=f-Eg*rH0i;tTNuIECMQzZHCdr;) zK4{^kUn*R-^<0vLY>%z#Zs-pkHb5lZ4c$qh;8RpWuSDu$SZs;Z>H=V)4C(~6=%3Ug zwG_{7D_z&TZVt^&%ZR#W*WGjp4q?J`8ljia14FO+Ymzj7}-J))zLeSok`-$Ag2cM7eAR^aDSpjJ03uj)SVP7{{CDcMeVs6GITk?0q@ zsfLitBNaXg_bG}68>yjqiMhnwz}dwvm4}(`IPgWXF8Cp|Ve0vkksliGo8bmeDL}Or z$77(Lj$Z54Q408&PMc${0SeOLzZKzQyec zSd?Q(eBTayu^jTKWof`56}CWBq{`bcL*1q3kliT~aZLn)XOmuuJNcYf>IZ0 zMcobZ*k<1eYm_QR`9GM;dcBbE1-f3gA^8yl({_)M-4~ElQ=*6tB^YRJq7H4n&Z_Z7lPqiL-cVf=CD5#!KGM z6azVwF}l`^%6mb~=h}pI=))B2X^ms3&TQd{&dwBlJw;?Gk3PntIz#qf)67Q01{y72 zR1f!vYHy&;W~wheW+i8JLdSLWhhc?4c!M?%$rjL&!9ubM=~X_6X*^yk-%N;HwX^3i zrI8PmpXPO_i)%;Ij4k6K;ns|B;V@}V#kx3lxvh|r2U`8W`gd?pdqFpbzE8If+ZU_q zAe#k_Chl8;)gdadoeN~HYN)slja(gqO~_)MM1ppNgqu(eVH%{p`j~DgZd+MMu#e zdTma}OB^k_YfCgHyM$=-g^xi(_w+{Xg?5e$VYeVVbu{Zee?I(d!YvmB;As3hOIRh? zPa#Ru799;_ZM|HJ1qRq^-BIADqONfB9$tWUQdO02hX#N8NB(9#PHU}?c(%cT`+-*b4WH~mh?h@e?kQmZNCA|>-F}G8Oxn*N&CAc9Z}E28>@Tu+2!8UHzyI6cguq_C_(~8wP2pW? zuH4&>sT~68fqwCoXss{4`tgrw8<imuWvK z3j5gyztjG2f@b3zY?Mpow9DMUbl@ds*Ke-p%xie@m1wq^&Mz;~Ohxa__O8Y1i!YQi zl4cNyrP+CZziD`uT-rtr-XPetls)7O$x@lU4+2sDrK4Ij+xJ7TYq5eS@C1VIeeV$h zQqFtyl!7=MqP=c?4aMNEl=CiorRQwkJZh;@D=NJD(?;puQcyU(k>+z`vN*4Klk2

@#X5>*kuH6#!p7W$=yHCViy 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⋘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

;F&K1mR&t*Zd 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%0Ds7$7LRWo#f2!{zxIEBldA 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%0D6kBz!aWj(B<5o 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 TYGi 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

k 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

bj23K$EJ8*KXl!c93|n@jtC*k6%wx(t>j<({ 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 z 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

52C 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

B^K>YW0U@$U=eqwx%XkLfYEZs*PfX!V{4v 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

7>eJc|%W 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 EdU@CBMOGsXFAKe(} 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. */ + +#ifndef _GL_CXXDEFS_H +#define _GL_CXXDEFS_H + +/* The three most frequent use cases of these macros are: + + * For providing a substitute for a function that is missing on some + platforms, but is declared and works fine on the platforms on which + it exists: + + #if @GNULIB_FOO@ + # if !@HAVE_FOO@ + _GL_FUNCDECL_SYS (foo, ...); + # endif + _GL_CXXALIAS_SYS (foo, ...); + _GL_CXXALIASWARN (foo); + #elif defined GNULIB_POSIXCHECK + ... + #endif + + * For providing a replacement for a function that exists on all platforms, + but is broken/insufficient and needs to be replaced on some platforms: + + #if @GNULIB_FOO@ + # if @REPLACE_FOO@ + # if !(defined __cplusplus && defined GNULIB_NAMESPACE) + # undef foo + # define foo rpl_foo + # endif + _GL_FUNCDECL_RPL (foo, ...); + _GL_CXXALIAS_RPL (foo, ...); + # else + _GL_CXXALIAS_SYS (foo, ...); + # endif + _GL_CXXALIASWARN (foo); + #elif defined GNULIB_POSIXCHECK + ... + #endif + + * For providing a replacement for a function that exists on some platforms + but is broken/insufficient and needs to be replaced on some of them and + is additionally either missing or undeclared on some other platforms: + + #if @GNULIB_FOO@ + # if @REPLACE_FOO@ + # if !(defined __cplusplus && defined GNULIB_NAMESPACE) + # undef foo + # define foo rpl_foo + # endif + _GL_FUNCDECL_RPL (foo, ...); + _GL_CXXALIAS_RPL (foo, ...); + # else + # if !@HAVE_FOO@ or if !@HAVE_DECL_FOO@ + _GL_FUNCDECL_SYS (foo, ...); + # endif + _GL_CXXALIAS_SYS (foo, ...); + # endif + _GL_CXXALIASWARN (foo); + #elif defined GNULIB_POSIXCHECK + ... + #endif +*/ + +/* _GL_EXTERN_C declaration; + performs the declaration with C linkage. */ +#if defined __cplusplus +# define _GL_EXTERN_C extern "C" +#else +# define _GL_EXTERN_C extern +#endif + +/* _GL_FUNCDECL_RPL (func, rettype, parameters_and_attributes); + declares a replacement function, named rpl_func, with the given prototype, + consisting of return type, parameters, and attributes. + Example: + _GL_FUNCDECL_RPL (open, int, (const char *filename, int flags, ...) + _GL_ARG_NONNULL ((1))); + */ +#define _GL_FUNCDECL_RPL(func,rettype,parameters_and_attributes) \ + _GL_FUNCDECL_RPL_1 (rpl_##func, rettype, parameters_and_attributes) +#define _GL_FUNCDECL_RPL_1(rpl_func,rettype,parameters_and_attributes) \ + _GL_EXTERN_C rettype rpl_func parameters_and_attributes + +/* _GL_FUNCDECL_SYS (func, rettype, parameters_and_attributes); + declares the system function, named func, with the given prototype, + consisting of return type, parameters, and attributes. + Example: + _GL_FUNCDECL_SYS (open, int, (const char *filename, int flags, ...) + _GL_ARG_NONNULL ((1))); + */ +#define _GL_FUNCDECL_SYS(func,rettype,parameters_and_attributes) \ + _GL_EXTERN_C rettype func parameters_and_attributes + +/* _GL_CXXALIAS_RPL (func, rettype, parameters); + declares a C++ alias called GNULIB_NAMESPACE::func + that redirects to rpl_func, if GNULIB_NAMESPACE is defined. + Example: + _GL_CXXALIAS_RPL (open, int, (const char *filename, int flags, ...)); + */ +#define _GL_CXXALIAS_RPL(func,rettype,parameters) \ + _GL_CXXALIAS_RPL_1 (func, rpl_##func, rettype, parameters) +#if defined __cplusplus && defined GNULIB_NAMESPACE +# define _GL_CXXALIAS_RPL_1(func,rpl_func,rettype,parameters) \ + namespace GNULIB_NAMESPACE \ + { \ + rettype (*const func) parameters = ::rpl_func; \ + } \ + _GL_EXTERN_C int _gl_cxxalias_dummy +#else +# define _GL_CXXALIAS_RPL_1(func,rpl_func,rettype,parameters) \ + _GL_EXTERN_C int _gl_cxxalias_dummy +#endif + +/* _GL_CXXALIAS_RPL_CAST_1 (func, rpl_func, rettype, parameters); + is like _GL_CXXALIAS_RPL_1 (func, rpl_func, rettype, parameters); + except that the C function rpl_func may have a slightly different + declaration. A cast is used to silence the "invalid conversion" error + that would otherwise occur. */ +#if defined __cplusplus && defined GNULIB_NAMESPACE +# define _GL_CXXALIAS_RPL_CAST_1(func,rpl_func,rettype,parameters) \ + namespace GNULIB_NAMESPACE \ + { \ + rettype (*const func) parameters = \ + reinterpret_cast(::rpl_func); \ + } \ + _GL_EXTERN_C int _gl_cxxalias_dummy +#else +# define _GL_CXXALIAS_RPL_CAST_1(func,rpl_func,rettype,parameters) \ + _GL_EXTERN_C int _gl_cxxalias_dummy +#endif + +/* _GL_CXXALIAS_SYS (func, rettype, parameters); + declares a C++ alias called GNULIB_NAMESPACE::func + that redirects to the system provided function func, if GNULIB_NAMESPACE + is defined. + Example: + _GL_CXXALIAS_SYS (open, int, (const char *filename, int flags, ...)); + */ +#if defined __cplusplus && defined GNULIB_NAMESPACE + /* If we were to write + rettype (*const func) parameters = ::func; + like above in _GL_CXXALIAS_RPL_1, the compiler could optimize calls + better (remove an indirection through a 'static' pointer variable), + but then the _GL_CXXALIASWARN macro below would cause a warning not only + for uses of ::func but also for uses of GNULIB_NAMESPACE::func. */ +# define _GL_CXXALIAS_SYS(func,rettype,parameters) \ + namespace GNULIB_NAMESPACE \ + { \ + static rettype (*func) parameters = ::func; \ + } \ + _GL_EXTERN_C int _gl_cxxalias_dummy +#else +# define _GL_CXXALIAS_SYS(func,rettype,parameters) \ + _GL_EXTERN_C int _gl_cxxalias_dummy +#endif + +/* _GL_CXXALIAS_SYS_CAST (func, rettype, parameters); + is like _GL_CXXALIAS_SYS (func, rettype, parameters); + except that the C function func may have a slightly different declaration. + A cast is used to silence the "invalid conversion" error that would + otherwise occur. */ +#if defined __cplusplus && defined GNULIB_NAMESPACE +# define _GL_CXXALIAS_SYS_CAST(func,rettype,parameters) \ + namespace GNULIB_NAMESPACE \ + { \ + static rettype (*func) parameters = \ + reinterpret_cast(::func); \ + } \ + _GL_EXTERN_C int _gl_cxxalias_dummy +#else +# define _GL_CXXALIAS_SYS_CAST(func,rettype,parameters) \ + _GL_EXTERN_C int _gl_cxxalias_dummy +#endif + +/* _GL_CXXALIAS_SYS_CAST2 (func, rettype, parameters, rettype2, parameters2); + is like _GL_CXXALIAS_SYS (func, rettype, parameters); + except that the C function is picked among a set of overloaded functions, + namely the one with rettype2 and parameters2. Two consecutive casts + are used to silence the "cannot find a match" and "invalid conversion" + errors that would otherwise occur. */ +#if defined __cplusplus && defined GNULIB_NAMESPACE + /* The outer cast must be a reinterpret_cast. + The inner cast: When the function is defined as a set of overloaded + functions, it works as a static_cast<>, choosing the designated variant. + When the function is defined as a single variant, it works as a + reinterpret_cast<>. The parenthesized cast syntax works both ways. */ +# define _GL_CXXALIAS_SYS_CAST2(func,rettype,parameters,rettype2,parameters2) \ + namespace GNULIB_NAMESPACE \ + { \ + static rettype (*func) parameters = \ + reinterpret_cast( \ + (rettype2(*)parameters2)(::func)); \ + } \ + _GL_EXTERN_C int _gl_cxxalias_dummy +#else +# define _GL_CXXALIAS_SYS_CAST2(func,rettype,parameters,rettype2,parameters2) \ + _GL_EXTERN_C int _gl_cxxalias_dummy +#endif + +/* _GL_CXXALIASWARN (func); + causes a warning to be emitted when ::func is used but not when + GNULIB_NAMESPACE::func is used. func must be defined without overloaded + variants. */ +#if defined __cplusplus && defined GNULIB_NAMESPACE +# define _GL_CXXALIASWARN(func) \ + _GL_CXXALIASWARN_1 (func, GNULIB_NAMESPACE) +# define _GL_CXXALIASWARN_1(func,namespace) \ + _GL_CXXALIASWARN_2 (func, namespace) +/* To work around GCC bug , + we enable the warning only when not optimizing. */ +# if !__OPTIMIZE__ +# define _GL_CXXALIASWARN_2(func,namespace) \ + _GL_WARN_ON_USE (func, \ + "The symbol ::" #func " refers to the system function. " \ + "Use " #namespace "::" #func " instead.") +# elif __GNUC__ >= 3 && GNULIB_STRICT_CHECKING +# define _GL_CXXALIASWARN_2(func,namespace) \ + extern __typeof__ (func) func +# else +# define _GL_CXXALIASWARN_2(func,namespace) \ + _GL_EXTERN_C int _gl_cxxalias_dummy +# endif +#else +# define _GL_CXXALIASWARN(func) \ + _GL_EXTERN_C int _gl_cxxalias_dummy +#endif + +/* _GL_CXXALIASWARN1 (func, rettype, parameters_and_attributes); + causes a warning to be emitted when the given overloaded variant of ::func + is used but not when GNULIB_NAMESPACE::func is used. */ +#if defined __cplusplus && defined GNULIB_NAMESPACE +# define _GL_CXXALIASWARN1(func,rettype,parameters_and_attributes) \ + _GL_CXXALIASWARN1_1 (func, rettype, parameters_and_attributes, \ + GNULIB_NAMESPACE) +# define _GL_CXXALIASWARN1_1(func,rettype,parameters_and_attributes,namespace) \ + _GL_CXXALIASWARN1_2 (func, rettype, parameters_and_attributes, namespace) +/* To work around GCC bug , + we enable the warning only when not optimizing. */ +# if !__OPTIMIZE__ +# define _GL_CXXALIASWARN1_2(func,rettype,parameters_and_attributes,namespace) \ + _GL_WARN_ON_USE_CXX (func, rettype, parameters_and_attributes, \ + "The symbol ::" #func " refers to the system function. " \ + "Use " #namespace "::" #func " instead.") +# elif __GNUC__ >= 3 && GNULIB_STRICT_CHECKING +# define _GL_CXXALIASWARN1_2(func,rettype,parameters_and_attributes,namespace) \ + extern __typeof__ (func) func +# else +# define _GL_CXXALIASWARN1_2(func,rettype,parameters_and_attributes,namespace) \ + _GL_EXTERN_C int _gl_cxxalias_dummy +# endif +#else +# define _GL_CXXALIASWARN1(func,rettype,parameters_and_attributes) \ + _GL_EXTERN_C int _gl_cxxalias_dummy +#endif + +#endif /* _GL_CXXDEFS_H */ diff --git a/ccan/Makefile.am b/ccan/Makefile.am new file mode 100644 index 0000000..a667df3 --- /dev/null +++ b/ccan/Makefile.am @@ -0,0 +1,4 @@ +noinst_LIBRARIES = libccan.a + +libccan_a_SOURCES = compiler/compiler.h opt/helpers.c opt/opt.c opt/opt.h opt/parse.c opt/private.h opt/usage.c typesafe_cb/typesafe_cb.h +libccan_a_CPPFLAGS = -I$(top_srcdir) diff --git a/ccan/compiler/LICENSE b/ccan/compiler/LICENSE new file mode 100644 index 0000000..fc8a5de --- /dev/null +++ b/ccan/compiler/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/ccan/compiler/_info b/ccan/compiler/_info new file mode 100644 index 0000000..c55ba22 --- /dev/null +++ b/ccan/compiler/_info @@ -0,0 +1,64 @@ +#include +#include +#include "config.h" + +/** + * compiler - macros for common compiler extensions + * + * Abstracts away some compiler hints. Currently these include: + * - COLD + * For functions not called in fast paths (aka. cold functions) + * - PRINTF_FMT + * For functions which take printf-style parameters. + * - IDEMPOTENT + * For functions which return the same value for same parameters. + * - NEEDED + * For functions and variables which must be emitted even if unused. + * - UNNEEDED + * For functions and variables which need not be emitted if unused. + * - UNUSED + * For parameters which are not used. + * - IS_COMPILE_CONSTANT + * For using different tradeoffs for compiletime vs runtime evaluation. + * + * License: LGPL (3 or any later version) + * Author: Rusty Russell + * + * Example: + * #include + * #include + * #include + * + * // Example of a (slow-path) logging function. + * static int log_threshold = 2; + * static void COLD PRINTF_FMT(2,3) + * logger(int level, const char *fmt, ...) + * { + * va_list ap; + * va_start(ap, fmt); + * if (level >= log_threshold) + * vfprintf(stderr, fmt, ap); + * va_end(ap); + * } + * + * int main(int argc, char *argv[]) + * { + * if (argc != 1) { + * logger(3, "Don't want %i arguments!\n", argc-1); + * return 1; + * } + * return 0; + * } + */ +int main(int argc, char *argv[]) +{ + /* Expect exactly one argument */ + if (argc != 2) + return 1; + + if (strcmp(argv[1], "depends") == 0) { + return 0; + } + + return 1; +} diff --git a/ccan/compiler/compiler.h b/ccan/compiler/compiler.h new file mode 100644 index 0000000..74e0f18 --- /dev/null +++ b/ccan/compiler/compiler.h @@ -0,0 +1,216 @@ +#ifndef CCAN_COMPILER_H +#define CCAN_COMPILER_H +#include "config.h" + +#ifndef COLD +#if HAVE_ATTRIBUTE_COLD +/** + * COLD - a function is unlikely to be called. + * + * Used to mark an unlikely code path and optimize appropriately. + * It is usually used on logging or error routines. + * + * Example: + * static void COLD moan(const char *reason) + * { + * fprintf(stderr, "Error: %s (%s)\n", reason, strerror(errno)); + * } + */ +#define COLD __attribute__((cold)) +#else +#define COLD +#endif +#endif + +#ifndef NORETURN +#if HAVE_ATTRIBUTE_NORETURN +/** + * NORETURN - a function does not return + * + * Used to mark a function which exits; useful for suppressing warnings. + * + * Example: + * static void NORETURN fail(const char *reason) + * { + * fprintf(stderr, "Error: %s (%s)\n", reason, strerror(errno)); + * exit(1); + * } + */ +#define NORETURN __attribute__((noreturn)) +#else +#define NORETURN +#endif +#endif + +#ifndef PRINTF_FMT +#if HAVE_ATTRIBUTE_PRINTF +/** + * PRINTF_FMT - a function takes printf-style arguments + * @nfmt: the 1-based number of the function's format argument. + * @narg: the 1-based number of the function's first variable argument. + * + * This allows the compiler to check your parameters as it does for printf(). + * + * Example: + * void PRINTF_FMT(2,3) my_printf(const char *prefix, const char *fmt, ...); + */ +#define PRINTF_FMT(nfmt, narg) \ + __attribute__((format(__printf__, nfmt, narg))) +#else +#define PRINTF_FMT(nfmt, narg) +#endif +#endif + +#ifndef IDEMPOTENT +#if HAVE_ATTRIBUTE_CONST +/** + * IDEMPOTENT - a function's return depends only on its argument + * + * This allows the compiler to assume that the function will return the exact + * same value for the exact same arguments. This implies that the function + * must not use global variables, or dereference pointer arguments. + */ +#define IDEMPOTENT __attribute__((const)) +#else +#define IDEMPOTENT +#endif +#endif + +#if HAVE_ATTRIBUTE_UNUSED +#ifndef UNNEEDED +/** + * UNNEEDED - a variable/function may not be needed + * + * This suppresses warnings about unused variables or functions, but tells + * the compiler that if it is unused it need not emit it into the source code. + * + * Example: + * // With some preprocessor options, this is unnecessary. + * static UNNEEDED int counter; + * + * // With some preprocessor options, this is unnecessary. + * static UNNEEDED void add_to_counter(int add) + * { + * counter += add; + * } + */ +#define UNNEEDED __attribute__((unused)) +#endif + +#ifndef NEEDED +#if HAVE_ATTRIBUTE_USED +/** + * NEEDED - a variable/function is needed + * + * This suppresses warnings about unused variables or functions, but tells + * the compiler that it must exist even if it (seems) unused. + * + * Example: + * // Even if this is unused, these are vital for debugging. + * static NEEDED int counter; + * static NEEDED void dump_counter(void) + * { + * printf("Counter is %i\n", counter); + * } + */ +#define NEEDED __attribute__((used)) +#else +/* Before used, unused functions and vars were always emitted. */ +#define NEEDED __attribute__((unused)) +#endif +#endif + +#ifndef UNUSED +/** + * UNUSED - a parameter is unused + * + * Some compilers (eg. gcc with -W or -Wunused) warn about unused + * function parameters. This suppresses such warnings and indicates + * to the reader that it's deliberate. + * + * Example: + * // This is used as a callback, so needs to have this prototype. + * static int some_callback(void *unused UNUSED) + * { + * return 0; + * } + */ +#define UNUSED __attribute__((unused)) +#endif +#else +#ifndef UNNEEDED +#define UNNEEDED +#endif +#ifndef NEEDED +#define NEEDED +#endif +#ifndef UNUSED +#define UNUSED +#endif +#endif + +#ifndef IS_COMPILE_CONSTANT +#if HAVE_BUILTIN_CONSTANT_P +/** + * IS_COMPILE_CONSTANT - does the compiler know the value of this expression? + * @expr: the expression to evaluate + * + * When an expression manipulation is complicated, it is usually better to + * implement it in a function. However, if the expression being manipulated is + * known at compile time, it is better to have the compiler see the entire + * expression so it can simply substitute the result. + * + * This can be done using the IS_COMPILE_CONSTANT() macro. + * + * Example: + * enum greek { ALPHA, BETA, GAMMA, DELTA, EPSILON }; + * + * // Out-of-line version. + * const char *greek_name(enum greek greek); + * + * // Inline version. + * static inline const char *_greek_name(enum greek greek) + * { + * switch (greek) { + * case ALPHA: return "alpha"; + * case BETA: return "beta"; + * case GAMMA: return "gamma"; + * case DELTA: return "delta"; + * case EPSILON: return "epsilon"; + * default: return "**INVALID**"; + * } + * } + * + * // Use inline if compiler knows answer. Otherwise call function + * // to avoid copies of the same code everywhere. + * #define greek_name(g) \ + * (IS_COMPILE_CONSTANT(greek) ? _greek_name(g) : greek_name(g)) + */ +#define IS_COMPILE_CONSTANT(expr) __builtin_constant_p(expr) +#else +/* If we don't know, assume it's not. */ +#define IS_COMPILE_CONSTANT(expr) 0 +#endif +#endif + +#ifndef WARN_UNUSED_RESULT +#if HAVE_WARN_UNUSED_RESULT +/** + * WARN_UNUSED_RESULT - warn if a function return value is unused. + * + * Used to mark a function where it is extremely unlikely that the caller + * can ignore the result, eg realloc(). + * + * Example: + * // buf param may be freed by this; need return value! + * static char *WARN_UNUSED_RESULT enlarge(char *buf, unsigned *size) + * { + * return realloc(buf, (*size) *= 2); + * } + */ +#define WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#else +#define WARN_UNUSED_RESULT +#endif +#endif +#endif /* CCAN_COMPILER_H */ diff --git a/ccan/compiler/test/compile_fail-printf.c b/ccan/compiler/test/compile_fail-printf.c new file mode 100644 index 0000000..8f34ae5 --- /dev/null +++ b/ccan/compiler/test/compile_fail-printf.c @@ -0,0 +1,22 @@ +#include + +static void PRINTF_FMT(2,3) my_printf(int x, const char *fmt, ...) +{ +} + +int main(int argc, char *argv[]) +{ + unsigned int i = 0; + + my_printf(1, "Not a pointer " +#ifdef FAIL + "%p", +#if !HAVE_ATTRIBUTE_PRINTF +#error "Unfortunately we don't fail if !HAVE_ATTRIBUTE_PRINTF." +#endif +#else + "%i", +#endif + i); + return 0; +} diff --git a/ccan/compiler/test/run-is_compile_constant.c b/ccan/compiler/test/run-is_compile_constant.c new file mode 100644 index 0000000..a66f2e1 --- /dev/null +++ b/ccan/compiler/test/run-is_compile_constant.c @@ -0,0 +1,15 @@ +#include +#include + +int main(int argc, char *argv[]) +{ + plan_tests(2); + + ok1(!IS_COMPILE_CONSTANT(argc)); +#if HAVE_BUILTIN_CONSTANT_P + ok1(IS_COMPILE_CONSTANT(7)); +#else + pass("If !HAVE_BUILTIN_CONSTANT_P, IS_COMPILE_CONSTANT always false"); +#endif + return exit_status(); +} diff --git a/ccan/opt/LICENSE b/ccan/opt/LICENSE new file mode 100644 index 0000000..d511905 --- /dev/null +++ b/ccan/opt/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/ccan/opt/_info b/ccan/opt/_info new file mode 100644 index 0000000..97b98f2 --- /dev/null +++ b/ccan/opt/_info @@ -0,0 +1,67 @@ +#include +#include +#include "config.h" + +/** + * opt - simple command line parsing + * + * Simple but powerful command line parsing. + * + * Example: + * #include + * #include + * #include + * + * static bool someflag; + * static int verbose; + * static char *somestring; + * + * static struct opt_table opts[] = { + * OPT_WITHOUT_ARG("--verbose|-v", opt_inc_intval, &verbose, + * "Verbose mode (can be specified more than once)"), + * OPT_WITHOUT_ARG("--someflag", opt_set_bool, &someflag, + * "Set someflag"), + * OPT_WITH_ARG("--somefile=", opt_set_charp, opt_show_charp, + * &somestring, "Set somefile to "), + * OPT_WITHOUT_ARG("--usage|--help|-h", opt_usage_and_exit, + * "args...\nA silly test program.", + * "Print this message."), + * OPT_ENDTABLE + * }; + * + * int main(int argc, char *argv[]) + * { + * int i; + * + * opt_register_table(opts, NULL); + * // For fun, register an extra one. + * opt_register_noarg("--no-someflag", opt_set_invbool, &someflag, + * "Unset someflag"); + * if (!opt_parse(&argc, argv, opt_log_stderr)) + * exit(1); + * + * printf("someflag = %i, verbose = %i, somestring = %s\n", + * someflag, verbose, somestring); + * printf("%u args left over:", argc - 1); + * for (i = 1; i < argc; i++) + * printf(" %s", argv[i]); + * printf("\n"); + * return 0; + * } + * + * License: GPL (2 or any later version) + * Author: Rusty Russell + */ +int main(int argc, char *argv[]) +{ + if (argc != 2) + return 1; + + if (strcmp(argv[1], "depends") == 0) { + printf("ccan/typesafe_cb\n"); + printf("ccan/compiler\n"); + return 0; + } + + return 1; +} diff --git a/ccan/opt/helpers.c b/ccan/opt/helpers.c new file mode 100644 index 0000000..8d0ac54 --- /dev/null +++ b/ccan/opt/helpers.c @@ -0,0 +1,193 @@ +#include +#include +#include +#include +#include +#include "private.h" + +/* Upper bound to sprintf this simple type? Each 3 bits < 1 digit. */ +#define CHAR_SIZE(type) (((sizeof(type)*CHAR_BIT + 2) / 3) + 1) + +/* FIXME: asprintf module? */ +static char *arg_bad(const char *fmt, const char *arg) +{ + char *str = malloc(strlen(fmt) + strlen(arg)); + sprintf(str, fmt, arg); + return str; +} + +char *opt_set_bool(bool *b) +{ + *b = true; + return NULL; +} + +char *opt_set_invbool(bool *b) +{ + *b = false; + return NULL; +} + +char *opt_set_bool_arg(const char *arg, bool *b) +{ + if (!strcasecmp(arg, "yes") || !strcasecmp(arg, "true")) + return opt_set_bool(b); + if (!strcasecmp(arg, "no") || !strcasecmp(arg, "false")) + return opt_set_invbool(b); + + return opt_invalid_argument(arg); +} + +char *opt_set_invbool_arg(const char *arg, bool *b) +{ + char *err = opt_set_bool_arg(arg, b); + + if (!err) + *b = !*b; + return err; +} + +/* Set a char *. */ +char *opt_set_charp(const char *arg, char **p) +{ + *p = (char *)arg; + return NULL; +} + +/* Set an integer value, various forms. Sets to 1 on arg == NULL. */ +char *opt_set_intval(const char *arg, int *i) +{ + long l; + char *err = opt_set_longval(arg, &l); + + if (err) + return err; + *i = l; + /* Beware truncation... */ + if (*i != l) + return arg_bad("value '%s' does not fit into an integer", arg); + return err; +} + +char *opt_set_floatval(const char *arg, float *f) +{ + char *endp; + + errno = 0; + *f = strtof(arg, &endp); + if (*endp || !arg[0]) + return arg_bad("'%s' is not a number", arg); + if (errno) + return arg_bad("'%s' is out of range", arg); + return NULL; +} + +char *opt_set_uintval(const char *arg, unsigned int *ui) +{ + int i; + char *err = opt_set_intval(arg, &i); + + if (err) + return err; + if (i < 0) + return arg_bad("'%s' is negative", arg); + *ui = i; + return NULL; +} + +char *opt_set_longval(const char *arg, long *l) +{ + char *endp; + + /* This is how the manpage says to do it. Yech. */ + errno = 0; + *l = strtol(arg, &endp, 0); + if (*endp || !arg[0]) + return arg_bad("'%s' is not a number", arg); + if (errno) + return arg_bad("'%s' is out of range", arg); + return NULL; +} + +char *opt_set_ulongval(const char *arg, unsigned long *ul) +{ + long int l; + char *err; + + err = opt_set_longval(arg, &l); + if (err) + return err; + *ul = l; + if (l < 0) + return arg_bad("'%s' is negative", arg); + return NULL; +} + +char *opt_inc_intval(int *i) +{ + (*i)++; + return NULL; +} + +/* Display version string. */ +char *opt_version_and_exit(const char *version) +{ + printf("%s\n", version); + fflush(stdout); + exit(0); +} + +char *opt_usage_and_exit(const char *extra) +{ + printf("%s", opt_usage(opt_argv0, extra)); + fflush(stdout); + exit(0); +} + +void opt_show_bool(char buf[OPT_SHOW_LEN], const bool *b) +{ + strncpy(buf, *b ? "true" : "false", OPT_SHOW_LEN); +} + +void opt_show_invbool(char buf[OPT_SHOW_LEN], const bool *b) +{ + strncpy(buf, *b ? "false" : "true", OPT_SHOW_LEN); +} + +void opt_show_charp(char buf[OPT_SHOW_LEN], char *const *p) +{ + size_t len = strlen(*p); + buf[0] = '"'; + if (len > OPT_SHOW_LEN - 2) + len = OPT_SHOW_LEN - 2; + strncpy(buf+1, *p, len); + buf[1+len] = '"'; + if (len < OPT_SHOW_LEN - 2) + buf[2+len] = '\0'; +} + +/* Set an integer value, various forms. Sets to 1 on arg == NULL. */ +void opt_show_intval(char buf[OPT_SHOW_LEN], const int *i) +{ + snprintf(buf, OPT_SHOW_LEN, "%i", *i); +} + +void opt_show_floatval(char buf[OPT_SHOW_LEN], const float *f) +{ + snprintf(buf, OPT_SHOW_LEN, "%.1f", *f); +} + +void opt_show_uintval(char buf[OPT_SHOW_LEN], const unsigned int *ui) +{ + snprintf(buf, OPT_SHOW_LEN, "%u", *ui); +} + +void opt_show_longval(char buf[OPT_SHOW_LEN], const long *l) +{ + snprintf(buf, OPT_SHOW_LEN, "%li", *l); +} + +void opt_show_ulongval(char buf[OPT_SHOW_LEN], const unsigned long *ul) +{ + snprintf(buf, OPT_SHOW_LEN, "%lu", *ul); +} diff --git a/ccan/opt/opt.c b/ccan/opt/opt.c new file mode 100644 index 0000000..48cd386 --- /dev/null +++ b/ccan/opt/opt.c @@ -0,0 +1,255 @@ +#include +#include +#include +#include +#include + +#ifndef WIN32 + #include +#else +#include + #define errx(status, fmt, ...) { \ + fprintf(stderr, fmt, __VA_ARGS__); \ + fprintf(stderr, "\n"); \ + exit(status); } +#endif + +#include +#include +#include +#include "private.h" + +struct opt_table *opt_table; +unsigned int opt_count, opt_num_short, opt_num_short_arg, opt_num_long; +const char *opt_argv0; + +/* Returns string after first '-'. */ +static const char *first_name(const char *names, unsigned *len) +{ + *len = strcspn(names + 1, "|= "); + return names + 1; +} + +static const char *next_name(const char *names, unsigned *len) +{ + names += *len; + if (names[0] == ' ' || names[0] == '=' || names[0] == '\0') + return NULL; + return first_name(names + 1, len); +} + +static const char *first_opt(unsigned *i, unsigned *len) +{ + for (*i = 0; *i < opt_count; (*i)++) { + if (opt_table[*i].type == OPT_SUBTABLE) + continue; + return first_name(opt_table[*i].names, len); + } + return NULL; +} + +static const char *next_opt(const char *p, unsigned *i, unsigned *len) +{ + for (; *i < opt_count; (*i)++) { + if (opt_table[*i].type == OPT_SUBTABLE) + continue; + if (!p) + return first_name(opt_table[*i].names, len); + p = next_name(p, len); + if (p) + return p; + } + return NULL; +} + +const char *first_lopt(unsigned *i, unsigned *len) +{ + const char *p; + for (p = first_opt(i, len); p; p = next_opt(p, i, len)) { + if (p[0] == '-') { + /* Skip leading "-" */ + (*len)--; + p++; + break; + } + } + return p; +} + +const char *next_lopt(const char *p, unsigned *i, unsigned *len) +{ + for (p = next_opt(p, i, len); p; p = next_opt(p, i, len)) { + if (p[0] == '-') { + /* Skip leading "-" */ + (*len)--; + p++; + break; + } + } + return p; +} + +const char *first_sopt(unsigned *i) +{ + const char *p; + unsigned int len = 0 /* GCC bogus warning */; + + for (p = first_opt(i, &len); p; p = next_opt(p, i, &len)) { + if (p[0] != '-') + break; + } + return p; +} + +const char *next_sopt(const char *p, unsigned *i) +{ + unsigned int len = 1; + for (p = next_opt(p, i, &len); p; p = next_opt(p, i, &len)) { + if (p[0] != '-') + break; + } + return p; +} + +static void check_opt(const struct opt_table *entry) +{ + const char *p; + unsigned len; + + if (entry->type != OPT_HASARG && entry->type != OPT_NOARG && entry->type != OPT_PROCESSARG) + errx(1, "Option %s: unknown entry type %u", + entry->names, entry->type); + + if (!entry->desc) + errx(1, "Option %s: description cannot be NULL", entry->names); + + + if (entry->names[0] != '-') + errx(1, "Option %s: does not begin with '-'", entry->names); + + for (p = first_name(entry->names, &len); p; p = next_name(p, &len)) { + if (*p == '-') { + if (len == 1) + errx(1, "Option %s: invalid long option '--'", + entry->names); + opt_num_long++; + } else { + if (len != 1) + errx(1, "Option %s: invalid short option" + " '%.*s'", entry->names, len+1, p-1); + opt_num_short++; + if (entry->type == OPT_HASARG || entry->type == OPT_PROCESSARG) + opt_num_short_arg++; + } + /* Don't document args unless there are some. */ + if (entry->type == OPT_NOARG) { + if (p[len] == ' ' || p[len] == '=') + errx(1, "Option %s: does not take arguments" + " '%s'", entry->names, p+len+1); + } + } +} + +static void add_opt(const struct opt_table *entry) +{ + opt_table = realloc(opt_table, sizeof(opt_table[0]) * (opt_count+1)); + opt_table[opt_count++] = *entry; +} + +void _opt_register(const char *names, enum opt_type type, + char *(*cb)(void *arg), + char *(*cb_arg)(const char *optarg, void *arg), + void (*show)(char buf[OPT_SHOW_LEN], const void *arg), + const void *arg, const char *desc) +{ + struct opt_table opt; + opt.names = names; + opt.type = type; + opt.cb = cb; + opt.cb_arg = cb_arg; + opt.show = show; + opt.u.carg = arg; + opt.desc = desc; + check_opt(&opt); + add_opt(&opt); +} + +void opt_register_table(const struct opt_table entry[], const char *desc) +{ + unsigned int i, start = opt_count; + + if (desc) { + struct opt_table heading = OPT_SUBTABLE(NULL, desc); + add_opt(&heading); + } + for (i = 0; entry[i].type != OPT_END; i++) { + if (entry[i].type == OPT_SUBTABLE) + opt_register_table(subtable_of(&entry[i]), + entry[i].desc); + else { + check_opt(&entry[i]); + add_opt(&entry[i]); + } + } + /* We store the table length in arg ptr. */ + if (desc) + opt_table[start].u.tlen = (opt_count - start); +} + +/* Parse your arguments. */ +bool opt_parse(int *argc, char *argv[], void (*errlog)(const char *fmt, ...)) +{ + int ret; + unsigned offset = 0; + + #ifdef WIN32 + char *original_argv0 = argv[0]; + argv[0] = (char*)basename(argv[0]); + #endif + + /* This helps opt_usage. */ + opt_argv0 = argv[0]; + + while ((ret = parse_one(argc, argv, &offset, errlog)) == 1); + + #ifdef WIN32 + argv[0] = original_argv0; + #endif + + /* parse_one returns 0 on finish, -1 on error */ + return (ret == 0); +} + +void opt_free_table(void) +{ + free(opt_table); + opt_table=0; +} + +void opt_log_stderr(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); +} + +void opt_log_stderr_exit(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + exit(1); +} + +char *opt_invalid_argument(const char *arg) +{ + char *str = malloc(sizeof("Invalid argument '%s'") + strlen(arg)); + sprintf(str, "Invalid argument '%s'", arg); + return str; +} diff --git a/ccan/opt/opt.h b/ccan/opt/opt.h new file mode 100644 index 0000000..90ff680 --- /dev/null +++ b/ccan/opt/opt.h @@ -0,0 +1,365 @@ +#ifndef CCAN_OPT_H +#define CCAN_OPT_H +#include +#include +#include +#include + +struct opt_table; + +/** + * OPT_WITHOUT_ARG() - macro for initializing an opt_table entry (without arg) + * @names: the names of the option eg. "--foo", "-f" or "--foo|-f|--foobar". + * @cb: the callback when the option is found. + * @arg: the argument to hand to @cb. + * @desc: the description for opt_usage(), or opt_hidden. + * + * This is a typesafe wrapper for initializing a struct opt_table. The callback + * of type "char *cb(type *)", "char *cb(const type *)" or "char *cb(void *)", + * where "type" is the type of the @arg argument. + * + * If the @cb returns non-NULL, opt_parse() will stop parsing, use the + * returned string to form an error message for errlog(), free() the + * string and return false. + * + * Any number of equivalent short or long options can be listed in @names, + * separated by '|'. Short options are a single hyphen followed by a single + * character, long options are two hyphens followed by one or more characters. + * + * See Also: + * OPT_WITH_ARG() + */ +#define OPT_WITHOUT_ARG(names, cb, arg, desc) \ + { (names), OPT_CB_NOARG((cb), (arg)), { (arg) }, (desc) } + +/** + * OPT_WITH_ARG() - macro for initializing long and short option (with arg) + * @names: the option names eg. "--foo=", "-f" or "-f|--foo ". + * @cb: the callback when the option is found (along with ). + * @show: the callback to print the value in get_usage (or NULL) + * @arg: the argument to hand to @cb and @show + * @desc: the description for opt_usage(), or opt_hidden. + * + * This is a typesafe wrapper for initializing a struct opt_table. The callback + * is of type "char *cb(const char *, type *)", + * "char *cb(const char *, const type *)" or "char *cb(const char *, void *)", + * where "type" is the type of the @arg argument. The first argument to the + * @cb is the argument found on the commandline. + * + * Similarly, if @show is not NULL, it should be of type "void *show(char *, + * const type *)". It should write up to OPT_SHOW_LEN bytes into the first + * argument; unless it uses the entire OPT_SHOW_LEN bytes it should + * nul-terminate that buffer. + * + * Any number of equivalent short or long options can be listed in @names, + * separated by '|'. Short options are a single hyphen followed by a single + * character, long options are two hyphens followed by one or more characters. + * A space or equals in @names is ignored for parsing, and only used + * for printing the usage. + * + * If the @cb returns non-NULL, opt_parse() will stop parsing, use the + * returned string to form an error message for errlog(), free() the + * string and return false. + * + * See Also: + * OPT_WITHOUT_ARG() + */ +#define OPT_WITH_ARG(name, cb, show, arg, desc) \ + { (name), OPT_CB_ARG((cb), (show), (arg)), { (arg) }, (desc) } + +/** + * OPT_WITH_CBARG() - variant of OPT_WITH_ARG which assigns arguments to arg + * and then performs the callback function on the args as well. + */ +#define OPT_WITH_CBARG(name, cb, show, arg, desc) \ + { (name), OPT_CB_WITHARG((cb), (show), (arg)), { (arg) }, (desc) } + +/** + * OPT_SUBTABLE() - macro for including another table inside a table. + * @table: the table to include in this table. + * @desc: description of this subtable (for opt_usage()) or NULL. + */ +#define OPT_SUBTABLE(table, desc) \ + { (const char *)(table), OPT_SUBTABLE, \ + sizeof(_check_is_entry(table)) ? NULL : NULL, NULL, NULL, \ + { NULL }, (desc) } + +/** + * OPT_ENDTABLE - macro to create final entry in table. + * + * This must be the final element in the opt_table array. + */ +#define OPT_ENDTABLE { NULL, OPT_END, NULL, NULL, NULL, { NULL }, NULL } + +/** + * opt_register_table - register a table of options + * @table: the table of options + * @desc: description of this subtable (for opt_usage()) or NULL. + * + * The table must be terminated by OPT_ENDTABLE. + * + * Example: + * static int verbose = 0; + * static struct opt_table opts[] = { + * OPT_WITHOUT_ARG("--verbose", opt_inc_intval, &verbose, + * "Verbose mode (can be specified more than once)"), + * OPT_WITHOUT_ARG("-v", opt_inc_intval, &verbose, + * "Verbose mode (can be specified more than once)"), + * OPT_WITHOUT_ARG("--usage", opt_usage_and_exit, + * "args...\nA silly test program.", + * "Print this message."), + * OPT_ENDTABLE + * }; + * + * ... + * opt_register_table(opts, NULL); + */ +void opt_register_table(const struct opt_table *table, const char *desc); + +/** + * opt_register_noarg - register an option with no arguments + * @names: the names of the option eg. "--foo", "-f" or "--foo|-f|--foobar". + * @cb: the callback when the option is found. + * @arg: the argument to hand to @cb. + * @desc: the verbose description of the option (for opt_usage()), or NULL. + * + * This is used for registering a single commandline option which takes + * no argument. + * + * The callback is of type "char *cb(type *)", "char *cb(const type *)" + * or "char *cb(void *)", where "type" is the type of the @arg + * argument. + * + * If the @cb returns non-NULL, opt_parse() will stop parsing, use the + * returned string to form an error message for errlog(), free() the + * string and return false. + */ +#define opt_register_noarg(names, cb, arg, desc) \ + _opt_register((names), OPT_CB_NOARG((cb), (arg)), (arg), (desc)) + +/** + * opt_register_arg - register an option with an arguments + * @names: the names of the option eg. "--foo", "-f" or "--foo|-f|--foobar". + * @cb: the callback when the option is found. + * @show: the callback to print the value in get_usage (or NULL) + * @arg: the argument to hand to @cb. + * @desc: the verbose description of the option (for opt_usage()), or NULL. + * + * This is used for registering a single commandline option which takes + * an argument. + * + * The callback is of type "char *cb(const char *, type *)", + * "char *cb(const char *, const type *)" or "char *cb(const char *, void *)", + * where "type" is the type of the @arg argument. The first argument to the + * @cb is the argument found on the commandline. + * + * At least one of @longopt and @shortopt must be non-zero. If the + * @cb returns false, opt_parse() will stop parsing and return false. + * + * Example: + * static char *explode(const char *optarg, void *unused) + * { + * errx(1, "BOOM! %s", optarg); + * } + * ... + * opt_register_arg("--explode|--boom", explode, NULL, NULL, opt_hidden); + */ +#define opt_register_arg(names, cb, show, arg, desc) \ + _opt_register((names), OPT_CB_ARG((cb), (show), (arg)), (arg), (desc)) + +/** + * opt_parse - parse arguments. + * @argc: pointer to argc + * @argv: argv array. + * @errlog: the function to print errors + * + * This iterates through the command line and calls callbacks registered with + * opt_register_table()/opt_register_arg()/opt_register_noarg(). If there + * are unknown options, missing arguments or a callback returns false, then + * an error message is printed and false is returned. + * + * On success, argc and argv are adjusted so only the non-option elements + * remain, and true is returned. + * + * Example: + * if (!opt_parse(&argc, argv, opt_log_stderr)) { + * printf("You screwed up, aborting!\n"); + * exit(1); + * } + * + * See Also: + * opt_log_stderr, opt_log_stderr_exit + */ +bool opt_parse(int *argc, char *argv[], void (*errlog)(const char *fmt, ...)); + +/** + * opt_free_table - free the table. + * + * This frees the internal memory. Call this as the last + * opt function. + */ +void opt_free_table(void); + +/** + * opt_log_stderr - print message to stderr. + * @fmt: printf-style format. + * + * This is a helper for opt_parse, to print errors to stderr. + * + * See Also: + * opt_log_stderr_exit + */ +void opt_log_stderr(const char *fmt, ...); + +/** + * opt_log_stderr_exit - print message to stderr, then exit(1) + * @fmt: printf-style format. + * + * Just like opt_log_stderr, only then does exit(1). This means that + * when handed to opt_parse, opt_parse will never return false. + * + * Example: + * // This never returns false; just exits if there's an erorr. + * opt_parse(&argc, argv, opt_log_stderr_exit); + */ +void opt_log_stderr_exit(const char *fmt, ...); + +/** + * opt_invalid_argument - helper to allocate an "Invalid argument '%s'" string + * @arg: the argument which was invalid. + * + * This is a helper for callbacks to return a simple error string. + */ +char *opt_invalid_argument(const char *arg); + +/** + * opt_usage - create usage message + * @argv0: the program name + * @extra: extra details to print after the initial command, or NULL. + * + * Creates a usage message, with the program name, arguments, some extra details + * and a table of all the options with their descriptions. If an option has + * description opt_hidden, it is not shown here. + * + * If "extra" is NULL, then the extra information is taken from any + * registered option which calls opt_usage_and_exit(). This avoids duplicating + * that string in the common case. + * + * The result should be passed to free(). + */ +char *opt_usage(const char *argv0, const char *extra); + +/** + * opt_hidden - string for undocumented options. + * + * This can be used as the desc parameter if you want an option not to be + * shown by opt_usage(). + */ +extern const char opt_hidden[]; + +/* Maximum length of arg to show in opt_usage */ +#define OPT_SHOW_LEN 80 + +/* Standard helpers. You can write your own: */ +/* Sets the @b to true. */ +char *opt_set_bool(bool *b); +/* Sets @b based on arg: (yes/no/true/false). */ +char *opt_set_bool_arg(const char *arg, bool *b); +void opt_show_bool(char buf[OPT_SHOW_LEN], const bool *b); +/* The inverse */ +char *opt_set_invbool(bool *b); +void opt_show_invbool(char buf[OPT_SHOW_LEN], const bool *b); +/* Sets @b based on !arg: (yes/no/true/false). */ +char *opt_set_invbool_arg(const char *arg, bool *b); + +/* Set a char *. */ +char *opt_set_charp(const char *arg, char **p); +void opt_show_charp(char buf[OPT_SHOW_LEN], char *const *p); + +/* Set an integer value, various forms. Sets to 1 on arg == NULL. */ +char *opt_set_intval(const char *arg, int *i); +void opt_show_intval(char buf[OPT_SHOW_LEN], const int *i); +char *opt_set_floatval(const char *arg, float *f); +void opt_show_floatval(char buf[OPT_SHOW_LEN], const float *f); +char *opt_set_uintval(const char *arg, unsigned int *ui); +void opt_show_uintval(char buf[OPT_SHOW_LEN], const unsigned int *ui); +char *opt_set_longval(const char *arg, long *l); +void opt_show_longval(char buf[OPT_SHOW_LEN], const long *l); +char *opt_set_ulongval(const char *arg, unsigned long *ul); +void opt_show_ulongval(char buf[OPT_SHOW_LEN], const unsigned long *ul); + +/* Increment. */ +char *opt_inc_intval(int *i); + +/* Display version string to stdout, exit(0). */ +char *opt_version_and_exit(const char *version); + +/* Display usage string to stdout, exit(0). */ +char *opt_usage_and_exit(const char *extra); + +/* Below here are private declarations. */ +/* You can use this directly to build tables, but the macros will ensure + * consistency and type safety. */ +enum opt_type { + OPT_NOARG = 1, /* -f|--foo */ + OPT_HASARG = 2, /* -f arg|--foo=arg|--foo arg */ + OPT_PROCESSARG = 4, + OPT_SUBTABLE = 8, /* Actually, longopt points to a subtable... */ + OPT_END = 16, /* End of the table. */ +}; + +struct opt_table { + const char *names; /* pipe-separated names, --longopt or -s */ + enum opt_type type; + char *(*cb)(void *arg); /* OPT_NOARG */ + char *(*cb_arg)(const char *optarg, void *arg); /* OPT_HASARG */ + void (*show)(char buf[OPT_SHOW_LEN], const void *arg); + union { + const void *carg; + void *arg; + size_t tlen; + } u; + const char *desc; +}; + +/* Resolves to the four parameters for non-arg callbacks. */ +#define OPT_CB_NOARG(cb, arg) \ + OPT_NOARG, \ + typesafe_cb_cast3(char *(*)(void *), \ + char *(*)(typeof(*(arg))*), \ + char *(*)(const typeof(*(arg))*), \ + char *(*)(const void *), (cb)), \ + NULL, NULL + +/* Resolves to the four parameters for arg callbacks. */ +#define OPT_CB_ARG(cb, show, arg) \ + OPT_HASARG, NULL, \ + typesafe_cb_cast3(char *(*)(const char *,void *), \ + char *(*)(const char *, typeof(*(arg))*), \ + char *(*)(const char *, const typeof(*(arg))*), \ + char *(*)(const char *, const void *), \ + (cb)), \ + typesafe_cb_cast(void (*)(char buf[], const void *), \ + void (*)(char buf[], const typeof(*(arg))*), (show)) + +#define OPT_CB_WITHARG(cb, show, arg) \ + OPT_PROCESSARG, NULL, \ + typesafe_cb_cast3(char *(*)(const char *,void *), \ + char *(*)(const char *, typeof(*(arg))*), \ + char *(*)(const char *, const typeof(*(arg))*), \ + char *(*)(const char *, const void *), \ + (cb)), \ + typesafe_cb_cast(void (*)(char buf[], const void *), \ + void (*)(char buf[], const typeof(*(arg))*), (show)) + +/* Non-typesafe register function. */ +void _opt_register(const char *names, enum opt_type type, + char *(*cb)(void *arg), + char *(*cb_arg)(const char *optarg, void *arg), + void (*show)(char buf[OPT_SHOW_LEN], const void *arg), + const void *arg, const char *desc); + +/* We use this to get typechecking for OPT_SUBTABLE */ +static inline int _check_is_entry(struct opt_table *e UNUSED) { return 0; } + +#endif /* CCAN_OPT_H */ diff --git a/ccan/opt/parse.c b/ccan/opt/parse.c new file mode 100644 index 0000000..18af157 --- /dev/null +++ b/ccan/opt/parse.c @@ -0,0 +1,132 @@ +/* Actual code to parse commandline. */ +#include +#include +#include +#include +#include "private.h" + +/* glibc does this as: +/tmp/opt-example: invalid option -- 'x' +/tmp/opt-example: unrecognized option '--long' +/tmp/opt-example: option '--someflag' doesn't allow an argument +/tmp/opt-example: option '--s' is ambiguous +/tmp/opt-example: option requires an argument -- 's' +*/ +static int parse_err(void (*errlog)(const char *fmt, ...), + const char *argv0, const char *arg, unsigned len, + const char *problem) +{ + errlog("%s: %.*s: %s", argv0, len, arg, problem); + return -1; +} + +static void consume_option(int *argc, char *argv[], unsigned optnum) +{ + memmove(&argv[optnum], &argv[optnum+1], + sizeof(argv[optnum]) * (*argc-optnum)); + (*argc)--; +} + +/* Returns 1 if argument consumed, 0 if all done, -1 on error. */ +int parse_one(int *argc, char *argv[], unsigned *offset, + void (*errlog)(const char *fmt, ...)) +{ + unsigned i, arg, len; + const char *o, *optarg = NULL; + char *problem; + + if (getenv("POSIXLY_CORRECT")) { + /* Don't find options after non-options. */ + arg = 1; + } else { + for (arg = 1; argv[arg]; arg++) { + if (argv[arg][0] == '-') + break; + } + } + + if (!argv[arg] || argv[arg][0] != '-') + return 0; + + /* Special arg terminator option. */ + if (strcmp(argv[arg], "--") == 0) { + consume_option(argc, argv, arg); + return 0; + } + + /* Long options start with -- */ + if (argv[arg][1] == '-') { + assert(*offset == 0); + for (o = first_lopt(&i, &len); o; o = next_lopt(o, &i, &len)) { + if (strncmp(argv[arg] + 2, o, len) != 0) + continue; + if (argv[arg][2 + len] == '=') + optarg = argv[arg] + 2 + len + 1; + else if (argv[arg][2 + len] != '\0') + continue; + break; + } + if (!o) + return parse_err(errlog, argv[0], + argv[arg], strlen(argv[arg]), + "unrecognized option"); + /* For error messages, we include the leading '--' */ + o -= 2; + len += 2; + } else { + /* offset allows us to handle -abc */ + for (o = first_sopt(&i); o; o = next_sopt(o, &i)) { + if (argv[arg][*offset + 1] != *o) + continue; + (*offset)++; + break; + } + if (!o) + return parse_err(errlog, argv[0], + argv[arg], strlen(argv[arg]), + "unrecognized option"); + /* For error messages, we include the leading '-' */ + o--; + len = 2; + } + + if (opt_table[i].type == OPT_NOARG) { + if (optarg) + return parse_err(errlog, argv[0], o, len, + "doesn't allow an argument"); + problem = opt_table[i].cb(opt_table[i].u.arg); + } else { + if (!optarg) { + /* Swallow any short options as optarg, eg -afile */ + if (*offset && argv[arg][*offset + 1]) { + optarg = argv[arg] + *offset + 1; + *offset = 0; + } else + optarg = argv[arg+1]; + } + if (!optarg) + return parse_err(errlog, argv[0], o, len, + "requires an argument"); + if (opt_table[i].type == OPT_PROCESSARG) + opt_set_charp(optarg, opt_table[i].u.arg); + problem = opt_table[i].cb_arg(optarg, opt_table[i].u.arg); + } + + if (problem) { + parse_err(errlog, argv[0], o, len, problem); + free(problem); + return -1; + } + + /* If no more letters in that short opt, reset offset. */ + if (*offset && !argv[arg][*offset + 1]) + *offset = 0; + + /* All finished with that option? */ + if (*offset == 0) { + consume_option(argc, argv, arg); + if (optarg && optarg == argv[arg]) + consume_option(argc, argv, arg); + } + return 1; +} diff --git a/ccan/opt/private.h b/ccan/opt/private.h new file mode 100644 index 0000000..048951e --- /dev/null +++ b/ccan/opt/private.h @@ -0,0 +1,19 @@ +#ifndef CCAN_OPT_PRIVATE_H +#define CCAN_OPT_PRIVATE_H + +extern struct opt_table *opt_table; +extern unsigned int opt_count, opt_num_short, opt_num_short_arg, opt_num_long; + +extern const char *opt_argv0; + +#define subtable_of(entry) ((struct opt_table *)((entry)->names)) + +const char *first_sopt(unsigned *i); +const char *next_sopt(const char *names, unsigned *i); +const char *first_lopt(unsigned *i, unsigned *len); +const char *next_lopt(const char *p, unsigned *i, unsigned *len); + +int parse_one(int *argc, char *argv[], unsigned *offset, + void (*errlog)(const char *fmt, ...)); + +#endif /* CCAN_OPT_PRIVATE_H */ diff --git a/ccan/opt/test/compile_ok-const-arg.c b/ccan/opt/test/compile_ok-const-arg.c new file mode 100644 index 0000000..f1d10da --- /dev/null +++ b/ccan/opt/test/compile_ok-const-arg.c @@ -0,0 +1,13 @@ +#include +#include +#include +#include +#include + +int main(int argc, char *argv[]) +{ + opt_register_noarg("-v", opt_version_and_exit, + (const char *)"1.2.3", + (const char *)"Print version"); + return 0; +} diff --git a/ccan/opt/test/run-checkopt.c b/ccan/opt/test/run-checkopt.c new file mode 100644 index 0000000..71ee3c4 --- /dev/null +++ b/ccan/opt/test/run-checkopt.c @@ -0,0 +1,144 @@ +#include "config.h" +#include +#include +#include +#include +#include +#include +#include "utils.h" + +/* We don't actually want it to exit... */ +static jmp_buf exited; +#define errx save_and_jump + +static void save_and_jump(int ecode, const char *fmt, ...); + +#include +#include +#include +#include + +static char *output = NULL; + +static int saved_vprintf(const char *fmt, va_list ap) +{ + char *p; + int ret = vasprintf(&p, fmt, ap); + + if (output) { + output = realloc(output, strlen(output) + strlen(p) + 1); + strcat(output, p); + free(p); + } else + output = p; + return ret; +} + +static void save_and_jump(int ecode, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + saved_vprintf(fmt, ap); + va_end(ap); + longjmp(exited, ecode + 1); +} + +static void reset(void) +{ + free(output); + output = NULL; + free(opt_table); + opt_table = NULL; + opt_count = opt_num_short = opt_num_short_arg = opt_num_long = 0; +} + +int main(int argc, char *argv[]) +{ + int exitval; + + plan_tests(14); + + exitval = setjmp(exited); + if (exitval == 0) { + /* Bad type. */ + _opt_register("-a", OPT_SUBTABLE, (void *)opt_version_and_exit, + NULL, NULL, "1.2.3", ""); + fail("_opt_register returned?"); + } else { + ok1(exitval - 1 == 1); + ok1(strstr(output, "Option -a: unknown entry type")); + } + reset(); + + exitval = setjmp(exited); + if (exitval == 0) { + /* NULL description. */ + opt_register_noarg("-a", test_noarg, "", NULL); + fail("_opt_register returned?"); + } else { + ok1(exitval - 1 == 1); + ok1(strstr(output, "Option -a: description cannot be NULL")); + } + reset(); + + exitval = setjmp(exited); + if (exitval == 0) { + /* Bad option name. */ + opt_register_noarg("a", test_noarg, "", ""); + fail("_opt_register returned?"); + } else { + ok1(exitval - 1 == 1); + ok1(strstr(output, "Option a: does not begin with '-'")); + } + + reset(); + + exitval = setjmp(exited); + if (exitval == 0) { + /* Bad option name. */ + opt_register_noarg("--", test_noarg, "", ""); + fail("_opt_register returned?"); + } else { + ok1(exitval - 1 == 1); + ok1(strstr(output, "Option --: invalid long option '--'")); + } + + reset(); + + exitval = setjmp(exited); + if (exitval == 0) { + /* Bad option name. */ + opt_register_noarg("--a|-aaa", test_noarg, "", ""); + fail("_opt_register returned?"); + } else { + ok1(exitval - 1 == 1); + ok1(strstr(output, + "Option --a|-aaa: invalid short option '-aaa'")); + } + reset(); + + exitval = setjmp(exited); + if (exitval == 0) { + /* Documentation for non-optios. */ + opt_register_noarg("--a foo", test_noarg, "", ""); + fail("_opt_register returned?"); + } else { + ok1(exitval - 1 == 1); + ok1(strstr(output, + "Option --a foo: does not take arguments 'foo'")); + } + reset(); + + exitval = setjmp(exited); + if (exitval == 0) { + /* Documentation for non-optios. */ + opt_register_noarg("--a=foo", test_noarg, "", ""); + fail("_opt_register returned?"); + } else { + ok1(exitval - 1 == 1); + ok1(strstr(output, + "Option --a=foo: does not take arguments 'foo'")); + } + return exit_status(); +} diff --git a/ccan/opt/test/run-correct-reporting.c b/ccan/opt/test/run-correct-reporting.c new file mode 100644 index 0000000..8534f29 --- /dev/null +++ b/ccan/opt/test/run-correct-reporting.c @@ -0,0 +1,49 @@ +/* Make sure when multiple equivalent options, correct one is used for errors */ + +#include +#include +#include +#include +#include +#include +#include "utils.h" + +int main(int argc, char *argv[]) +{ + plan_tests(12); + + /* --aaa without args. */ + opt_register_arg("-a|--aaa", test_arg, NULL, "aaa", ""); + ok1(!parse_args(&argc, &argv, "--aaa", NULL)); + ok1(strstr(err_output, ": --aaa: requires an argument")); + free(err_output); + err_output = NULL; + ok1(!parse_args(&argc, &argv, "-a", NULL)); + ok1(strstr(err_output, ": -a: requires an argument")); + free(err_output); + err_output = NULL; + + /* Multiple */ + opt_register_arg("--bbb|-b|-c|--ccc", test_arg, NULL, "aaa", ""); + ok1(!parse_args(&argc, &argv, "--bbb", NULL)); + ok1(strstr(err_output, ": --bbb: requires an argument")); + free(err_output); + err_output = NULL; + ok1(!parse_args(&argc, &argv, "-b", NULL)); + ok1(strstr(err_output, ": -b: requires an argument")); + free(err_output); + err_output = NULL; + ok1(!parse_args(&argc, &argv, "-c", NULL)); + ok1(strstr(err_output, ": -c: requires an argument")); + free(err_output); + err_output = NULL; + ok1(!parse_args(&argc, &argv, "--ccc", NULL)); + ok1(strstr(err_output, ": --ccc: requires an argument")); + free(err_output); + err_output = NULL; + + /* parse_args allocates argv */ + free(argv); + return exit_status(); +} + diff --git a/ccan/opt/test/run-helpers.c b/ccan/opt/test/run-helpers.c new file mode 100644 index 0000000..a58e4d9 --- /dev/null +++ b/ccan/opt/test/run-helpers.c @@ -0,0 +1,440 @@ +#include "config.h" +#include +#include +#include +#include +#include +#include "utils.h" + +/* We don't actually want it to exit... */ +static jmp_buf exited; +#define exit(status) longjmp(exited, (status) + 1) + +#define printf saved_printf +static int saved_printf(const char *fmt, ...); + +#define fprintf saved_fprintf +static int saved_fprintf(FILE *ignored, const char *fmt, ...); + +#define vfprintf(f, fmt, ap) saved_vprintf(fmt, ap) +static int saved_vprintf(const char *fmt, va_list ap); + +#define malloc(size) saved_malloc(size) +static void *saved_malloc(size_t size); + +#include +#include +#include +#include + +static void reset_options(void) +{ + free(opt_table); + opt_table = NULL; + opt_count = opt_num_short = opt_num_short_arg = opt_num_long = 0; +} + +static char *output = NULL; + +static int saved_vprintf(const char *fmt, va_list ap) +{ + char *p; + int ret = vasprintf(&p, fmt, ap); + + if (output) { + output = realloc(output, strlen(output) + strlen(p) + 1); + strcat(output, p); + free(p); + } else + output = p; + return ret; +} + +static int saved_printf(const char *fmt, ...) +{ + va_list ap; + int ret; + + va_start(ap, fmt); + ret = saved_vprintf(fmt, ap); + va_end(ap); + return ret; +} + +static int saved_fprintf(FILE *ignored, const char *fmt, ...) +{ + va_list ap; + int ret; + + va_start(ap, fmt); + ret = saved_vprintf(fmt, ap); + va_end(ap); + return ret; +} + +#undef malloc +static void *last_allocation; +static void *saved_malloc(size_t size) +{ + return last_allocation = malloc(size); +} + +/* Test helpers. */ +int main(int argc, char *argv[]) +{ + plan_tests(100); + + /* opt_set_bool */ + { + bool arg = false; + reset_options(); + opt_register_noarg("-a", opt_set_bool, &arg, ""); + ok1(parse_args(&argc, &argv, "-a", NULL)); + ok1(arg); + opt_register_arg("-b", opt_set_bool_arg, NULL, &arg, ""); + ok1(parse_args(&argc, &argv, "-b", "no", NULL)); + ok1(!arg); + ok1(parse_args(&argc, &argv, "-b", "yes", NULL)); + ok1(arg); + ok1(parse_args(&argc, &argv, "-b", "false", NULL)); + ok1(!arg); + ok1(parse_args(&argc, &argv, "-b", "true", NULL)); + ok1(arg); + ok1(!parse_args(&argc, &argv, "-b", "unknown", NULL)); + ok1(arg); + ok1(strstr(err_output, ": -b: Invalid argument 'unknown'")); + } + /* opt_set_invbool */ + { + bool arg = true; + reset_options(); + opt_register_noarg("-a", opt_set_invbool, &arg, ""); + ok1(parse_args(&argc, &argv, "-a", NULL)); + ok1(!arg); + opt_register_arg("-b", opt_set_invbool_arg, NULL, + &arg, ""); + ok1(parse_args(&argc, &argv, "-b", "no", NULL)); + ok1(arg); + ok1(parse_args(&argc, &argv, "-b", "yes", NULL)); + ok1(!arg); + ok1(parse_args(&argc, &argv, "-b", "false", NULL)); + ok1(arg); + ok1(parse_args(&argc, &argv, "-b", "true", NULL)); + ok1(!arg); + ok1(!parse_args(&argc, &argv, "-b", "unknown", NULL)); + ok1(!arg); + ok1(strstr(err_output, ": -b: Invalid argument 'unknown'")); + } + /* opt_set_charp */ + { + char *arg = (char *)"wrong"; + reset_options(); + opt_register_arg("-a", opt_set_charp, NULL, &arg, "All"); + ok1(parse_args(&argc, &argv, "-a", "string", NULL)); + ok1(strcmp(arg, "string") == 0); + } + /* opt_set_intval */ + { + int arg = 1000; + reset_options(); + opt_register_arg("-a", opt_set_intval, NULL, &arg, "All"); + ok1(parse_args(&argc, &argv, "-a", "9999", NULL)); + ok1(arg == 9999); + ok1(parse_args(&argc, &argv, "-a", "-9999", NULL)); + ok1(arg == -9999); + ok1(parse_args(&argc, &argv, "-a", "0", NULL)); + ok1(arg == 0); + ok1(!parse_args(&argc, &argv, "-a", "100crap", NULL)); + if (sizeof(int) == 4) + ok1(!parse_args(&argc, &argv, "-a", "4294967296", NULL)); + else + fail("Handle other int sizes"); + } + /* opt_set_uintval */ + { + unsigned int arg = 1000; + reset_options(); + opt_register_arg("-a", opt_set_uintval, NULL, &arg, "All"); + ok1(parse_args(&argc, &argv, "-a", "9999", NULL)); + ok1(arg == 9999); + ok1(!parse_args(&argc, &argv, "-a", "-9999", NULL)); + ok1(parse_args(&argc, &argv, "-a", "0", NULL)); + ok1(arg == 0); + ok1(!parse_args(&argc, &argv, "-a", "100crap", NULL)); + ok1(!parse_args(&argc, &argv, "-a", "4294967296", NULL)); + if (ULONG_MAX == UINT_MAX) { + pass("Can't test overflow"); + pass("Can't test error message"); + } else { + char buf[30]; + sprintf(buf, "%lu", ULONG_MAX); + ok1(!parse_args(&argc, &argv, "-a", buf, NULL)); + ok1(strstr(err_output, ": -a: value '") + && strstr(err_output, buf) + && strstr(err_output, "' does not fit into an integer")); + } + } + /* opt_set_longval */ + { + long int arg = 1000; + reset_options(); + opt_register_arg("-a", opt_set_longval, NULL, &arg, "All"); + ok1(parse_args(&argc, &argv, "-a", "9999", NULL)); + ok1(arg == 9999); + ok1(parse_args(&argc, &argv, "-a", "-9999", NULL)); + ok1(arg == -9999); + ok1(parse_args(&argc, &argv, "-a", "0", NULL)); + ok1(arg == 0); + ok1(!parse_args(&argc, &argv, "-a", "100crap", NULL)); + if (sizeof(long) == 4) + ok1(!parse_args(&argc, &argv, "-a", "4294967296", NULL)); + else if (sizeof(long)== 8) + ok1(!parse_args(&argc, &argv, "-a", "18446744073709551616", NULL)); + else + fail("FIXME: Handle other long sizes"); + } + /* opt_set_ulongval */ + { + unsigned long int arg = 1000; + reset_options(); + opt_register_arg("-a", opt_set_ulongval, NULL, &arg, "All"); + ok1(parse_args(&argc, &argv, "-a", "9999", NULL)); + ok1(arg == 9999); + ok1(!parse_args(&argc, &argv, "-a", "-9999", NULL)); + ok1(parse_args(&argc, &argv, "-a", "0", NULL)); + ok1(arg == 0); + ok1(!parse_args(&argc, &argv, "-a", "100crap", NULL)); + if (sizeof(long) == 4) + ok1(!parse_args(&argc, &argv, "-a", "4294967296", NULL)); + else if (sizeof(long)== 8) + ok1(!parse_args(&argc, &argv, "-a", "18446744073709551616", NULL)); + else + fail("FIXME: Handle other long sizes"); + } + /* opt_inc_intval */ + { + int arg = 1000; + reset_options(); + opt_register_noarg("-a", opt_inc_intval, &arg, ""); + ok1(parse_args(&argc, &argv, "-a", NULL)); + ok1(arg == 1001); + ok1(parse_args(&argc, &argv, "-a", "-a", NULL)); + ok1(arg == 1003); + ok1(parse_args(&argc, &argv, "-aa", NULL)); + ok1(arg == 1005); + } + + /* opt_show_version_and_exit. */ + { + int exitval; + reset_options(); + opt_register_noarg("-a", + opt_version_and_exit, "1.2.3", ""); + /* parse_args allocates argv */ + free(argv); + + argc = 2; + argv = malloc(sizeof(argv[0]) * 3); + argv[0] = "thisprog"; + argv[1] = "-a"; + argv[2] = NULL; + + exitval = setjmp(exited); + if (exitval == 0) { + opt_parse(&argc, argv, save_err_output); + fail("opt_show_version_and_exit returned?"); + } else { + ok1(exitval - 1 == 0); + } + ok1(strcmp(output, "1.2.3\n") == 0); + free(output); + free(argv); + output = NULL; + } + + /* opt_usage_and_exit. */ + { + int exitval; + reset_options(); + opt_register_noarg("-a", + opt_usage_and_exit, "[args]", ""); + + argc = 2; + argv = malloc(sizeof(argv[0]) * 3); + argv[0] = "thisprog"; + argv[1] = "-a"; + argv[2] = NULL; + + exitval = setjmp(exited); + if (exitval == 0) { + opt_parse(&argc, argv, save_err_output); + fail("opt_usage_and_exit returned?"); + } else { + ok1(exitval - 1 == 0); + } + ok1(strstr(output, "[args]")); + ok1(strstr(output, argv[0])); + ok1(strstr(output, "[-a]")); + free(output); + free(argv); + /* It exits without freeing usage string. */ + free(last_allocation); + output = NULL; + } + + /* opt_show_bool */ + { + bool b; + char buf[OPT_SHOW_LEN+2] = { 0 }; + buf[OPT_SHOW_LEN] = '!'; + + b = true; + opt_show_bool(buf, &b); + ok1(strcmp(buf, "true") == 0); + ok1(buf[OPT_SHOW_LEN] == '!'); + + b = false; + opt_show_bool(buf, &b); + ok1(strcmp(buf, "false") == 0); + ok1(buf[OPT_SHOW_LEN] == '!'); + } + + /* opt_show_invbool */ + { + bool b; + char buf[OPT_SHOW_LEN+2] = { 0 }; + buf[OPT_SHOW_LEN] = '!'; + + b = true; + opt_show_invbool(buf, &b); + ok1(strcmp(buf, "false") == 0); + ok1(buf[OPT_SHOW_LEN] == '!'); + + b = false; + opt_show_invbool(buf, &b); + ok1(strcmp(buf, "true") == 0); + ok1(buf[OPT_SHOW_LEN] == '!'); + } + + /* opt_show_charp */ + { + char str[OPT_SHOW_LEN*2], *p; + char buf[OPT_SHOW_LEN+2] = { 0 }; + buf[OPT_SHOW_LEN] = '!'; + + /* Short test. */ + p = str; + strcpy(p, "short"); + opt_show_charp(buf, &p); + ok1(strcmp(buf, "\"short\"") == 0); + ok1(buf[OPT_SHOW_LEN] == '!'); + + /* Truncate test. */ + memset(p, 'x', OPT_SHOW_LEN*2); + p[OPT_SHOW_LEN*2-1] = '\0'; + opt_show_charp(buf, &p); + ok1(buf[0] == '"'); + ok1(buf[OPT_SHOW_LEN-1] == '"'); + ok1(buf[OPT_SHOW_LEN] == '!'); + ok1(strspn(buf+1, "x") == OPT_SHOW_LEN-2); + } + + /* opt_show_intval */ + { + int i; + char buf[OPT_SHOW_LEN+2] = { 0 }; + buf[OPT_SHOW_LEN] = '!'; + + i = -77; + opt_show_intval(buf, &i); + ok1(strcmp(buf, "-77") == 0); + ok1(buf[OPT_SHOW_LEN] == '!'); + + i = 77; + opt_show_intval(buf, &i); + ok1(strcmp(buf, "77") == 0); + ok1(buf[OPT_SHOW_LEN] == '!'); + } + + /* opt_show_uintval */ + { + unsigned int ui; + char buf[OPT_SHOW_LEN+2] = { 0 }; + buf[OPT_SHOW_LEN] = '!'; + + ui = 4294967295U; + opt_show_uintval(buf, &ui); + ok1(strcmp(buf, "4294967295") == 0); + ok1(buf[OPT_SHOW_LEN] == '!'); + } + + /* opt_show_longval */ + { + long l; + char buf[OPT_SHOW_LEN+2] = { 0 }; + buf[OPT_SHOW_LEN] = '!'; + + l = 1234567890L; + opt_show_longval(buf, &l); + ok1(strcmp(buf, "1234567890") == 0); + ok1(buf[OPT_SHOW_LEN] == '!'); + } + + /* opt_show_ulongval */ + { + unsigned long ul; + char buf[OPT_SHOW_LEN+2] = { 0 }; + buf[OPT_SHOW_LEN] = '!'; + + ul = 4294967295UL; + opt_show_ulongval(buf, &ul); + ok1(strcmp(buf, "4294967295") == 0); + ok1(buf[OPT_SHOW_LEN] == '!'); + } + + /* opt_log_stderr. */ + { + reset_options(); + opt_register_noarg("-a", + opt_usage_and_exit, "[args]", ""); + + argc = 2; + argv = malloc(sizeof(argv[0]) * 3); + argv[0] = "thisprog"; + argv[1] = "--garbage"; + argv[2] = NULL; + ok1(!opt_parse(&argc, argv, opt_log_stderr)); + ok1(!strcmp(output, + "thisprog: --garbage: unrecognized option\n")); + free(output); + free(argv); + output = NULL; + } + + /* opt_log_stderr_exit. */ + { + int exitval; + reset_options(); + opt_register_noarg("-a", + opt_usage_and_exit, "[args]", ""); + argc = 2; + argv = malloc(sizeof(argv[0]) * 3); + argv[0] = "thisprog"; + argv[1] = "--garbage"; + argv[2] = NULL; + exitval = setjmp(exited); + if (exitval == 0) { + opt_parse(&argc, argv, opt_log_stderr_exit); + fail("opt_log_stderr_exit returned?"); + } else { + ok1(exitval - 1 == 1); + } + free(argv); + ok1(!strcmp(output, + "thisprog: --garbage: unrecognized option\n")); + free(output); + output = NULL; + } + + return exit_status(); +} diff --git a/ccan/opt/test/run-iter.c b/ccan/opt/test/run-iter.c new file mode 100644 index 0000000..66fd5db --- /dev/null +++ b/ccan/opt/test/run-iter.c @@ -0,0 +1,88 @@ +#include +#include +#include +#include +#include +#include "utils.h" +#include +#include +#include +#include + +static void reset_options(void) +{ + free(opt_table); + opt_table = NULL; + opt_count = opt_num_short = opt_num_short_arg = opt_num_long = 0; +} + +/* Test iterators. */ +int main(int argc, char *argv[]) +{ + unsigned j, i, len = 0; + const char *p; + + plan_tests(37 * 2); + for (j = 0; j < 2; j ++) { + reset_options(); + /* Giving subtable a title makes an extra entry! */ + opt_register_table(subtables, j == 0 ? NULL : "subtable"); + + p = first_lopt(&i, &len); + ok1(i == j + 0); + ok1(len == 3); + ok1(strncmp(p, "jjj", len) == 0); + p = next_lopt(p, &i, &len); + ok1(i == j + 0); + ok1(len == 3); + ok1(strncmp(p, "lll", len) == 0); + p = next_lopt(p, &i, &len); + ok1(i == j + 1); + ok1(len == 3); + ok1(strncmp(p, "mmm", len) == 0); + p = next_lopt(p, &i, &len); + ok1(i == j + 5); + ok1(len == 3); + ok1(strncmp(p, "ddd", len) == 0); + p = next_lopt(p, &i, &len); + ok1(i == j + 6); + ok1(len == 3); + ok1(strncmp(p, "eee", len) == 0); + p = next_lopt(p, &i, &len); + ok1(i == j + 7); + ok1(len == 3); + ok1(strncmp(p, "ggg", len) == 0); + p = next_lopt(p, &i, &len); + ok1(i == j + 8); + ok1(len == 3); + ok1(strncmp(p, "hhh", len) == 0); + p = next_lopt(p, &i, &len); + ok1(!p); + + p = first_sopt(&i); + ok1(i == j + 0); + ok1(*p == 'j'); + p = next_sopt(p, &i); + ok1(i == j + 0); + ok1(*p == 'l'); + p = next_sopt(p, &i); + ok1(i == j + 1); + ok1(*p == 'm'); + p = next_sopt(p, &i); + ok1(i == j + 2); + ok1(*p == 'a'); + p = next_sopt(p, &i); + ok1(i == j + 3); + ok1(*p == 'b'); + p = next_sopt(p, &i); + ok1(i == j + 7); + ok1(*p == 'g'); + p = next_sopt(p, &i); + ok1(i == j + 8); + ok1(*p == 'h'); + p = next_sopt(p, &i); + ok1(!p); + } + + return exit_status(); +} diff --git a/ccan/opt/test/run-no-options.c b/ccan/opt/test/run-no-options.c new file mode 100644 index 0000000..cf255fe --- /dev/null +++ b/ccan/opt/test/run-no-options.c @@ -0,0 +1,33 @@ +/* Make sure we still work with no options registered */ +#include +#include +#include +#include +#include +#include +#include "utils.h" + +int main(int argc, char *argv[]) +{ + const char *myname = argv[0]; + + plan_tests(7); + + /* Simple short arg.*/ + ok1(!parse_args(&argc, &argv, "-a", NULL)); + /* Simple long arg.*/ + ok1(!parse_args(&argc, &argv, "--aaa", NULL)); + + /* Extra arguments preserved. */ + ok1(parse_args(&argc, &argv, "extra", "args", NULL)); + ok1(argc == 3); + ok1(argv[0] == myname); + ok1(strcmp(argv[1], "extra") == 0); + ok1(strcmp(argv[2], "args") == 0); + + /* parse_args allocates argv */ + free(argv); + + return exit_status(); +} + diff --git a/ccan/opt/test/run-usage.c b/ccan/opt/test/run-usage.c new file mode 100644 index 0000000..2af2c7e --- /dev/null +++ b/ccan/opt/test/run-usage.c @@ -0,0 +1,112 @@ +#include +#include +#include +#include +#include +#include "utils.h" +#include +#include +#include +#include + +static char *my_cb(void *p) +{ + return NULL; +} + +static void reset_options(void) +{ + free(opt_table); + opt_table = NULL; + opt_count = opt_num_short = opt_num_short_arg = opt_num_long = 0; +} + +/* Test helpers. */ +int main(int argc, char *argv[]) +{ + char *output; + char *longname = strdup("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + char *shortname = strdup("shortname"); + + plan_tests(48); + opt_register_table(subtables, NULL); + opt_register_noarg("--kkk|-k", my_cb, NULL, "magic kkk option"); + opt_register_noarg("-?", opt_usage_and_exit, "...", + "This message"); + opt_register_arg("--longname", opt_set_charp, opt_show_charp, + &longname, "a really long option default"); + opt_register_arg("--shortname", opt_set_charp, opt_show_charp, + &shortname, "a short option default"); + output = opt_usage("my name", "ExTrA Args"); + diag("%s", output); + ok1(strstr(output, "Usage: my name")); + ok1(strstr(output, "--jjj|-j|--lll|-l ")); + ok1(strstr(output, "ExTrA Args")); + ok1(strstr(output, "-a ")); + ok1(strstr(output, " Description of a\n")); + ok1(strstr(output, "-b ")); + ok1(strstr(output, " Description of b (default: b)\n")); + ok1(strstr(output, "--ddd ")); + ok1(strstr(output, " Description of ddd\n")); + ok1(strstr(output, "--eee ")); + ok1(strstr(output, " (default: eee)\n")); + ok1(strstr(output, "long table options:\n")); + ok1(strstr(output, "--ggg|-g ")); + ok1(strstr(output, " Description of ggg\n")); + ok1(strstr(output, "-h|--hhh ")); + ok1(strstr(output, " Description of hhh\n")); + ok1(strstr(output, "--kkk|-k")); + ok1(strstr(output, "magic kkk option")); + /* This entry is hidden. */ + ok1(!strstr(output, "--mmm|-m")); + free(output); + + /* NULL should use string from registered options. */ + output = opt_usage("my name", NULL); + diag("%s", output); + ok1(strstr(output, "Usage: my name")); + ok1(strstr(output, "--jjj|-j|--lll|-l ")); + ok1(strstr(output, "...")); + ok1(strstr(output, "-a ")); + ok1(strstr(output, " Description of a\n")); + ok1(strstr(output, "-b ")); + ok1(strstr(output, " Description of b (default: b)\n")); + ok1(strstr(output, "--ddd ")); + ok1(strstr(output, " Description of ddd\n")); + ok1(strstr(output, "--eee ")); + ok1(strstr(output, " (default: eee)\n")); + ok1(strstr(output, "long table options:\n")); + ok1(strstr(output, "--ggg|-g ")); + ok1(strstr(output, " Description of ggg\n")); + ok1(strstr(output, "-h|--hhh ")); + ok1(strstr(output, " Description of hhh\n")); + ok1(strstr(output, "--kkk|-k")); + ok1(strstr(output, "magic kkk option")); + ok1(strstr(output, "--longname")); + ok1(strstr(output, "a really long option default")); + ok1(strstr(output, "(default: \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"...)")); + ok1(strstr(output, "--shortname")); + ok1(strstr(output, "a short option default")); + ok1(strstr(output, "(default: \"shortname\")")); + /* This entry is hidden. */ + ok1(!strstr(output, "--mmm|-m")); + free(output); + + reset_options(); + /* Empty table test. */ + output = opt_usage("nothing", NULL); + ok1(strstr(output, "Usage: nothing \n")); + free(output); + + /* No short args. */ + opt_register_noarg("--aaa", test_noarg, NULL, "AAAAll"); + output = opt_usage("onearg", NULL); + ok1(strstr(output, "Usage: onearg \n")); + ok1(strstr(output, "--aaa")); + ok1(strstr(output, "AAAAll")); + free(output); + + free(shortname); + free(longname); + return exit_status(); +} diff --git a/ccan/opt/test/run.c b/ccan/opt/test/run.c new file mode 100644 index 0000000..9a769ba --- /dev/null +++ b/ccan/opt/test/run.c @@ -0,0 +1,297 @@ +#include +#include +#include +#include +#include +#include +#include "utils.h" + +static void reset_options(void) +{ + free(opt_table); + opt_table = NULL; + opt_count = opt_num_short = opt_num_short_arg = opt_num_long = 0; + free(err_output); + err_output = NULL; +} + +int main(int argc, char *argv[]) +{ + const char *myname = argv[0]; + + plan_tests(215); + + /* Simple short arg.*/ + opt_register_noarg("-a", test_noarg, NULL, "All"); + ok1(parse_args(&argc, &argv, "-a", NULL)); + ok1(argc == 1); + ok1(argv[0] == myname); + ok1(argv[1] == NULL); + ok1(test_cb_called == 1); + + /* Simple long arg. */ + opt_register_noarg("--aaa", test_noarg, NULL, "AAAAll"); + ok1(parse_args(&argc, &argv, "--aaa", NULL)); + ok1(argc == 1); + ok1(argv[0] == myname); + ok1(argv[1] == NULL); + ok1(test_cb_called == 2); + + /* Both long and short args. */ + opt_register_noarg("--aaa|-a", test_noarg, NULL, "AAAAAAll"); + ok1(parse_args(&argc, &argv, "--aaa", "-a", NULL)); + ok1(argc == 1); + ok1(argv[0] == myname); + ok1(argv[1] == NULL); + ok1(test_cb_called == 4); + + /* Extra arguments preserved. */ + ok1(parse_args(&argc, &argv, "--aaa", "-a", "extra", "args", NULL)); + ok1(argc == 3); + ok1(argv[0] == myname); + ok1(strcmp(argv[1], "extra") == 0); + ok1(strcmp(argv[2], "args") == 0); + ok1(test_cb_called == 6); + + /* Malformed versions. */ + ok1(!parse_args(&argc, &argv, "--aaa=arg", NULL)); + ok1(strstr(err_output, ": --aaa: doesn't allow an argument")); + ok1(!parse_args(&argc, &argv, "--aa", NULL)); + ok1(strstr(err_output, ": --aa: unrecognized option")); + ok1(!parse_args(&argc, &argv, "--aaargh", NULL)); + ok1(strstr(err_output, ": --aaargh: unrecognized option")); + + /* Argument variants. */ + reset_options(); + test_cb_called = 0; + opt_register_arg("-a|--aaa", test_arg, NULL, "aaa", "AAAAAAll"); + ok1(parse_args(&argc, &argv, "--aaa", "aaa", NULL)); + ok1(argc == 1); + ok1(argv[0] == myname); + ok1(test_cb_called == 1); + + ok1(parse_args(&argc, &argv, "--aaa=aaa", NULL)); + ok1(argc == 1); + ok1(argv[0] == myname); + ok1(test_cb_called == 2); + + ok1(parse_args(&argc, &argv, "-a", "aaa", NULL)); + ok1(argc == 1); + ok1(argv[0] == myname); + ok1(test_cb_called == 3); + + /* Malformed versions. */ + ok1(!parse_args(&argc, &argv, "-a", NULL)); + ok1(strstr(err_output, ": -a: requires an argument")); + ok1(!parse_args(&argc, &argv, "--aaa", NULL)); + ok1(strstr(err_output, ": --aaa: requires an argument")); + ok1(!parse_args(&argc, &argv, "--aa", NULL)); + ok1(strstr(err_output, ": --aa: unrecognized option")); + ok1(!parse_args(&argc, &argv, "--aaargh", NULL)); + ok1(strstr(err_output, ": --aaargh: unrecognized option")); + + /* Now, tables. */ + /* Short table: */ + reset_options(); + test_cb_called = 0; + opt_register_table(short_table, NULL); + ok1(parse_args(&argc, &argv, "-a", NULL)); + ok1(argc == 1); + ok1(argv[0] == myname); + ok1(argv[1] == NULL); + ok1(test_cb_called == 1); + /* This one needs an arg. */ + ok1(parse_args(&argc, &argv, "-b", NULL) == false); + ok1(test_cb_called == 1); + ok1(parse_args(&argc, &argv, "-b", "b", NULL)); + ok1(argc == 1); + ok1(argv[0] == myname); + ok1(argv[1] == NULL); + ok1(test_cb_called == 2); + + /* Long table: */ + reset_options(); + test_cb_called = 0; + opt_register_table(long_table, NULL); + ok1(parse_args(&argc, &argv, "--ddd", NULL)); + ok1(argc == 1); + ok1(argv[0] == myname); + ok1(argv[1] == NULL); + ok1(test_cb_called == 1); + /* This one needs an arg. */ + ok1(parse_args(&argc, &argv, "--eee", NULL) == false); + ok1(test_cb_called == 1); + ok1(parse_args(&argc, &argv, "--eee", "eee", NULL)); + ok1(argc == 1); + ok1(argv[0] == myname); + ok1(argv[1] == NULL); + ok1(test_cb_called == 2); + + /* Short and long, both. */ + reset_options(); + test_cb_called = 0; + opt_register_table(long_and_short_table, NULL); + ok1(parse_args(&argc, &argv, "-g", NULL)); + ok1(argc == 1); + ok1(argv[0] == myname); + ok1(argv[1] == NULL); + ok1(test_cb_called == 1); + ok1(parse_args(&argc, &argv, "--ggg", NULL)); + ok1(argc == 1); + ok1(argv[0] == myname); + ok1(argv[1] == NULL); + ok1(test_cb_called == 2); + /* This one needs an arg. */ + ok1(parse_args(&argc, &argv, "-h", NULL) == false); + ok1(test_cb_called == 2); + ok1(parse_args(&argc, &argv, "-h", "hhh", NULL)); + ok1(argc == 1); + ok1(argv[0] == myname); + ok1(argv[1] == NULL); + ok1(test_cb_called == 3); + ok1(parse_args(&argc, &argv, "--hhh", NULL) == false); + ok1(test_cb_called == 3); + ok1(parse_args(&argc, &argv, "--hhh", "hhh", NULL)); + ok1(argc == 1); + ok1(argv[0] == myname); + ok1(argv[1] == NULL); + ok1(test_cb_called == 4); + + /* Those will all work as tables. */ + test_cb_called = 0; + reset_options(); + opt_register_table(subtables, NULL); + ok1(parse_args(&argc, &argv, "-a", NULL)); + ok1(argc == 1); + ok1(argv[0] == myname); + ok1(argv[1] == NULL); + ok1(test_cb_called == 1); + /* This one needs an arg. */ + ok1(parse_args(&argc, &argv, "-b", NULL) == false); + ok1(test_cb_called == 1); + ok1(parse_args(&argc, &argv, "-b", "b", NULL)); + ok1(argc == 1); + ok1(argv[0] == myname); + ok1(argv[1] == NULL); + ok1(test_cb_called == 2); + + ok1(parse_args(&argc, &argv, "--ddd", NULL)); + ok1(argc == 1); + ok1(argv[0] == myname); + ok1(argv[1] == NULL); + ok1(test_cb_called == 3); + /* This one needs an arg. */ + ok1(parse_args(&argc, &argv, "--eee", NULL) == false); + ok1(test_cb_called == 3); + ok1(parse_args(&argc, &argv, "--eee", "eee", NULL)); + ok1(argc == 1); + ok1(argv[0] == myname); + ok1(argv[1] == NULL); + ok1(test_cb_called == 4); + + /* Short and long, both. */ + ok1(parse_args(&argc, &argv, "-g", NULL)); + ok1(argc == 1); + ok1(argv[0] == myname); + ok1(argv[1] == NULL); + ok1(test_cb_called == 5); + ok1(parse_args(&argc, &argv, "--ggg", NULL)); + ok1(argc == 1); + ok1(argv[0] == myname); + ok1(argv[1] == NULL); + ok1(test_cb_called == 6); + /* This one needs an arg. */ + ok1(parse_args(&argc, &argv, "-h", NULL) == false); + ok1(test_cb_called == 6); + ok1(parse_args(&argc, &argv, "-h", "hhh", NULL)); + ok1(argc == 1); + ok1(argv[0] == myname); + ok1(argv[1] == NULL); + ok1(test_cb_called == 7); + ok1(parse_args(&argc, &argv, "--hhh", NULL) == false); + ok1(test_cb_called == 7); + ok1(parse_args(&argc, &argv, "--hhh", "hhh", NULL)); + ok1(argc == 1); + ok1(argv[0] == myname); + ok1(argv[1] == NULL); + ok1(test_cb_called == 8); + + /* Now the tricky one: -? must not be confused with an unknown option */ + test_cb_called = 0; + reset_options(); + + /* glibc's getopt does not handle ? with arguments. */ + opt_register_noarg("-?", test_noarg, NULL, "Help"); + ok1(parse_args(&argc, &argv, "-?", NULL)); + ok1(test_cb_called == 1); + ok1(parse_args(&argc, &argv, "-a", NULL) == false); + ok1(test_cb_called == 1); + ok1(strstr(err_output, ": -a: unrecognized option")); + ok1(parse_args(&argc, &argv, "--aaaa", NULL) == false); + ok1(test_cb_called == 1); + ok1(strstr(err_output, ": --aaaa: unrecognized option")); + + test_cb_called = 0; + reset_options(); + + /* Corner cases involving short arg parsing weirdness. */ + opt_register_noarg("-a|--aaa", test_noarg, NULL, "a"); + opt_register_arg("-b|--bbb", test_arg, NULL, "bbb", "b"); + opt_register_arg("-c|--ccc", test_arg, NULL, "aaa", "c"); + /* -aa == -a -a */ + ok1(parse_args(&argc, &argv, "-aa", NULL)); + ok1(test_cb_called == 2); + ok1(parse_args(&argc, &argv, "-aab", NULL) == false); + ok1(test_cb_called == 4); + ok1(strstr(err_output, ": -b: requires an argument")); + ok1(parse_args(&argc, &argv, "-bbbb", NULL)); + ok1(test_cb_called == 5); + ok1(parse_args(&argc, &argv, "-aabbbb", NULL)); + ok1(test_cb_called == 8); + ok1(parse_args(&argc, &argv, "-aabbbb", "-b", "bbb", NULL)); + ok1(test_cb_called == 12); + ok1(parse_args(&argc, &argv, "-aabbbb", "--bbb", "bbb", NULL)); + ok1(test_cb_called == 16); + ok1(parse_args(&argc, &argv, "-aabbbb", "--bbb=bbb", NULL)); + ok1(test_cb_called == 20); + ok1(parse_args(&argc, &argv, "-aacaaa", NULL)); + ok1(test_cb_called == 23); + ok1(parse_args(&argc, &argv, "-aacaaa", "-a", NULL)); + ok1(test_cb_called == 27); + ok1(parse_args(&argc, &argv, "-aacaaa", "--bbb", "bbb", "-aacaaa", + NULL)); + ok1(test_cb_called == 34); + + test_cb_called = 0; + reset_options(); + + /* -- and POSIXLY_CORRECT */ + opt_register_noarg("-a|--aaa", test_noarg, NULL, "a"); + ok1(parse_args(&argc, &argv, "-a", "--", "-a", NULL)); + ok1(test_cb_called == 1); + ok1(argc == 2); + ok1(strcmp(argv[1], "-a") == 0); + ok1(!argv[2]); + + unsetenv("POSIXLY_CORRECT"); + ok1(parse_args(&argc, &argv, "-a", "somearg", "-a", "--", "-a", NULL)); + ok1(test_cb_called == 3); + ok1(argc == 3); + ok1(strcmp(argv[1], "somearg") == 0); + ok1(strcmp(argv[2], "-a") == 0); + ok1(!argv[3]); + + setenv("POSIXLY_CORRECT", "1", 1); + ok1(parse_args(&argc, &argv, "-a", "somearg", "-a", "--", "-a", NULL)); + ok1(test_cb_called == 4); + ok1(argc == 5); + ok1(strcmp(argv[1], "somearg") == 0); + ok1(strcmp(argv[2], "-a") == 0); + ok1(strcmp(argv[3], "--") == 0); + ok1(strcmp(argv[4], "-a") == 0); + ok1(!argv[5]); + + /* parse_args allocates argv */ + free(argv); + return exit_status(); +} diff --git a/ccan/opt/test/utils.c b/ccan/opt/test/utils.c new file mode 100644 index 0000000..9544fa7 --- /dev/null +++ b/ccan/opt/test/utils.c @@ -0,0 +1,110 @@ +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include "utils.h" + +unsigned int test_cb_called; +char *test_noarg(void *arg) +{ + test_cb_called++; + return NULL; +} + +char *test_arg(const char *optarg, const char *arg) +{ + test_cb_called++; + ok1(strcmp(optarg, arg) == 0); + return NULL; +} + +void show_arg(char buf[OPT_SHOW_LEN], const char *arg) +{ + strncpy(buf, arg, OPT_SHOW_LEN); +} + +char *err_output = NULL; + +void save_err_output(const char *fmt, ...) +{ + va_list ap; + char *p; + + va_start(ap, fmt); + /* Check return, for fascist gcc */ + if (vasprintf(&p, fmt, ap) == -1) + p = NULL; + va_end(ap); + + if (err_output) { + err_output = realloc(err_output, + strlen(err_output) + strlen(p) + 1); + strcat(err_output, p); + free(p); + } else + err_output = p; +} + +static bool allocated = false; + +bool parse_args(int *argc, char ***argv, ...) +{ + char **a; + va_list ap; + + va_start(ap, argv); + *argc = 1; + a = malloc(sizeof(*a) * (*argc + 1)); + a[0] = (*argv)[0]; + while ((a[*argc] = va_arg(ap, char *)) != NULL) { + (*argc)++; + a = realloc(a, sizeof(*a) * (*argc + 1)); + } + + if (allocated) + free(*argv); + + *argv = a; + allocated = true; + /* Re-set before parsing. */ + optind = 0; + + return opt_parse(argc, *argv, save_err_output); +} + +struct opt_table short_table[] = { + /* Short opts, different args. */ + OPT_WITHOUT_ARG("-a", test_noarg, "a", "Description of a"), + OPT_WITH_ARG("-b", test_arg, show_arg, "b", "Description of b"), + OPT_ENDTABLE +}; + +struct opt_table long_table[] = { + /* Long opts, different args. */ + OPT_WITHOUT_ARG("--ddd", test_noarg, "ddd", "Description of ddd"), + OPT_WITH_ARG("--eee ", test_arg, show_arg, "eee", ""), + OPT_ENDTABLE +}; + +struct opt_table long_and_short_table[] = { + /* Short and long, different args. */ + OPT_WITHOUT_ARG("--ggg|-g", test_noarg, "ggg", "Description of ggg"), + OPT_WITH_ARG("-h|--hhh", test_arg, NULL, "hhh", "Description of hhh"), + OPT_ENDTABLE +}; + +/* Sub-table test. */ +struct opt_table subtables[] = { + /* Two short, and two long long, no description */ + OPT_WITH_ARG("--jjj|-j|--lll|-l", test_arg, show_arg, "jjj", ""), + /* Hidden option */ + OPT_WITH_ARG("--mmm|-m", test_arg, show_arg, "mmm", opt_hidden), + OPT_SUBTABLE(short_table, NULL), + OPT_SUBTABLE(long_table, "long table options"), + OPT_SUBTABLE(long_and_short_table, NULL), + OPT_ENDTABLE +}; diff --git a/ccan/opt/test/utils.h b/ccan/opt/test/utils.h new file mode 100644 index 0000000..f7c1896 --- /dev/null +++ b/ccan/opt/test/utils.h @@ -0,0 +1,19 @@ +#ifndef CCAN_OPT_TEST_UTILS_H +#define CCAN_OPT_TEST_UTILS_H +#include +#include + +bool parse_args(int *argc, char ***argv, ...); +extern char *err_output; +void save_err_output(const char *fmt, ...); + +extern unsigned int test_cb_called; +char *test_noarg(void *arg); +char *test_arg(const char *optarg, const char *arg); +void show_arg(char buf[OPT_SHOW_LEN], const char *arg); + +extern struct opt_table short_table[]; +extern struct opt_table long_table[]; +extern struct opt_table long_and_short_table[]; +extern struct opt_table subtables[]; +#endif /* CCAN_OPT_TEST_UTILS_H */ diff --git a/ccan/opt/usage.c b/ccan/opt/usage.c new file mode 100644 index 0000000..4d784bc --- /dev/null +++ b/ccan/opt/usage.c @@ -0,0 +1,111 @@ +#include +#include +#include +#include +#include +#include "private.h" + +/* We only use this for pointer comparisons. */ +const char opt_hidden[1]; + +static unsigned write_short_options(char *str) +{ + unsigned int i, num = 0; + const char *p; + + for (p = first_sopt(&i); p; p = next_sopt(p, &i)) { + if (opt_table[i].desc != opt_hidden) + str[num++] = *p; + } + return num; +} + +#define OPT_SPACE_PAD " " + +/* FIXME: Get all purdy. */ +char *opt_usage(const char *argv0, const char *extra) +{ + unsigned int i, num, len; + char *ret, *p; + + if (!extra) { + extra = ""; + for (i = 0; i < opt_count; i++) { + if (opt_table[i].cb == (void *)opt_usage_and_exit + && opt_table[i].u.carg) { + extra = opt_table[i].u.carg; + break; + } + } + } + + /* An overestimate of our length. */ + len = strlen("Usage: %s ") + strlen(argv0) + + strlen("[-%.*s]") + opt_num_short + 1 + + strlen(" ") + strlen(extra) + + strlen("\n"); + + for (i = 0; i < opt_count; i++) { + if (opt_table[i].type == OPT_SUBTABLE) { + len += strlen("\n") + strlen(opt_table[i].desc) + + strlen(":\n"); + } else if (opt_table[i].desc != opt_hidden) { + len += strlen(opt_table[i].names) + strlen(" "); + len += strlen(OPT_SPACE_PAD) + + strlen(opt_table[i].desc) + 1; + if (opt_table[i].show) { + len += strlen("(default: %s)") + + OPT_SHOW_LEN + sizeof("..."); + } + len += strlen("\n"); + } + } + + p = ret = malloc(len); + if (!ret) + return NULL; + + p += sprintf(p, "Usage: %s", argv0); + p += sprintf(p, " [-"); + num = write_short_options(p); + if (num) { + p += num; + p += sprintf(p, "]"); + } else { + /* Remove start of single-entry options */ + p -= 3; + } + if (extra) + p += sprintf(p, " %s", extra); + p += sprintf(p, "\n"); + + for (i = 0; i < opt_count; i++) { + if (opt_table[i].desc == opt_hidden) + continue; + if (opt_table[i].type == OPT_SUBTABLE) { + p += sprintf(p, "%s:\n", opt_table[i].desc); + continue; + } + len = sprintf(p, "%s", opt_table[i].names); + if (opt_table[i].type == OPT_HASARG + && !strchr(opt_table[i].names, ' ') + && !strchr(opt_table[i].names, '=')) + len += sprintf(p + len, " "); + len += sprintf(p + len, "%.*s", + len < strlen(OPT_SPACE_PAD) + ? (unsigned)strlen(OPT_SPACE_PAD) - len : 1, + OPT_SPACE_PAD); + + len += sprintf(p + len, "%s", opt_table[i].desc); + if (opt_table[i].show) { + char buf[OPT_SHOW_LEN + sizeof("...")]; + strcpy(buf + OPT_SHOW_LEN, "..."); + opt_table[i].show(buf, opt_table[i].u.arg); + len += sprintf(p + len, " (default: %s)", buf); + } + p += len; + p += sprintf(p, "\n"); + } + *p = '\0'; + return ret; +} diff --git a/ccan/typesafe_cb/LICENSE b/ccan/typesafe_cb/LICENSE new file mode 100644 index 0000000..2d2d780 --- /dev/null +++ b/ccan/typesafe_cb/LICENSE @@ -0,0 +1,510 @@ + + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations +below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it +becomes a de-facto standard. To achieve this, non-free programs must +be allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control +compilation and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at least + three years, to give the same user the materials specified in + Subsection 6a, above, for a charge no more than the cost of + performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply, and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License +may add an explicit geographical distribution limitation excluding those +countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms +of the ordinary General Public License). + + To apply these terms, attach the following notices to the library. +It is safest to attach them to the start of each source file to most +effectively convey the exclusion of warranty; and each file should +have at least the "copyright" line and a pointer to where the full +notice is found. + + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or +your school, if any, to sign a "copyright disclaimer" for the library, +if necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James + Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/ccan/typesafe_cb/_info b/ccan/typesafe_cb/_info new file mode 100644 index 0000000..2fe4fec --- /dev/null +++ b/ccan/typesafe_cb/_info @@ -0,0 +1,151 @@ +#include +#include +#include "config.h" + +/** + * typesafe_cb - macros for safe callbacks. + * + * The basis of the typesafe_cb header is typesafe_cb_cast(): a + * conditional cast macro. If an expression exactly matches a given + * type, it is cast to the target type, otherwise it is left alone. + * + * This allows us to create functions which take a small number of + * specific types, rather than being forced to use a void *. In + * particular, it is useful for creating typesafe callbacks as the + * helpers typesafe_cb(), typesafe_cb_preargs() and + * typesafe_cb_postargs() demonstrate. + * + * The standard way of passing arguments to callback functions in C is + * to use a void pointer, which the callback then casts back to the + * expected type. This unfortunately subverts the type checking the + * compiler would perform if it were a direct call. Here's an example: + * + * static void my_callback(void *_obj) + * { + * struct obj *obj = _obj; + * ... + * } + * ... + * register_callback(my_callback, &my_obj); + * + * If we wanted to use the natural type for my_callback (ie. "void + * my_callback(struct obj *obj)"), we could make register_callback() + * take a void * as its first argument, but this would subvert all + * type checking. We really want register_callback() to accept only + * the exactly correct function type to match the argument, or a + * function which takes a void *. + * + * This is where typesafe_cb() comes in: it uses typesafe_cb_cast() to + * cast the callback function if it matches the argument type: + * + * void _register_callback(void (*cb)(void *arg), void *arg); + * #define register_callback(cb, arg) \ + * _register_callback(typesafe_cb(void, void *, (cb), (arg)), \ + * (arg)) + * + * On compilers which don't support the extensions required + * typesafe_cb_cast() and friend become an unconditional cast, so your + * code will compile but you won't get type checking. + * + * Example: + * #include + * #include + * #include + * + * // Generic callback infrastructure. + * struct callback { + * struct callback *next; + * int value; + * int (*callback)(int value, void *arg); + * void *arg; + * }; + * static struct callback *callbacks; + * + * static void _register_callback(int value, int (*cb)(int, void *), + * void *arg) + * { + * struct callback *new = malloc(sizeof(*new)); + * new->next = callbacks; + * new->value = value; + * new->callback = cb; + * new->arg = arg; + * callbacks = new; + * } + * #define register_callback(value, cb, arg) \ + * _register_callback(value, \ + * typesafe_cb_preargs(int, void *, \ + * (cb), (arg), int),\ + * (arg)) + * + * static struct callback *find_callback(int value) + * { + * struct callback *i; + * + * for (i = callbacks; i; i = i->next) + * if (i->value == value) + * return i; + * return NULL; + * } + * + * // Define several silly callbacks. Note they don't use void *! + * #define DEF_CALLBACK(name, op) \ + * static int name(int val, int *arg) \ + * { \ + * printf("%s", #op); \ + * return val op *arg; \ + * } + * DEF_CALLBACK(multiply, *); + * DEF_CALLBACK(add, +); + * DEF_CALLBACK(divide, /); + * DEF_CALLBACK(sub, -); + * DEF_CALLBACK(or, |); + * DEF_CALLBACK(and, &); + * DEF_CALLBACK(xor, ^); + * DEF_CALLBACK(assign, =); + * + * // Silly game to find the longest chain of values. + * int main(int argc, char *argv[]) + * { + * int i, run = 1, num = argv[1] ? atoi(argv[1]) : 0; + * + * for (i = 1; i < 1024;) { + * // Since run is an int, compiler checks "add" does too. + * register_callback(i++, add, &run); + * register_callback(i++, divide, &run); + * register_callback(i++, sub, &run); + * register_callback(i++, multiply, &run); + * register_callback(i++, or, &run); + * register_callback(i++, and, &run); + * register_callback(i++, xor, &run); + * register_callback(i++, assign, &run); + * } + * + * printf("%i ", num); + * while (run < 56) { + * struct callback *cb = find_callback(num % i); + * if (!cb) { + * printf("-> STOP\n"); + * return 1; + * } + * num = cb->callback(num, cb->arg); + * printf("->%i ", num); + * run++; + * } + * printf("-> Winner!\n"); + * return 0; + * } + * + * License: LGPL (2 or any later version) + * Author: Rusty Russell + */ +int main(int argc, char *argv[]) +{ + if (argc != 2) + return 1; + + if (strcmp(argv[1], "depends") == 0) { + return 0; + } + + return 1; +} diff --git a/ccan/typesafe_cb/test/compile_fail-cast_if_type-promotable.c b/ccan/typesafe_cb/test/compile_fail-cast_if_type-promotable.c new file mode 100644 index 0000000..11d42f4 --- /dev/null +++ b/ccan/typesafe_cb/test/compile_fail-cast_if_type-promotable.c @@ -0,0 +1,23 @@ +#include +#include + +static void _set_some_value(void *val) +{ +} + +#define set_some_value(expr) \ + _set_some_value(typesafe_cb_cast(void *, long, (expr))) + +int main(int argc, char *argv[]) +{ +#ifdef FAIL + bool x = 0; +#if !HAVE_TYPEOF||!HAVE_BUILTIN_CHOOSE_EXPR||!HAVE_BUILTIN_TYPES_COMPATIBLE_P +#error "Unfortunately we don't fail if typesafe_cb_cast is a noop." +#endif +#else + long x = 0; +#endif + set_some_value(x); + return 0; +} diff --git a/ccan/typesafe_cb/test/compile_fail-typesafe_cb-int.c b/ccan/typesafe_cb/test/compile_fail-typesafe_cb-int.c new file mode 100644 index 0000000..c403336 --- /dev/null +++ b/ccan/typesafe_cb/test/compile_fail-typesafe_cb-int.c @@ -0,0 +1,27 @@ +#include +#include + +void _callback(void (*fn)(void *arg), void *arg); +void _callback(void (*fn)(void *arg), void *arg) +{ + fn(arg); +} + +/* Callback is set up to warn if arg isn't a pointer (since it won't + * pass cleanly to _callback's second arg. */ +#define callback(fn, arg) \ + _callback(typesafe_cb(void, (fn), (arg)), (arg)) + +void my_callback(int something); +void my_callback(int something) +{ +} + +int main(int argc, char *argv[]) +{ +#ifdef FAIL + /* This fails due to arg, not due to cast. */ + callback(my_callback, 100); +#endif + return 0; +} diff --git a/ccan/typesafe_cb/test/compile_fail-typesafe_cb.c b/ccan/typesafe_cb/test/compile_fail-typesafe_cb.c new file mode 100644 index 0000000..81e36d7 --- /dev/null +++ b/ccan/typesafe_cb/test/compile_fail-typesafe_cb.c @@ -0,0 +1,34 @@ +#include +#include + +static void _register_callback(void (*cb)(void *arg), void *arg) +{ +} + +#define register_callback(cb, arg) \ + _register_callback(typesafe_cb(void, void *, (cb), (arg)), (arg)) + +static void my_callback(char *p) +{ +} + +int main(int argc, char *argv[]) +{ + char str[] = "hello world"; +#ifdef FAIL + int *p; +#if !HAVE_TYPEOF||!HAVE_BUILTIN_CHOOSE_EXPR||!HAVE_BUILTIN_TYPES_COMPATIBLE_P +#error "Unfortunately we don't fail if typesafe_cb_cast is a noop." +#endif +#else + char *p; +#endif + p = NULL; + + /* This should work always. */ + register_callback(my_callback, str); + + /* This will fail with FAIL defined */ + register_callback(my_callback, p); + return 0; +} diff --git a/ccan/typesafe_cb/test/compile_fail-typesafe_cb_cast-multi.c b/ccan/typesafe_cb/test/compile_fail-typesafe_cb_cast-multi.c new file mode 100644 index 0000000..62b5f91 --- /dev/null +++ b/ccan/typesafe_cb/test/compile_fail-typesafe_cb_cast-multi.c @@ -0,0 +1,43 @@ +#include +#include + +struct foo { + int x; +}; + +struct bar { + int x; +}; + +struct baz { + int x; +}; + +struct any { + int x; +}; + +struct other { + int x; +}; + +static void take_any(struct any *any) +{ +} + +int main(int argc, char *argv[]) +{ +#ifdef FAIL + struct other +#if !HAVE_TYPEOF||!HAVE_BUILTIN_CHOOSE_EXPR||!HAVE_BUILTIN_TYPES_COMPATIBLE_P +#error "Unfortunately we don't fail if typesafe_cb_cast is a noop." +#endif +#else + struct foo +#endif + *arg = NULL; + take_any(typesafe_cb_cast3(struct any *, + struct foo *, struct bar *, struct baz *, + arg)); + return 0; +} diff --git a/ccan/typesafe_cb/test/compile_fail-typesafe_cb_cast.c b/ccan/typesafe_cb/test/compile_fail-typesafe_cb_cast.c new file mode 100644 index 0000000..d2e6f2a --- /dev/null +++ b/ccan/typesafe_cb/test/compile_fail-typesafe_cb_cast.c @@ -0,0 +1,25 @@ +#include + +void _set_some_value(void *val); + +void _set_some_value(void *val) +{ +} + +#define set_some_value(expr) \ + _set_some_value(typesafe_cb_cast(void *, unsigned long, (expr))) + +int main(int argc, char *argv[]) +{ +#ifdef FAIL + int x = 0; + set_some_value(x); +#if !HAVE_TYPEOF||!HAVE_BUILTIN_CHOOSE_EXPR||!HAVE_BUILTIN_TYPES_COMPATIBLE_P +#error "Unfortunately we don't fail if typesafe_cb_cast is a noop." +#endif +#else + void *p = 0; + set_some_value(p); +#endif + return 0; +} diff --git a/ccan/typesafe_cb/test/compile_fail-typesafe_cb_postargs.c b/ccan/typesafe_cb/test/compile_fail-typesafe_cb_postargs.c new file mode 100644 index 0000000..7d35308 --- /dev/null +++ b/ccan/typesafe_cb/test/compile_fail-typesafe_cb_postargs.c @@ -0,0 +1,27 @@ +#include +#include + +static void _register_callback(void (*cb)(void *arg, int x), void *arg) +{ +} +#define register_callback(cb, arg) \ + _register_callback(typesafe_cb_postargs(void, void *, (cb), (arg), int), (arg)) + +static void my_callback(char *p, int x) +{ +} + +int main(int argc, char *argv[]) +{ +#ifdef FAIL + int *p; +#if !HAVE_TYPEOF||!HAVE_BUILTIN_CHOOSE_EXPR||!HAVE_BUILTIN_TYPES_COMPATIBLE_P +#error "Unfortunately we don't fail if typesafe_cb_cast is a noop." +#endif +#else + char *p; +#endif + p = NULL; + register_callback(my_callback, p); + return 0; +} diff --git a/ccan/typesafe_cb/test/compile_fail-typesafe_cb_preargs.c b/ccan/typesafe_cb/test/compile_fail-typesafe_cb_preargs.c new file mode 100644 index 0000000..bd55c67 --- /dev/null +++ b/ccan/typesafe_cb/test/compile_fail-typesafe_cb_preargs.c @@ -0,0 +1,28 @@ +#include +#include + +static void _register_callback(void (*cb)(int x, void *arg), void *arg) +{ +} + +#define register_callback(cb, arg) \ + _register_callback(typesafe_cb_preargs(void, void *, (cb), (arg), int), (arg)) + +static void my_callback(int x, char *p) +{ +} + +int main(int argc, char *argv[]) +{ +#ifdef FAIL + int *p; +#if !HAVE_TYPEOF||!HAVE_BUILTIN_CHOOSE_EXPR||!HAVE_BUILTIN_TYPES_COMPATIBLE_P +#error "Unfortunately we don't fail if typesafe_cb_cast is a noop." +#endif +#else + char *p; +#endif + p = NULL; + register_callback(my_callback, p); + return 0; +} diff --git a/ccan/typesafe_cb/test/compile_ok-typesafe_cb-NULL.c b/ccan/typesafe_cb/test/compile_ok-typesafe_cb-NULL.c new file mode 100644 index 0000000..265de8b --- /dev/null +++ b/ccan/typesafe_cb/test/compile_ok-typesafe_cb-NULL.c @@ -0,0 +1,17 @@ +#include +#include + +/* NULL args for callback function should be OK for normal and _def. */ + +static void _register_callback(void (*cb)(const void *arg), const void *arg) +{ +} + +#define register_callback(cb, arg) \ + _register_callback(typesafe_cb(void, const void *, (cb), (arg)), (arg)) + +int main(int argc, char *argv[]) +{ + register_callback(NULL, "hello world"); + return 0; +} diff --git a/ccan/typesafe_cb/test/compile_ok-typesafe_cb-undefined.c b/ccan/typesafe_cb/test/compile_ok-typesafe_cb-undefined.c new file mode 100644 index 0000000..aa50bad --- /dev/null +++ b/ccan/typesafe_cb/test/compile_ok-typesafe_cb-undefined.c @@ -0,0 +1,49 @@ +#include +#include + +/* const args in callbacks should be OK. */ + +static void _register_callback(void (*cb)(void *arg), void *arg) +{ +} + +#define register_callback(cb, arg) \ + _register_callback(typesafe_cb(void, void *, (cb), (arg)), (arg)) + +static void _register_callback_pre(void (*cb)(int x, void *arg), void *arg) +{ +} + +#define register_callback_pre(cb, arg) \ + _register_callback_pre(typesafe_cb_preargs(void, void *, (cb), (arg), int), (arg)) + +static void _register_callback_post(void (*cb)(void *arg, int x), void *arg) +{ +} + +#define register_callback_post(cb, arg) \ + _register_callback_post(typesafe_cb_postargs(void, void *, (cb), (arg), int), (arg)) + +struct undefined; + +static void my_callback(struct undefined *undef) +{ +} + +static void my_callback_pre(int x, struct undefined *undef) +{ +} + +static void my_callback_post(struct undefined *undef, int x) +{ +} + +int main(int argc, char *argv[]) +{ + struct undefined *handle = NULL; + + register_callback(my_callback, handle); + register_callback_pre(my_callback_pre, handle); + register_callback_post(my_callback_post, handle); + return 0; +} diff --git a/ccan/typesafe_cb/test/compile_ok-typesafe_cb-vars.c b/ccan/typesafe_cb/test/compile_ok-typesafe_cb-vars.c new file mode 100644 index 0000000..f6a2bfe --- /dev/null +++ b/ccan/typesafe_cb/test/compile_ok-typesafe_cb-vars.c @@ -0,0 +1,52 @@ +#include +#include + +/* const args in callbacks should be OK. */ + +static void _register_callback(void (*cb)(void *arg), void *arg) +{ +} + +#define register_callback(cb, arg) \ + _register_callback(typesafe_cb(void, void *, (cb), (arg)), (arg)) + +static void _register_callback_pre(void (*cb)(int x, void *arg), void *arg) +{ +} + +#define register_callback_pre(cb, arg) \ + _register_callback_pre(typesafe_cb_preargs(void, void *, (cb), (arg), int), (arg)) + +static void _register_callback_post(void (*cb)(void *arg, int x), void *arg) +{ +} + +#define register_callback_post(cb, arg) \ + _register_callback_post(typesafe_cb_postargs(void, void *, (cb), (arg), int), (arg)) + +struct undefined; + +static void my_callback(struct undefined *undef) +{ +} + +static void my_callback_pre(int x, struct undefined *undef) +{ +} + +static void my_callback_post(struct undefined *undef, int x) +{ +} + +int main(int argc, char *argv[]) +{ + struct undefined *handle = NULL; + void (*cb)(struct undefined *undef) = my_callback; + void (*pre)(int x, struct undefined *undef) = my_callback_pre; + void (*post)(struct undefined *undef, int x) = my_callback_post; + + register_callback(cb, handle); + register_callback_pre(pre, handle); + register_callback_post(post, handle); + return 0; +} diff --git a/ccan/typesafe_cb/test/compile_ok-typesafe_cb_cast.c b/ccan/typesafe_cb/test/compile_ok-typesafe_cb_cast.c new file mode 100644 index 0000000..4bb3b8b --- /dev/null +++ b/ccan/typesafe_cb/test/compile_ok-typesafe_cb_cast.c @@ -0,0 +1,41 @@ +#include +#include + +struct foo { + int x; +}; + +struct bar { + int x; +}; + +struct baz { + int x; +}; + +struct any { + int x; +}; + +static void take_any(struct any *any) +{ +} + +int main(int argc, char *argv[]) +{ + /* Otherwise we get unused warnings for these. */ + struct foo *foo = NULL; + struct bar *bar = NULL; + struct baz *baz = NULL; + + take_any(typesafe_cb_cast3(struct any *, + struct foo *, struct bar *, struct baz *, + foo)); + take_any(typesafe_cb_cast3(struct any *, + struct foo *, struct bar *, struct baz *, + bar)); + take_any(typesafe_cb_cast3(struct any *, + struct foo *, struct bar *, struct baz *, + baz)); + return 0; +} diff --git a/ccan/typesafe_cb/test/run.c b/ccan/typesafe_cb/test/run.c new file mode 100644 index 0000000..79863db --- /dev/null +++ b/ccan/typesafe_cb/test/run.c @@ -0,0 +1,109 @@ +#include +#include +#include +#include + +static char dummy = 0; + +/* The example usage. */ +static void _set_some_value(void *val) +{ + ok1(val == &dummy); +} + +#define set_some_value(expr) \ + _set_some_value(typesafe_cb_cast(void *, unsigned long, (expr))) + +static void _callback_onearg(void (*fn)(void *arg), void *arg) +{ + fn(arg); +} + +static void _callback_preargs(void (*fn)(int a, int b, void *arg), void *arg) +{ + fn(1, 2, arg); +} + +static void _callback_postargs(void (*fn)(void *arg, int a, int b), void *arg) +{ + fn(arg, 1, 2); +} + +#define callback_onearg(cb, arg) \ + _callback_onearg(typesafe_cb(void, void *, (cb), (arg)), (arg)) + +#define callback_preargs(cb, arg) \ + _callback_preargs(typesafe_cb_preargs(void, void *, (cb), (arg), int, int), (arg)) + +#define callback_postargs(cb, arg) \ + _callback_postargs(typesafe_cb_postargs(void, void *, (cb), (arg), int, int), (arg)) + +static void my_callback_onearg(char *p) +{ + ok1(strcmp(p, "hello world") == 0); +} + +static void my_callback_preargs(int a, int b, char *p) +{ + ok1(a == 1); + ok1(b == 2); + ok1(strcmp(p, "hello world") == 0); +} + +static void my_callback_postargs(char *p, int a, int b) +{ + ok1(a == 1); + ok1(b == 2); + ok1(strcmp(p, "hello world") == 0); +} + +/* This is simply a compile test; we promised typesafe_cb_cast can be in a + * static initializer. */ +struct callback_onearg +{ + void (*fn)(void *arg); + const void *arg; +}; + +struct callback_onearg cb_onearg += { typesafe_cb(void, void *, my_callback_onearg, (char *)(intptr_t)"hello world"), + "hello world" }; + +struct callback_preargs +{ + void (*fn)(int a, int b, void *arg); + const void *arg; +}; + +struct callback_preargs cb_preargs += { typesafe_cb_preargs(void, void *, my_callback_preargs, + (char *)(intptr_t)"hi", int, int), "hi" }; + +struct callback_postargs +{ + void (*fn)(void *arg, int a, int b); + const void *arg; +}; + +struct callback_postargs cb_postargs += { typesafe_cb_postargs(void, void *, my_callback_postargs, + (char *)(intptr_t)"hi", int, int), "hi" }; + +int main(int argc, char *argv[]) +{ + void *p = &dummy; + unsigned long l = (unsigned long)p; + char str[] = "hello world"; + + plan_tests(2 + 1 + 3 + 3); + set_some_value(p); + set_some_value(l); + + callback_onearg(my_callback_onearg, str); + + callback_preargs(my_callback_preargs, str); + + callback_postargs(my_callback_postargs, str); + + return exit_status(); +} diff --git a/ccan/typesafe_cb/typesafe_cb.h b/ccan/typesafe_cb/typesafe_cb.h new file mode 100644 index 0000000..40cfa39 --- /dev/null +++ b/ccan/typesafe_cb/typesafe_cb.h @@ -0,0 +1,133 @@ +#ifndef CCAN_TYPESAFE_CB_H +#define CCAN_TYPESAFE_CB_H +#include "config.h" + +#if HAVE_TYPEOF && HAVE_BUILTIN_CHOOSE_EXPR && HAVE_BUILTIN_TYPES_COMPATIBLE_P +/** + * typesafe_cb_cast - only cast an expression if it matches a given type + * @desttype: the type to cast to + * @oktype: the type we allow + * @expr: the expression to cast + * + * This macro is used to create functions which allow multiple types. + * The result of this macro is used somewhere that a @desttype type is + * expected: if @expr is exactly of type @oktype, then it will be + * cast to @desttype type, otherwise left alone. + * + * This macro can be used in static initializers. + * + * This is merely useful for warnings: if the compiler does not + * support the primitives required for typesafe_cb_cast(), it becomes an + * unconditional cast, and the @oktype argument is not used. In + * particular, this means that @oktype can be a type which uses the + * "typeof": it will not be evaluated if typeof is not supported. + * + * Example: + * // We can take either an unsigned long or a void *. + * void _set_some_value(void *val); + * #define set_some_value(e) \ + * _set_some_value(typesafe_cb_cast(void *, (e), unsigned long)) + */ +#define typesafe_cb_cast(desttype, oktype, expr) \ + __builtin_choose_expr( \ + __builtin_types_compatible_p(__typeof__(0?(expr):(expr)), \ + oktype), \ + (desttype)(expr), (expr)) +#else +#define typesafe_cb_cast(desttype, oktype, expr) ((desttype)(expr)) +#endif + +/** + * typesafe_cb_cast3 - only cast an expression if it matches given types + * @desttype: the type to cast to + * @ok1: the first type we allow + * @ok2: the second type we allow + * @ok3: the third type we allow + * @expr: the expression to cast + * + * This is a convenient wrapper for multiple typesafe_cb_cast() calls. + * You can chain them inside each other (ie. use typesafe_cb_cast() + * for expr) if you need more than 3 arguments. + * + * Example: + * // We can take either a long, unsigned long, void * or a const void *. + * void _set_some_value(void *val); + * #define set_some_value(expr) \ + * _set_some_value(typesafe_cb_cast3(void *,, \ + * long, unsigned long, const void *,\ + * (expr))) + */ +#define typesafe_cb_cast3(desttype, ok1, ok2, ok3, expr) \ + typesafe_cb_cast(desttype, ok1, \ + typesafe_cb_cast(desttype, ok2, \ + typesafe_cb_cast(desttype, ok3, \ + (expr)))) + +/** + * typesafe_cb - cast a callback function if it matches the arg + * @rtype: the return type of the callback function + * @atype: the (pointer) type which the callback function expects. + * @fn: the callback function to cast + * @arg: the (pointer) argument to hand to the callback function. + * + * If a callback function takes a single argument, this macro does + * appropriate casts to a function which takes a single atype argument if the + * callback provided matches the @arg. + * + * It is assumed that @arg is of pointer type: usually @arg is passed + * or assigned to a void * elsewhere anyway. + * + * Example: + * void _register_callback(void (*fn)(void *arg), void *arg); + * #define register_callback(fn, arg) \ + * _register_callback(typesafe_cb(void, (fn), void*, (arg)), (arg)) + */ +#define typesafe_cb(rtype, atype, fn, arg) \ + typesafe_cb_cast(rtype (*)(atype), \ + rtype (*)(__typeof__(arg)), \ + (fn)) + +/** + * typesafe_cb_preargs - cast a callback function if it matches the arg + * @rtype: the return type of the callback function + * @atype: the (pointer) type which the callback function expects. + * @fn: the callback function to cast + * @arg: the (pointer) argument to hand to the callback function. + * + * This is a version of typesafe_cb() for callbacks that take other arguments + * before the @arg. + * + * Example: + * void _register_callback(void (*fn)(int, void *arg), void *arg); + * #define register_callback(fn, arg) \ + * _register_callback(typesafe_cb_preargs(void, (fn), void *, \ + * (arg), int), \ + * (arg)) + */ +#define typesafe_cb_preargs(rtype, atype, fn, arg, ...) \ + typesafe_cb_cast(rtype (*)(__VA_ARGS__, atype), \ + rtype (*)(__VA_ARGS__, __typeof__(arg)), \ + (fn)) + +/** + * typesafe_cb_postargs - cast a callback function if it matches the arg + * @rtype: the return type of the callback function + * @atype: the (pointer) type which the callback function expects. + * @fn: the callback function to cast + * @arg: the (pointer) argument to hand to the callback function. + * + * This is a version of typesafe_cb() for callbacks that take other arguments + * after the @arg. + * + * Example: + * void _register_callback(void (*fn)(void *arg, int), void *arg); + * #define register_callback(fn, arg) \ + * _register_callback(typesafe_cb_postargs(void, (fn), void *, \ + * (arg), int), \ + * (arg)) + */ +#define typesafe_cb_postargs(rtype, atype, fn, arg, ...) \ + typesafe_cb_cast(rtype (*)(atype, __VA_ARGS__), \ + rtype (*)(__typeof__(arg), __VA_ARGS__), \ + (fn)) +#endif /* CCAN_CAST_IF_TYPE_H */ diff --git a/cgminer.c b/cgminer.c new file mode 100644 index 0000000..e9a0683 --- /dev/null +++ b/cgminer.c @@ -0,0 +1,11279 @@ +/* + * Copyright 2011-2014 Con Kolivas + * Copyright 2011-2012 Luke Dashjr + * Copyright 2010 Jeff Garzik + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#include "config.h" + +#ifdef HAVE_CURSES +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef USE_USBUTILS +#include +#endif + +#include +#include + +#ifndef WIN32 +#include +#else +#include +#endif +#include +#include +#ifdef HAVE_LIBCURL +#include +#else +char *curly = ":D"; +#endif +#include +#include + +#include "compat.h" +#include "miner.h" +#include "bench_block.h" +#ifdef USE_USBUTILS +#include "usbutils.h" +#endif + +#if defined(unix) || defined(__APPLE__) +#include +#include +#include +#endif + +#ifdef USE_AVALON +#include "driver-avalon.h" +#endif + +#ifdef USE_AVALON2 +#include "driver-avalon2.h" +#endif + +#ifdef USE_AVALON4 +#include "driver-avalon4.h" +#endif + +#ifdef USE_BFLSC +#include "driver-bflsc.h" +#endif + +#ifdef USE_SP10 +#include "driver-spondoolies-sp10.h" +#endif + +#ifdef USE_SP30 +#include "driver-spondoolies-sp30.h" +#endif + +#ifdef USE_BLOCK_ERUPTER +#include "driver-blockerupter.h" +#endif + +#ifdef USE_BITFURY +#include "driver-bitfury.h" +#endif + +#ifdef USE_COINTERRA +#include "driver-cointerra.h" +#endif + +#ifdef USE_HASHFAST +#include "driver-hashfast.h" +#endif + +#ifdef USE_BITMAIN +#include "driver-bitmain.h" +#endif + +#ifdef USE_BITMAIN_C5 +#include "driver-btm-c5.h" +#endif + +#if defined(USE_BITFORCE) || defined(USE_ICARUS) || defined(USE_AVALON) || defined(USE_AVALON2) || defined(USE_BMSC) || defined (USE_BITMAIN) || defined(USE_MODMINER) +# define USE_FPGA +#endif + +struct strategies strategies[] = +{ + { "Failover" }, + { "Round Robin" }, + { "Rotate" }, + { "Load Balance" }, + { "Balance" }, +}; + +static char packagename[256]; + +FILE * g_logwork_file = NULL; +FILE * g_logwork_files[65] = {0}; +FILE * g_logwork_diffs[65] = {0}; +int g_logwork_asicnum = 0; + +bool opt_work_update; +bool opt_protocol; +static struct benchfile_layout +{ + int length; + char *name; +} benchfile_data[] = +{ + { 1, "Version" }, + { 64, "MerkleRoot" }, + { 64, "PrevHash" }, + { 8, "DifficultyBits" }, + { 10, "NonceTime" } // 10 digits +}; +enum benchwork +{ + BENCHWORK_VERSION = 0, + BENCHWORK_MERKLEROOT, + BENCHWORK_PREVHASH, + BENCHWORK_DIFFBITS, + BENCHWORK_NONCETIME, + BENCHWORK_COUNT +}; + +#ifdef HAVE_LIBCURL +static char *opt_btc_address; +static char *opt_btc_sig; +#endif +static char *opt_benchfile; +static bool opt_benchfile_display; +static FILE *benchfile_in; +static int benchfile_line; +static int benchfile_work; +static bool opt_benchmark; +bool have_longpoll; +bool want_per_device_stats; +bool use_syslog; +bool opt_quiet; +bool opt_realquiet; +bool opt_loginput; +bool opt_compact; +const int opt_cutofftemp = 95; +int opt_log_interval = 5; +int opt_queue = 1; +static int max_queue = 1; +int opt_scantime = -1; +int opt_expiry = 120; +static const bool opt_time = true; +unsigned long long global_hashrate; +unsigned long global_quota_gcd = 1; +time_t last_getwork; + +#if defined(USE_USBUTILS) +int nDevs; +#endif +bool opt_restart = true; +bool opt_nogpu; + +struct list_head scan_devices; +static bool opt_display_devs; +int total_devices; +int zombie_devs; +static int most_devices; +struct cgpu_info **devices; +int mining_threads; +int num_processors; +#ifdef HAVE_CURSES +bool use_curses = true; +#else +bool use_curses; +#endif +static bool opt_widescreen; +static bool alt_status; +static bool switch_status; +static bool opt_submit_stale = true; +static int opt_shares; +bool opt_fail_only; +static bool opt_fix_protocol; +bool opt_lowmem; +bool opt_autofan; +bool opt_autoengine; +bool opt_noadl; +char *opt_version_path = NULL; +char *opt_logfile_path = NULL; +char *opt_logfile_openflag = NULL; +char *opt_logwork_path = NULL; +char *opt_logwork_asicnum = NULL; +bool opt_logwork_diff = false; +char *opt_api_allow = NULL; +char *opt_api_groups; +char *opt_api_description = PACKAGE_STRING; +int opt_api_port = 4028; +char *opt_api_host = API_LISTEN_ADDR; +bool opt_api_listen; +bool opt_api_mcast; +char *opt_api_mcast_addr = API_MCAST_ADDR; +char *opt_api_mcast_code = API_MCAST_CODE; +char *opt_api_mcast_des = ""; +int opt_api_mcast_port = 4028; +bool opt_api_network; +bool opt_delaynet; +bool opt_disable_pool; +static bool no_work; +#ifdef USE_ICARUS +char *opt_icarus_options = NULL; +char *opt_icarus_timing = NULL; +float opt_anu_freq = 250; +float opt_au3_freq = 225; +int opt_au3_volt = 750; +float opt_rock_freq = 270; +#endif +bool opt_worktime; +#ifdef USE_AVALON +char *opt_avalon_options; +char *opt_bitburner_fury_options; +static char *opt_set_avalon_fan; +static char *opt_set_avalon_freq; +#endif +#ifdef USE_AVALON2 +static char *opt_set_avalon2_freq; +static char *opt_set_avalon2_fan; +static char *opt_set_avalon2_voltage; +#endif +#ifdef USE_AVALON4 +static char *opt_set_avalon4_fan; +static char *opt_set_avalon4_voltage; +static char *opt_set_avalon4_freq; +#endif +#ifdef USE_BLOCKERUPTER +int opt_bet_clk = 0; +#endif +#ifdef USE_HASHRATIO +#include "driver-hashratio.h" +#endif +#ifdef USE_KLONDIKE +char *opt_klondike_options = NULL; +#endif +#ifdef USE_DRILLBIT +char *opt_drillbit_options = NULL; +char *opt_drillbit_auto = NULL; +#endif +char *opt_bab_options = NULL; +#ifdef USE_BITMINE_A1 +char *opt_bitmine_a1_options = NULL; +#endif +#ifdef USE_BMSC +char *opt_bmsc_options = NULL; +char *opt_bmsc_bandops = NULL; +char *opt_bmsc_timing = NULL; +bool opt_bmsc_gray = false; +char *opt_bmsc_freq = NULL; +char *opt_bmsc_rdreg = NULL; +char *opt_bmsc_voltage = NULL; +bool opt_bmsc_bootstart = false; +bool opt_bmsc_rdworktest = false; +#endif +#ifdef USE_BITMAIN +char *opt_bitmain_options = NULL; +char *opt_bitmain_freq = NULL; +char *opt_bitmain_voltage = NULL; +#endif +#ifdef USE_HASHFAST +static char *opt_set_hfa_fan; +#endif +static char *opt_set_null; +#ifdef USE_MINION +int opt_minion_chipreport; +char *opt_minion_cores; +bool opt_minion_extra; +char *opt_minion_freq; +int opt_minion_freqchange = 1000; +int opt_minion_freqpercent = 70; +bool opt_minion_idlecount; +int opt_minion_ledcount; +int opt_minion_ledlimit = 98; +bool opt_minion_noautofreq; +bool opt_minion_overheat; +int opt_minion_spidelay; +char *opt_minion_spireset; +int opt_minion_spisleep = 200; +int opt_minion_spiusec; +char *opt_minion_temp; +#endif + +#ifdef USE_USBUTILS +char *opt_usb_select = NULL; +int opt_usbdump = -1; +bool opt_usb_list_all; +cgsem_t usb_resource_sem; +static pthread_t usb_poll_thread; +static bool usb_polling; +#endif + +char *opt_kernel_path; +char *cgminer_path; + +#if defined(USE_BITFORCE) +bool opt_bfl_noncerange; +#endif +#define QUIET (opt_quiet || opt_realquiet) + +struct thr_info *control_thr; +struct thr_info **mining_thr; +static int gwsched_thr_id; +static int watchpool_thr_id; +static int watchdog_thr_id; +#ifdef HAVE_CURSES +static int input_thr_id; +#endif +int gpur_thr_id; +static int api_thr_id; +#ifdef USE_USBUTILS +static int usbres_thr_id; +static int hotplug_thr_id; +#endif +static int total_control_threads; +bool hotplug_mode; +static int new_devices; +static int new_threads; +int hotplug_time = 5; + +#if LOCK_TRACKING +pthread_mutex_t lockstat_lock; +#endif + +pthread_mutex_t hash_lock; +pthread_mutex_t update_job_lock; + +static pthread_mutex_t *stgd_lock; +pthread_mutex_t console_lock; +cglock_t ch_lock; +static pthread_rwlock_t blk_lock; +static pthread_mutex_t sshare_lock; + +pthread_rwlock_t netacc_lock; +pthread_rwlock_t mining_thr_lock; +pthread_rwlock_t devices_lock; + +static pthread_mutex_t lp_lock; +static pthread_cond_t lp_cond; + +pthread_mutex_t restart_lock; +pthread_cond_t restart_cond; + +pthread_cond_t gws_cond; + +#define CG_LOCAL_MHASHES_MAX_NUM 12 +double g_local_mhashes_dones[CG_LOCAL_MHASHES_MAX_NUM] = {0}; +int g_local_mhashes_index = 0; +double g_displayed_rolling = 0; +char g_miner_version[256] = {0}; +char g_miner_compiletime[256] = {0}; +char g_miner_type[256] = {0}; + +double rolling1, rolling5, rolling15; +double total_rolling; +double total_mhashes_done; +char displayed_hash_rate[16] = {0}; +static struct timeval total_tv_start, total_tv_end; +static struct timeval restart_tv_start, update_tv_start; + +cglock_t control_lock; +pthread_mutex_t stats_lock; + +int hw_errors; +int g_max_fan, g_max_temp; +int64_t total_accepted, total_rejected, total_diff1; +int64_t total_getworks, total_stale, total_discarded; +double total_diff_accepted, total_diff_rejected, total_diff_stale; +static int staged_rollable; +unsigned int new_blocks; +static unsigned int work_block = 0; +unsigned int found_blocks; + +unsigned int local_work; +unsigned int local_work_last = 0; +long local_work_lasttime = 0; +unsigned int total_go, total_ro; + +struct pool **pools; +static struct pool *currentpool = NULL; + +int total_pools, enabled_pools; +enum pool_strategy pool_strategy = POOL_FAILOVER; +int opt_rotate_period; +static int total_urls, total_users, total_passes, total_userpasses; + +static +#ifndef HAVE_CURSES +const +#endif +bool curses_active; + +/* Protected by ch_lock */ +char current_hash[68]; +static char prev_block[12]; +static char current_block[32]; + +static char datestamp[40]; +static char blocktime[32]; +struct timeval block_timeval; +static char best_share[8] = "0"; +double current_diff = 0xFFFFFFFFFFFFFFFFULL; +static char block_diff[8]; +uint64_t best_diff = 0; + +struct block +{ + char hash[68]; + UT_hash_handle hh; + int block_no; +}; + +static struct block *blocks = NULL; + + +int swork_id; + +/* For creating a hash database of stratum shares submitted that have not had + * a response yet */ +struct stratum_share +{ + UT_hash_handle hh; + bool block; + struct work *work; + int id; + time_t sshare_time; + time_t sshare_sent; +}; + +static struct stratum_share *stratum_shares = NULL; + +char *opt_socks_proxy = NULL; +int opt_suggest_diff; +int opt_multi_version = 1; +static const char def_conf[] = "bmminer.conf"; +static char *default_config; +static bool config_loaded; +static int include_count; +#define JSON_INCLUDE_CONF "include" +#define JSON_LOAD_ERROR "JSON decode of file '%s' failed\n %s" +#define JSON_LOAD_ERROR_LEN strlen(JSON_LOAD_ERROR) +#define JSON_MAX_DEPTH 10 +#define JSON_MAX_DEPTH_ERR "Too many levels of JSON includes (limit 10) or a loop" +#define JSON_WEB_ERROR "WEB config err" + +#if defined(unix) || defined(__APPLE__) +static char *opt_stderr_cmd = NULL; +static int forkpid; +#endif // defined(unix) + +struct sigaction termhandler, inthandler; + +struct thread_q *getq; + +static uint32_t total_work; +struct work *staged_work = NULL; + +struct schedtime +{ + bool enable; + struct tm tm; +}; + +struct schedtime schedstart; +struct schedtime schedstop; +bool sched_paused; + +static bool time_before(struct tm *tm1, struct tm *tm2) +{ + if (tm1->tm_hour < tm2->tm_hour) + return true; + if (tm1->tm_hour == tm2->tm_hour && tm1->tm_min < tm2->tm_min) + return true; + return false; +} + +static bool should_run(void) +{ + struct timeval tv; + struct tm *tm; + + if (!schedstart.enable && !schedstop.enable) + return true; + + cgtime(&tv); + const time_t tmp_time = tv.tv_sec; + tm = localtime(&tmp_time); + if (schedstart.enable) + { + if (!schedstop.enable) + { + if (time_before(tm, &schedstart.tm)) + return false; + + /* This is a once off event with no stop time set */ + schedstart.enable = false; + return true; + } + if (time_before(&schedstart.tm, &schedstop.tm)) + { + if (time_before(tm, &schedstop.tm) && !time_before(tm, &schedstart.tm)) + return true; + return false; + } /* Times are reversed */ + if (time_before(tm, &schedstart.tm)) + { + if (time_before(tm, &schedstop.tm)) + return true; + return false; + } + return true; + } + /* only schedstop.enable == true */ + if (!time_before(tm, &schedstop.tm)) + return false; + return true; +} + +void get_datestamp(char *f, size_t fsiz, struct timeval *tv) +{ + struct tm *tm; + + const time_t tmp_time = tv->tv_sec; + tm = localtime(&tmp_time); + snprintf(f, fsiz, "[%d-%02d-%02d %02d:%02d:%02d]", + tm->tm_year + 1900, + tm->tm_mon + 1, + tm->tm_mday, + tm->tm_hour, + tm->tm_min, + tm->tm_sec); +} + +static void get_timestamp(char *f, size_t fsiz, struct timeval *tv) +{ + struct tm *tm; + + const time_t tmp_time = tv->tv_sec; + tm = localtime(&tmp_time); + snprintf(f, fsiz, "[%02d:%02d:%02d]", + tm->tm_hour, + tm->tm_min, + tm->tm_sec); +} + +static char exit_buf[512]; + +static void applog_and_exit(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vsnprintf(exit_buf, sizeof(exit_buf), fmt, ap); + va_end(ap); + _applog(LOG_ERR, exit_buf, true); + exit(1); +} + +static pthread_mutex_t sharelog_lock; +static FILE *sharelog_file = NULL; + +static struct thr_info *__get_thread(int thr_id) +{ + return mining_thr[thr_id]; +} + +struct thr_info *get_thread(int thr_id) +{ + struct thr_info *thr; + + rd_lock(&mining_thr_lock); + thr = __get_thread(thr_id); + rd_unlock(&mining_thr_lock); + + return thr; +} + +static struct cgpu_info *get_thr_cgpu(int thr_id) +{ + struct thr_info *thr = get_thread(thr_id); + + return thr->cgpu; +} + +struct cgpu_info *get_devices(int id) +{ + struct cgpu_info *cgpu; + + rd_lock(&devices_lock); + cgpu = devices[id]; + rd_unlock(&devices_lock); + + return cgpu; +} + +static void sharelog(const char*disposition, const struct work*work) +{ + char *target, *hash, *data; + struct cgpu_info *cgpu; + unsigned long int t; + struct pool *pool; + int thr_id, rv; + char s[1024]; + size_t ret; + + if (!sharelog_file) + return; + + thr_id = work->thr_id; + cgpu = get_thr_cgpu(thr_id); + pool = work->pool; + t = (unsigned long int)(work->tv_work_found.tv_sec); + target = bin2hex(work->target, sizeof(work->target)); + hash = bin2hex(work->hash, sizeof(work->hash)); + data = bin2hex(work->data, sizeof(work->data)); + + // timestamp,disposition,target,pool,dev,thr,sharehash,sharedata + rv = snprintf(s, sizeof(s), "%lu,%s,%s,%s,%s%u,%u,%s,%s\n", t, disposition, target, pool->rpc_url, cgpu->drv->name, cgpu->device_id, thr_id, hash, data); + free(target); + free(hash); + free(data); + if (rv >= (int)(sizeof(s))) + s[sizeof(s) - 1] = '\0'; + else if (rv < 0) + { + applog(LOG_ERR, "sharelog printf error"); + return; + } + + mutex_lock(&sharelog_lock); + ret = fwrite(s, rv, 1, sharelog_file); + fflush(sharelog_file); + mutex_unlock(&sharelog_lock); + + if (ret != 1) + applog(LOG_ERR, "sharelog fwrite error"); +} + +static char *getwork_req = "{\"method\": \"getwork\", \"params\": [], \"id\":0}\n"; + +static char *gbt_req = "{\"id\": 0, \"method\": \"getblocktemplate\", \"params\": [{\"capabilities\": [\"coinbasetxn\", \"workid\", \"coinbase/append\"]}]}\n"; + +static char *gbt_solo_req = "{\"id\": 0, \"method\": \"getblocktemplate\"}\n"; + +/* Adjust all the pools' quota to the greatest common denominator after a pool + * has been added or the quotas changed. */ +void adjust_quota_gcd(void) +{ + unsigned long gcd, lowest_quota = ~0UL, quota; + struct pool *pool; + int i; + + for (i = 0; i < total_pools; i++) + { + pool = pools[i]; + quota = pool->quota; + if (!quota) + continue; + if (quota < lowest_quota) + lowest_quota = quota; + } + + if (likely(lowest_quota < ~0UL)) + { + gcd = lowest_quota; + for (i = 0; i < total_pools; i++) + { + pool = pools[i]; + quota = pool->quota; + if (!quota) + continue; + while (quota % gcd) + gcd--; + } + } + else + gcd = 1; + + for (i = 0; i < total_pools; i++) + { + pool = pools[i]; + pool->quota_used *= global_quota_gcd; + pool->quota_used /= gcd; + pool->quota_gcd = pool->quota / gcd; + } + + global_quota_gcd = gcd; + applog(LOG_DEBUG, "Global quota greatest common denominator set to %lu", gcd); +} + +/* Return value is ignored if not called from add_pool_details */ +struct pool *add_pool(void) +{ + struct pool *pool; + + pool = calloc(sizeof(struct pool), 1); +#ifdef USE_BITMAIN_C5 + pool->support_vil = false; +#endif + if (!pool) + quit(1, "Failed to malloc pool in add_pool"); + pool->pool_no = pool->prio = total_pools; + pools = realloc(pools, sizeof(struct pool *) * (total_pools + 2)); + pools[total_pools++] = pool; + mutex_init(&pool->pool_lock); + if (unlikely(pthread_cond_init(&pool->cr_cond, NULL))) + quit(1, "Failed to pthread_cond_init in add_pool"); + cglock_init(&pool->data_lock); + mutex_init(&pool->stratum_lock); + cglock_init(&pool->gbt_lock); + INIT_LIST_HEAD(&pool->curlring); + + /* Make sure the pool doesn't think we've been idle since time 0 */ + pool->tv_idle.tv_sec = ~0UL; + + pool->rpc_req = getwork_req; + pool->rpc_proxy = NULL; + pool->quota = 1; + adjust_quota_gcd(); + + return pool; +} + +/* Pool variant of test and set */ +static bool pool_tset(struct pool *pool, bool *var) +{ + bool ret; + + mutex_lock(&pool->pool_lock); + ret = *var; + *var = true; + mutex_unlock(&pool->pool_lock); + + return ret; +} + +bool pool_tclear(struct pool *pool, bool *var) +{ + bool ret; + + mutex_lock(&pool->pool_lock); + ret = *var; + *var = false; + mutex_unlock(&pool->pool_lock); + + return ret; +} + +struct pool *current_pool(void) +{ + struct pool *pool; + + cg_rlock(&control_lock); + pool = currentpool; + cg_runlock(&control_lock); + + return pool; +} + +char *set_int_range(const char *arg, int *i, int min, int max) +{ + char *err = opt_set_intval(arg, i); + + if (err) + return err; + + if (*i < min || *i > max) + return "Value out of range"; + + return NULL; +} + +static char *set_int_0_to_9999(const char *arg, int *i) +{ + return set_int_range(arg, i, 0, 9999); +} + +static char *set_int_1_to_65535(const char *arg, int *i) +{ + return set_int_range(arg, i, 1, 65535); +} + +static char *set_int_0_to_10(const char *arg, int *i) +{ + return set_int_range(arg, i, 0, 10); +} + +static char *set_int_0_to_100(const char *arg, int *i) +{ + return set_int_range(arg, i, 0, 100); +} + +static char *set_int_0_to_255(const char *arg, int *i) +{ + return set_int_range(arg, i, 0, 255); +} + +static char *set_int_0_to_200(const char *arg, int *i) +{ + return set_int_range(arg, i, 0, 200); +} + +static char *set_int_32_to_63(const char *arg, int *i) +{ + return set_int_range(arg, i, 32, 63); +} + +static char *set_int_22_to_55(const char *arg, int *i) +{ + return set_int_range(arg, i, 22, 55); +} + +static char *set_int_42_to_65(const char *arg, int *i) +{ + return set_int_range(arg, i, 42, 62); +} + +static char *set_int_1_to_10(const char *arg, int *i) +{ + return set_int_range(arg, i, 1, 10); +} + +static char __maybe_unused *set_int_0_to_4(const char *arg, int *i) +{ + return set_int_range(arg, i, 0, 4); +} + +#ifdef USE_FPGA_SERIAL +static char *opt_add_serial; +static char *add_serial(char *arg) +{ + string_elist_add(arg, &scan_devices); + return NULL; +} +#endif + +void get_intrange(char *arg, int *val1, int *val2) +{ + if (sscanf(arg, "%d-%d", val1, val2) == 1) + *val2 = *val1; +} + +static char *set_balance(enum pool_strategy *strategy) +{ + *strategy = POOL_BALANCE; + return NULL; +} + +static char *set_loadbalance(enum pool_strategy *strategy) +{ + *strategy = POOL_LOADBALANCE; + return NULL; +} + +static char *set_rotate(const char *arg, char __maybe_unused *i) +{ + pool_strategy = POOL_ROTATE; + return set_int_range(arg, &opt_rotate_period, 0, 9999); +} + +static char *set_rr(enum pool_strategy *strategy) +{ + *strategy = POOL_ROUNDROBIN; + return NULL; +} + +/* Detect that url is for a stratum protocol either via the presence of + * stratum+tcp or by detecting a stratum server response */ +bool detect_stratum(struct pool *pool, char *url) +{ + check_extranonce_option(pool, url); + if (!extract_sockaddr(url, &pool->sockaddr_url, &pool->stratum_port)) + return false; + + if (!strncasecmp(url, "stratum+tcp://", 14)) + { + pool->rpc_url = strdup(url); + pool->has_stratum = true; + pool->stratum_url = pool->sockaddr_url; + return true; + } + + return false; +} + +static struct pool *add_url(void) +{ + total_urls++; + if (total_urls > total_pools) + add_pool(); + return pools[total_urls - 1]; +} + +static void setup_url(struct pool *pool, char *arg) +{ + arg = get_proxy(arg, pool); + + if (detect_stratum(pool, arg)) + return; + + opt_set_charp(arg, &pool->rpc_url); + if (strncmp(arg, "http://", 7) && + strncmp(arg, "https://", 8)) + { + char *httpinput; + + httpinput = malloc(256); + if (!httpinput) + quit(1, "Failed to malloc httpinput"); + strcpy(httpinput, "stratum+tcp://"); + strncat(httpinput, arg, 242); + detect_stratum(pool, httpinput); + } +} + +static char *set_url(char *arg) +{ + struct pool *pool = add_url(); + + setup_url(pool, arg); + return NULL; +} + +static char *set_quota(char *arg) +{ + char *semicolon = strchr(arg, ';'), *url; + int len, qlen, quota; + struct pool *pool; + + if (!semicolon) + return "No semicolon separated quota;URL pair found"; + len = strlen(arg); + *semicolon = '\0'; + qlen = strlen(arg); + if (!qlen) + return "No parameter for quota found"; + len -= qlen + 1; + if (len < 1) + return "No parameter for URL found"; + quota = atoi(arg); + if (quota < 0) + return "Invalid negative parameter for quota set"; + url = arg + qlen + 1; + pool = add_url(); + setup_url(pool, url); + pool->quota = quota; + applog(LOG_INFO, "Setting pool %d to quota %d", pool->pool_no, pool->quota); + adjust_quota_gcd(); + + return NULL; +} + +static char *set_user(const char *arg) +{ + struct pool *pool; + + if (total_userpasses) + return "Use only user + pass or userpass, but not both"; + total_users++; + if (total_users > total_pools) + add_pool(); + + pool = pools[total_users - 1]; + opt_set_charp(arg, &pool->rpc_user); + + return NULL; +} + +static char *set_pass(const char *arg) +{ + struct pool *pool; + + if (total_userpasses) + return "Use only user + pass or userpass, but not both"; + total_passes++; + if (total_passes > total_pools) + add_pool(); + + pool = pools[total_passes - 1]; + opt_set_charp(arg, &pool->rpc_pass); + + return NULL; +} + +static char *set_userpass(const char *arg) +{ + struct pool *pool; + char *updup; + + if (total_users || total_passes) + return "Use only user + pass or userpass, but not both"; + total_userpasses++; + if (total_userpasses > total_pools) + add_pool(); + + pool = pools[total_userpasses - 1]; + updup = strdup(arg); + opt_set_charp(arg, &pool->rpc_userpass); + pool->rpc_user = strtok(updup, ":"); + if (!pool->rpc_user) + return "Failed to find : delimited user info"; + pool->rpc_pass = strtok(NULL, ":"); + if (!pool->rpc_pass) + pool->rpc_pass = strdup(""); + + return NULL; +} + +static char *enable_debug(bool *flag) +{ + *flag = true; + /* Turn on verbose output, too. */ + opt_log_output = true; + return NULL; +} + +static char *opt_set_sched_start; +static char *opt_set_sched_stop; + +static char *set_schedtime(const char *arg, struct schedtime *st) +{ + if (sscanf(arg, "%d:%d", &st->tm.tm_hour, &st->tm.tm_min) != 2) + return "Invalid time set, should be HH:MM"; + if (st->tm.tm_hour > 23 || st->tm.tm_min > 59 || st->tm.tm_hour < 0 || st->tm.tm_min < 0) + return "Invalid time set."; + st->enable = true; + return NULL; +} + +static char *set_sched_start(const char *arg) +{ + return set_schedtime(arg, &schedstart); +} + +static char *set_sched_stop(const char *arg) +{ + return set_schedtime(arg, &schedstop); +} + +static char *opt_set_sharelog; +static char* set_sharelog(char *arg) +{ + char *r = ""; + long int i = strtol(arg, &r, 10); + + if ((!*r) && i >= 0 && i <= INT_MAX) + { + sharelog_file = fdopen((int)i, "a"); + if (!sharelog_file) + applog(LOG_ERR, "Failed to open fd %u for share log", (unsigned int)i); + } + else if (!strcmp(arg, "-")) + { + sharelog_file = stdout; + if (!sharelog_file) + applog(LOG_ERR, "Standard output missing for share log"); + } + else + { + sharelog_file = fopen(arg, "a"); + if (!sharelog_file) + applog(LOG_ERR, "Failed to open %s for share log", arg); + } + + return NULL; +} + +static char *temp_cutoff_str = NULL; +static char __maybe_unused *opt_set_temp_cutoff; + +char *set_temp_cutoff(char *arg) +{ + int val; + + if (!(arg && arg[0])) + return "Invalid parameters for set temp cutoff"; + val = atoi(arg); + if (val < 0 || val > 200) + return "Invalid value passed to set temp cutoff"; + temp_cutoff_str = arg; + + return NULL; +} + +static void load_temp_cutoffs() +{ + int i, val = 0, device = 0; + char *nextptr; + + if (temp_cutoff_str) + { + for (device = 0, nextptr = strtok(temp_cutoff_str, ","); nextptr; ++device, nextptr = strtok(NULL, ",")) + { + if (device >= total_devices) + quit(1, "Too many values passed to set temp cutoff"); + val = atoi(nextptr); + if (val < 0 || val > 200) + quit(1, "Invalid value passed to set temp cutoff"); + + rd_lock(&devices_lock); + devices[device]->cutofftemp = val; + rd_unlock(&devices_lock); + } + } + else + { + rd_lock(&devices_lock); + for (i = device; i < total_devices; ++i) + { + if (!devices[i]->cutofftemp) + devices[i]->cutofftemp = opt_cutofftemp; + } + rd_unlock(&devices_lock); + + return; + } + if (device <= 1) + { + rd_lock(&devices_lock); + for (i = device; i < total_devices; ++i) + devices[i]->cutofftemp = val; + rd_unlock(&devices_lock); + } +} + +static char *set_logfile_path(const char *arg) +{ + opt_set_charp(arg, &opt_logfile_path); + + return NULL; +} + +static char *set_logfile_openflag(const char *arg) +{ + opt_set_charp(arg, &opt_logfile_openflag); + + return NULL; +} + +static char *set_logwork_path(const char *arg) +{ + opt_set_charp(arg, &opt_logwork_path); + + return NULL; +} + +static char *set_logwork_asicnum(const char *arg) +{ + opt_set_charp(arg, &opt_logwork_asicnum); + + return NULL; +} + +static char *set_float_125_to_500(const char *arg, float *i) +{ + char *err = opt_set_floatval(arg, i); + + if (err) + return err; + + if (*i < 125 || *i > 500) + return "Value out of range"; + + return NULL; +} + +static char *set_float_100_to_250(const char *arg, float *i) +{ + char *err = opt_set_floatval(arg, i); + + if (err) + return err; + + if (*i < 100 || *i > 250) + return "Value out of range"; + + return NULL; +} +static char *set_version_path(const char *arg) +{ + opt_set_charp(arg, &opt_version_path); + + return NULL; +} + +#ifdef USE_BMSC +static char *set_bmsc_options(const char *arg) +{ + opt_set_charp(arg, &opt_bmsc_options); + + return NULL; +} + +static char *set_bmsc_bandops(const char *arg) +{ + opt_set_charp(arg, &opt_bmsc_bandops); + + return NULL; +} + +static char *set_bmsc_timing(const char *arg) +{ + opt_set_charp(arg, &opt_bmsc_timing); + + return NULL; +} + +static char *set_bmsc_freq(const char *arg) +{ + opt_set_charp(arg, &opt_bmsc_freq); + + return NULL; +} + +static char *set_bmsc_rdreg(const char *arg) +{ + opt_set_charp(arg, &opt_bmsc_rdreg); + + return NULL; +} + +static char *set_bmsc_voltage(const char *arg) +{ + opt_set_charp(arg, &opt_bmsc_voltage); + + return NULL; +} +#endif + +#ifdef USE_BITMAIN +static char *set_bitmain_options(const char *arg) +{ + opt_set_charp(arg, &opt_bitmain_options); + + return NULL; +} +static char *set_bitmain_freq(const char *arg) +{ + opt_set_charp(arg, &opt_bitmain_freq); + + return NULL; +} +static char *set_bitmain_voltage(const char *arg) +{ + opt_set_charp(arg, &opt_bitmain_voltage); + + return NULL; +} + +#endif + +static char *set_null(const char __maybe_unused *arg) +{ + return NULL; +} + +/* These options are available from config file or commandline */ +static struct opt_table opt_config_table[] = +{ +#ifdef USE_ICARUS + OPT_WITH_ARG("--anu-freq", + set_float_125_to_500, &opt_show_floatval, &opt_anu_freq, + "Set AntminerU1/2 frequency in MHz, range 125-500"), +#endif + + OPT_WITH_ARG("--version-file", + set_version_path, NULL, opt_hidden, + "Set miner version file"), + OPT_WITH_ARG("--logfile-openflag", + set_logfile_openflag, NULL, opt_hidden, + "Set log file open flag, default: a+"), + OPT_WITH_ARG("--logwork", + set_logwork_path, NULL, opt_hidden, + "Set log work file path, following: minertext"), + OPT_WITH_ARG("--logwork-asicnum", + set_logwork_asicnum, NULL, opt_hidden, + "Set log work asic num, following: 1, 32, 64"), + OPT_WITHOUT_ARG("--logwork-diff", + opt_set_bool, &opt_logwork_diff, + "Allow log work diff"), + OPT_WITH_ARG("--logfile", + set_logfile_path, NULL, opt_hidden, + "Set log file, default: bmminer.log"), + OPT_WITH_ARG("--api-allow", + opt_set_charp, NULL, &opt_api_allow, + "Allow API access only to the given list of [G:]IP[/Prefix] addresses[/subnets]"), + OPT_WITH_ARG("--api-description", + opt_set_charp, NULL, &opt_api_description, + "Description placed in the API status header, default: bmminer version"), + OPT_WITH_ARG("--api-groups", + opt_set_charp, NULL, &opt_api_groups, + "API one letter groups G:cmd:cmd[,P:cmd:*...] defining the cmds a groups can use"), + OPT_WITHOUT_ARG("--api-listen", + opt_set_bool, &opt_api_listen, + "Enable API, default: disabled"), + OPT_WITHOUT_ARG("--api-mcast", + opt_set_bool, &opt_api_mcast, + "Enable API Multicast listener, default: disabled"), + OPT_WITH_ARG("--api-mcast-addr", + opt_set_charp, NULL, &opt_api_mcast_addr, + "API Multicast listen address"), + OPT_WITH_ARG("--api-mcast-code", + opt_set_charp, NULL, &opt_api_mcast_code, + "Code expected in the API Multicast message, don't use '-'"), + OPT_WITH_ARG("--api-mcast-des", + opt_set_charp, NULL, &opt_api_mcast_des, + "Description appended to the API Multicast reply, default: ''"), + OPT_WITH_ARG("--api-mcast-port", + set_int_1_to_65535, opt_show_intval, &opt_api_mcast_port, + "API Multicast listen port"), + OPT_WITHOUT_ARG("--api-network", + opt_set_bool, &opt_api_network, + "Allow API (if enabled) to listen on/for any address, default: only 127.0.0.1"), + OPT_WITH_ARG("--api-port", + set_int_1_to_65535, opt_show_intval, &opt_api_port, + "Port number of miner API"), + OPT_WITH_ARG("--api-host", + opt_set_charp, NULL, &opt_api_host, + "Specify API listen address, default: 0.0.0.0"), +#ifdef USE_ICARUS + OPT_WITH_ARG("--au3-freq", + set_float_100_to_250, &opt_show_floatval, &opt_au3_freq, + "Set AntminerU3 frequency in MHz, range 100-250"), + OPT_WITH_ARG("--au3-volt", + set_int_0_to_9999, &opt_show_intval, &opt_au3_volt, + "Set AntminerU3 voltage in mv, range 725-850, 0 to not set"), +#endif +#ifdef USE_AVALON + OPT_WITHOUT_ARG("--avalon-auto", + opt_set_bool, &opt_avalon_auto, + "Adjust avalon overclock frequency dynamically for best hashrate"), + OPT_WITH_ARG("--avalon-cutoff", + set_int_0_to_100, opt_show_intval, &opt_avalon_overheat, + "Set avalon overheat cut off temperature"), + OPT_WITH_CBARG("--avalon-fan", + set_avalon_fan, NULL, &opt_set_avalon_fan, + "Set fanspeed percentage for avalon, single value or range (default: 20-100)"), + OPT_WITH_CBARG("--avalon-freq", + set_avalon_freq, NULL, &opt_set_avalon_freq, + "Set frequency range for avalon-auto, single value or range"), + OPT_WITH_ARG("--avalon-options", + opt_set_charp, NULL, &opt_avalon_options, + "Set avalon options baud:miners:asic:timeout:freq:tech"), + OPT_WITH_ARG("--avalon-temp", + set_int_0_to_100, opt_show_intval, &opt_avalon_temp, + "Set avalon target temperature"), +#endif +#ifdef USE_AVALON2 + OPT_WITH_CBARG("--avalon2-freq", + set_avalon2_freq, NULL, &opt_set_avalon2_freq, + "Set frequency range for Avalon2, single value or range, step: 25"), + OPT_WITH_CBARG("--avalon2-voltage", + set_avalon2_voltage, NULL, &opt_set_avalon2_voltage, + "Set Avalon2 core voltage, in millivolts, step: 125"), + OPT_WITH_CBARG("--avalon2-fan", + set_avalon2_fan, NULL, &opt_set_avalon2_fan, + "Set Avalon2 target fan speed"), + OPT_WITH_ARG("--avalon2-cutoff", + set_int_0_to_100, opt_show_intval, &opt_avalon2_overheat, + "Set Avalon2 overheat cut off temperature"), + OPT_WITHOUT_ARG("--avalon2-fixed-speed", + set_avalon2_fixed_speed, &opt_avalon2_fan_fixed, + "Set Avalon2 fan to fixed speed"), + OPT_WITH_ARG("--avalon2-polling-delay", + set_int_1_to_65535, opt_show_intval, &opt_avalon2_polling_delay, + "Set Avalon2 polling delay value (ms)"), +#endif +#ifdef USE_AVALON4 + OPT_WITHOUT_ARG("--avalon4-automatic-voltage", + opt_set_bool, &opt_avalon4_autov, + "Automatic adjust voltage base on module DH"), + OPT_WITH_CBARG("--avalon4-voltage", + set_avalon4_voltage, NULL, &opt_set_avalon4_voltage, + "Set Avalon4 core voltage, in millivolts, step: 125"), + OPT_WITH_CBARG("--avalon4-freq", + set_avalon4_freq, NULL, &opt_set_avalon4_freq, + "Set frequency for Avalon4, 1 to 3 values, example: 445:385:370"), + OPT_WITH_CBARG("--avalon4-fan", + set_avalon4_fan, NULL, &opt_set_avalon4_fan, + "Set Avalon4 target fan speed range"), + OPT_WITH_ARG("--avalon4-temp", + set_int_22_to_55, opt_show_intval, &opt_avalon4_temp_target, + "Set Avalon4 target temperature"), + OPT_WITH_ARG("--avalon4-cutoff", + set_int_42_to_65, opt_show_intval, &opt_avalon4_overheat, + "Set Avalon4 overheat cut off temperature"), + OPT_WITH_ARG("--avalon4-polling-delay", + set_int_1_to_65535, opt_show_intval, &opt_avalon4_polling_delay, + "Set Avalon4 polling delay value (ms)"), + OPT_WITH_ARG("--avalon4-ntime-offset", + opt_set_intval, opt_show_intval, &opt_avalon4_ntime_offset, + "Set Avalon4 MM ntime rolling max offset"), + OPT_WITH_ARG("--avalon4-aucspeed", + opt_set_intval, opt_show_intval, &opt_avalon4_aucspeed, + "Set Avalon4 AUC IIC bus speed"), + OPT_WITH_ARG("--avalon4-aucxdelay", + opt_set_intval, opt_show_intval, &opt_avalon4_aucxdelay, + "Set Avalon4 AUC IIC xfer read delay, 4800 ~= 1ms"), +#endif +#ifdef USE_BAB + OPT_WITH_ARG("--bab-options", + opt_set_charp, NULL, &opt_bab_options, + "Set bab options max:def:min:up:down:hz:delay:trf"), +#endif + OPT_WITHOUT_ARG("--balance", + set_balance, &pool_strategy, + "Change multipool strategy from failover to even share balance"), + OPT_WITH_ARG("--benchfile", + opt_set_charp, NULL, &opt_benchfile, + "Run bmminer in benchmark mode using a work file - produces no shares"), + OPT_WITHOUT_ARG("--benchfile-display", + opt_set_bool, &opt_benchfile_display, + "Display each benchfile nonce found"), + OPT_WITHOUT_ARG("--benchmark", + opt_set_bool, &opt_benchmark, + "Run bmminer in benchmark mode - produces no shares"), +#if defined(USE_BITFORCE) + OPT_WITHOUT_ARG("--bfl-range", + opt_set_bool, &opt_bfl_noncerange, + "Use nonce range on bitforce devices if supported"), +#endif +#ifdef USE_BFLSC + OPT_WITH_ARG("--bflsc-overheat", + set_int_0_to_200, opt_show_intval, &opt_bflsc_overheat, + "Set overheat temperature where BFLSC devices throttle, 0 to disable"), +#endif +#ifdef USE_AVALON + OPT_WITH_ARG("--bitburner-voltage", + opt_set_intval, NULL, &opt_bitburner_core_voltage, + "Set BitBurner (Avalon) core voltage, in millivolts"), + OPT_WITH_ARG("--bitburner-fury-voltage", + opt_set_intval, NULL, &opt_bitburner_fury_core_voltage, + "Set BitBurner Fury core voltage, in millivolts"), + OPT_WITH_ARG("--bitburner-fury-options", + opt_set_charp, NULL, &opt_bitburner_fury_options, + "Override avalon-options for BitBurner Fury boards baud:miners:asic:timeout:freq"), +#endif +#ifdef USE_BMSC + OPT_WITH_ARG("--bmsc-options", + set_bmsc_options, NULL, NULL, + opt_hidden), + OPT_WITH_ARG("--bmsc-bandops", + set_bmsc_bandops, NULL, NULL, + opt_hidden), + OPT_WITH_ARG("--bmsc-timing", + set_bmsc_timing, NULL, NULL, + opt_hidden), + OPT_WITHOUT_ARG("--bmsc-gray", + opt_set_bool, &opt_bmsc_gray, + "Use gray"), + OPT_WITH_ARG("--bmsc-freq", + set_bmsc_freq, NULL, NULL, + opt_hidden), + OPT_WITH_ARG("--bmsc-rdreg", + set_bmsc_rdreg, NULL, NULL, + opt_hidden), + OPT_WITH_ARG("--bmsc-voltage", + set_bmsc_voltage, NULL, NULL, + opt_hidden), + OPT_WITHOUT_ARG("--bmsc-bootstart", + opt_set_bool, &opt_bmsc_bootstart, + "Enable boot start, default: disabled"), + OPT_WITHOUT_ARG("--bmsc-rdworktest", + opt_set_bool, &opt_bmsc_rdworktest, + "Record work test data to file"), +#endif + +#ifdef USE_BITMAIN_C5 + OPT_WITHOUT_ARG("--bitmain-fan-ctrl", + opt_set_bool, &opt_bitmain_fan_ctrl, + "Enable bitmain miner fan controlling"), + OPT_WITH_ARG("--bitmain-fan-pwm", + set_int_0_to_100, opt_show_intval, &opt_bitmain_fan_pwm, + "Set bitmain fan pwm percentage 0~100"), + OPT_WITH_ARG("--bitmain-freq", + set_int_0_to_9999,opt_show_intval, &opt_bitmain_c5_freq, + "Set frequency"), + OPT_WITH_ARG("--bitmain-voltage", + set_int_0_to_9999,opt_show_intval, &opt_bitmain_c5_voltage, + "Set voltage"), + OPT_WITHOUT_ARG("--bitmain-use-vil", + opt_set_bool, &opt_bitmain_new_cmd_type_vil, + "Set bitmain miner use vil mode"), + +#endif + +#ifdef USE_BITMAIN + OPT_WITH_ARG("--bitmain-dev", + set_bitmain_dev, NULL, NULL, + "Set bitmain device (default: usb mode, other windows: COM1 or linux: /dev/bitmain-asic)"), + OPT_WITHOUT_ARG("--bitmain-hwerror", + opt_set_bool, &opt_bitmain_hwerror, + "Set bitmain device detect hardware error"), + OPT_WITHOUT_ARG("--bitmain-checkall", + opt_set_bool, &opt_bitmain_checkall, + "Set bitmain check all"), + OPT_WITHOUT_ARG("--bitmain-checkn2diff", + opt_set_bool, &opt_bitmain_checkn2diff, + "Set bitmain check not 2 pow diff"), + OPT_WITHOUT_ARG("--bitmain-nobeeper", + opt_set_bool, &opt_bitmain_nobeeper, + "Set bitmain beeper no ringing"), + OPT_WITHOUT_ARG("--bitmain-notempoverctrl", + opt_set_bool, &opt_bitmain_notempoverctrl, + "Set bitmain not stop runing when temprerature is over 80 degree Celsius"), + OPT_WITHOUT_ARG("--bitmain-auto", + opt_set_bool, &opt_bitmain_auto, + "Adjust bitmain overclock frequency dynamically for best hashrate"), + OPT_WITHOUT_ARG("--bitmain-homemode", + opt_set_bool, &opt_bitmain_homemode, + "Set bitmain miner to home mode"), + OPT_WITH_ARG("--bitmain-cutoff", + set_int_0_to_100, opt_show_intval, &opt_bitmain_overheat, + "Set bitmain overheat cut off temperature"), + OPT_WITH_ARG("--bitmain-fan", + set_bitmain_fan, NULL, NULL, + "Set fanspeed percentage for bitmain, single value or range (default: 20-100)"), + OPT_WITH_ARG("--bitmain-freq", + set_bitmain_freq, NULL, NULL, + "Set frequency"), + OPT_WITH_ARG("--bitmain-voltage", + set_bitmain_voltage, NULL, NULL, + "Set voltage"), + OPT_WITH_ARG("--bitmain-options", + set_bitmain_options, NULL, NULL, + "Set bitmain options baud:miners:asic:timeout:freq"), + OPT_WITH_ARG("--bitmain-temp", + set_int_0_to_100, opt_show_intval, &opt_bitmain_temp, + "Set bitmain target temperature"), +#endif +#ifdef USE_BITMINE_A1 + OPT_WITH_ARG("--bitmine-a1-options", + opt_set_charp, NULL, &opt_bitmine_a1_options, + "Bitmine A1 options ref_clk_khz:sys_clk_khz:spi_clk_khz:override_chip_num"), +#endif +#ifdef USE_BITFURY + OPT_WITH_ARG("--bxf-bits", + set_int_32_to_63, opt_show_intval, &opt_bxf_bits, + "Set max BXF/HXF bits for overclocking"), + OPT_WITH_ARG("--bxf-debug", + set_int_0_to_4, opt_show_intval, &opt_bxf_debug, + "BXF: Debug all USB I/O, > is to the board(s), < is from the board(s)"), + OPT_WITH_ARG("--bxf-temp-target", + set_int_0_to_200, opt_show_intval, &opt_bxf_temp_target, + "Set target temperature for BXF/HXF devices"), + OPT_WITH_ARG("--bxm-bits", + set_int_0_to_100, opt_show_intval, &opt_bxm_bits, + "Set BXM bits for overclocking"), +#endif +#ifdef USE_BLOCKERUPTER + OPT_WITH_ARG("--bet-clk", + opt_set_intval, opt_show_intval, &opt_bet_clk, + "Set Block Erupter clock"), +#endif +#ifdef HAVE_LIBCURL + OPT_WITH_ARG("--btc-address", + opt_set_charp, NULL, &opt_btc_address, + "Set bitcoin target address when solo mining to bitcoind (mandatory)"), + OPT_WITH_ARG("--btc-sig", + opt_set_charp, NULL, &opt_btc_sig, + "Set signature to add to coinbase when solo mining (optional)"), +#endif +#ifdef HAVE_CURSES + OPT_WITHOUT_ARG("--compact", + opt_set_bool, &opt_compact, + "Use compact display without per device statistics"), +#endif +#ifdef USE_COINTERRA + OPT_WITH_ARG("--cta-load", + set_int_0_to_255, opt_show_intval, &opt_cta_load, + "Set load for CTA devices, 0-255 range"), + OPT_WITH_ARG("--ps-load", + set_int_0_to_100, opt_show_intval, &opt_ps_load, + "Set power supply load for CTA devices, 0-100 range"), +#endif + OPT_WITHOUT_ARG("--debug|-D", + enable_debug, &opt_debug, + "Enable debug output"), + OPT_WITHOUT_ARG("--disable-rejecting", + opt_set_bool, &opt_disable_pool, + "Automatically disable pools that continually reject shares"), +#ifdef USE_DRILLBIT + OPT_WITH_ARG("--drillbit-options", + opt_set_charp, NULL, &opt_drillbit_options, + "Set drillbit options :clock[:clock_divider][:voltage]"), + OPT_WITH_ARG("--drillbit-auto", + opt_set_charp, NULL, &opt_drillbit_auto, + "Enable drillbit automatic tuning :[::]"), +#endif + OPT_WITH_ARG("--expiry|-E", + set_int_0_to_9999, opt_show_intval, &opt_expiry, + "Upper bound on how many seconds after getting work we consider a share from it stale"), + OPT_WITHOUT_ARG("--failover-only", + opt_set_bool, &opt_fail_only, + "Don't leak work to backup pools when primary pool is lagging"), + OPT_WITHOUT_ARG("--fix-protocol", + opt_set_bool, &opt_fix_protocol, + "Do not redirect to a different getwork protocol (eg. stratum)"), +#ifdef USE_HASHFAST + OPT_WITHOUT_ARG("--hfa-dfu-boot", + opt_set_bool, &opt_hfa_dfu_boot, + opt_hidden), + OPT_WITH_ARG("--hfa-hash-clock", + set_int_0_to_9999, opt_show_intval, &opt_hfa_hash_clock, + "Set hashfast clock speed"), + OPT_WITH_ARG("--hfa-fail-drop", + set_int_0_to_100, opt_show_intval, &opt_hfa_fail_drop, + "Set how many MHz to drop clockspeed each failure on an overlocked hashfast device"), + OPT_WITH_CBARG("--hfa-fan", + set_hfa_fan, NULL, &opt_set_hfa_fan, + "Set fanspeed percentage for hashfast, single value or range (default: 10-85)"), + OPT_WITH_ARG("--hfa-name", + opt_set_charp, NULL, &opt_hfa_name, + "Set a unique name for a single hashfast device specified with --usb or the first device found"), + OPT_WITHOUT_ARG("--hfa-noshed", + opt_set_bool, &opt_hfa_noshed, + "Disable hashfast dynamic core disabling feature"), + OPT_WITH_ARG("--hfa-ntime-roll", + opt_set_intval, NULL, &opt_hfa_ntime_roll, + opt_hidden), + OPT_WITH_ARG("--hfa-options", + opt_set_charp, NULL, &opt_hfa_options, + "Set hashfast options name:clock (comma separated)"), + OPT_WITHOUT_ARG("--hfa-pll-bypass", + opt_set_bool, &opt_hfa_pll_bypass, + opt_hidden), + OPT_WITH_ARG("--hfa-temp-overheat", + set_int_0_to_200, opt_show_intval, &opt_hfa_overheat, + "Set the hashfast overheat throttling temperature"), + OPT_WITH_ARG("--hfa-temp-target", + set_int_0_to_200, opt_show_intval, &opt_hfa_target, + "Set the hashfast target temperature (0 to disable)"), +#endif +#ifdef USE_HASHRATIO + OPT_WITH_CBARG("--hro-freq", + set_hashratio_freq, NULL, &opt_hashratio_freq, + "Set the hashratio clock frequency"), +#endif + OPT_WITH_ARG("--hotplug", + set_int_0_to_9999, NULL, &hotplug_time, +#ifdef USE_USBUTILS + "Seconds between hotplug checks (0 means never check)" +#else + opt_hidden +#endif + ), +#ifdef USE_ICARUS + OPT_WITH_ARG("--icarus-options", + opt_set_charp, NULL, &opt_icarus_options, + opt_hidden), + OPT_WITH_ARG("--icarus-timing", + opt_set_charp, NULL, &opt_icarus_timing, + opt_hidden), +#endif +#if defined(HAVE_MODMINER) + OPT_WITH_ARG("--kernel-path|-K", + opt_set_charp, opt_show_charp, &opt_kernel_path, + "Specify a path to where bitstream files are"), +#endif +#ifdef USE_KLONDIKE + OPT_WITH_ARG("--klondike-options", + opt_set_charp, NULL, &opt_klondike_options, + "Set klondike options clock:temptarget"), +#endif + OPT_WITHOUT_ARG("--load-balance", + set_loadbalance, &pool_strategy, + "Change multipool strategy from failover to quota based balance"), + OPT_WITH_ARG("--log|-l", + set_int_0_to_9999, opt_show_intval, &opt_log_interval, + "Interval in seconds between log output"), + OPT_WITHOUT_ARG("--lowmem", + opt_set_bool, &opt_lowmem, + "Minimise caching of shares for low memory applications"), +#ifdef USE_MINION + OPT_WITH_ARG("--minion-chipreport", + set_int_0_to_100, opt_show_intval, &opt_minion_chipreport, + "Seconds to report chip 5min hashrate, range 0-100 (default: 0=disabled)"), + OPT_WITH_ARG("--minion-cores", + opt_set_charp, NULL, &opt_minion_cores, + opt_hidden), + OPT_WITHOUT_ARG("--minion-extra", + opt_set_bool, &opt_minion_extra, + opt_hidden), + OPT_WITH_ARG("--minion-freq", + opt_set_charp, NULL, &opt_minion_freq, + "Set minion chip frequencies in MHz, single value or comma list, range 100-1400 (default: 1200)"), + OPT_WITH_ARG("--minion-freqchange", + set_int_0_to_9999, opt_show_intval, &opt_minion_freqchange, + "Millisecond total time to do frequency changes (default: 1000)"), + OPT_WITH_ARG("--minion-freqpercent", + set_int_0_to_100, opt_show_intval, &opt_minion_freqpercent, + "Percentage to use when starting up a chip (default: 70%)"), + OPT_WITHOUT_ARG("--minion-idlecount", + opt_set_bool, &opt_minion_idlecount, + "Report when IdleCount is >0 or changes"), + OPT_WITH_ARG("--minion-ledcount", + set_int_0_to_100, opt_show_intval, &opt_minion_ledcount, + "Turn off led when more than this many chips below the ledlimit (default: 0)"), + OPT_WITH_ARG("--minion-ledlimit", + set_int_0_to_200, opt_show_intval, &opt_minion_ledlimit, + "Turn off led when chips GHs are below this (default: 90)"), + OPT_WITHOUT_ARG("--minion-noautofreq", + opt_set_bool, &opt_minion_noautofreq, + "Disable automatic frequency adjustment"), + OPT_WITHOUT_ARG("--minion-overheat", + opt_set_bool, &opt_minion_overheat, + "Enable directly halting any chip when the status exceeds 100C"), + OPT_WITH_ARG("--minion-spidelay", + set_int_0_to_9999, opt_show_intval, &opt_minion_spidelay, + "Add a delay in microseconds after each SPI I/O"), + OPT_WITH_ARG("--minion-spireset", + opt_set_charp, NULL, &opt_minion_spireset, + "SPI regular reset: iNNN for I/O count or sNNN for seconds - 0 means none"), + OPT_WITH_ARG("--minion-spisleep", + set_int_0_to_9999, opt_show_intval, &opt_minion_spisleep, + "Sleep time in milliseconds when doing an SPI reset"), + OPT_WITH_ARG("--minion-spiusec", + set_int_0_to_9999, NULL, &opt_minion_spiusec, + opt_hidden), + OPT_WITH_ARG("--minion-temp", + opt_set_charp, NULL, &opt_minion_temp, + "Set minion chip temperature threshold, single value or comma list, range 120-160 (default: 135C)"), +#endif +#if defined(unix) || defined(__APPLE__) + OPT_WITH_ARG("--monitor|-m", + opt_set_charp, NULL, &opt_stderr_cmd, + "Use custom pipe cmd for output messages"), +#endif // defined(unix) +#ifdef USE_BITFURY + OPT_WITH_ARG("--nfu-bits", + set_int_32_to_63, opt_show_intval, &opt_nfu_bits, + "Set nanofury bits for overclocking, range 32-63"), +#endif + OPT_WITHOUT_ARG("--net-delay", + opt_set_bool, &opt_delaynet, + "Impose small delays in networking to not overload slow routers"), + OPT_WITHOUT_ARG("--no-pool-disable", + opt_set_invbool, &opt_disable_pool, + opt_hidden), + OPT_WITHOUT_ARG("--no-submit-stale", + opt_set_invbool, &opt_submit_stale, + "Don't submit shares if they are detected as stale"), +#ifdef USE_BITFURY + OPT_WITH_ARG("--osm-led-mode", + set_int_0_to_4, opt_show_intval, &opt_osm_led_mode, + "Set LED mode for OneStringMiner devices"), +#endif + OPT_WITH_ARG("--pass|-p", + set_pass, NULL, &opt_set_null, + "Password for bitcoin JSON-RPC server"), + OPT_WITHOUT_ARG("--per-device-stats", + opt_set_bool, &want_per_device_stats, + "Force verbose mode and output per-device statistics"), + OPT_WITH_ARG("--pools", + opt_set_bool, NULL, &opt_set_null, opt_hidden), + OPT_WITHOUT_ARG("--protocol-dump|-P", + opt_set_bool, &opt_protocol, + "Verbose dump of protocol-level activities"), + OPT_WITH_ARG("--queue|-Q", + set_int_0_to_9999, opt_show_intval, &opt_queue, + "Maximum number of work items to have queued"), + OPT_WITHOUT_ARG("--quiet|-q", + opt_set_bool, &opt_quiet, + "Disable logging output, display status and errors"), + OPT_WITH_ARG("--quota|-U", + set_quota, NULL, &opt_set_null, + "quota;URL combination for server with load-balance strategy quotas"), + OPT_WITHOUT_ARG("--real-quiet", + opt_set_bool, &opt_realquiet, + "Disable all output"), + OPT_WITH_ARG("--retries", + set_null, NULL, &opt_set_null, + opt_hidden), + OPT_WITH_ARG("--retry-pause", + set_null, NULL, &opt_set_null, + opt_hidden), +#ifdef USE_ICARUS + OPT_WITH_ARG("--rock-freq", + set_float_125_to_500, &opt_show_floatval, &opt_rock_freq, + "Set RockMiner frequency in MHz, range 125-500"), +#endif + OPT_WITH_ARG("--rotate", + set_rotate, NULL, &opt_set_null, + "Change multipool strategy from failover to regularly rotate at N minutes"), + OPT_WITHOUT_ARG("--round-robin", + set_rr, &pool_strategy, + "Change multipool strategy from failover to round robin on failure"), +#ifdef USE_FPGA_SERIAL + OPT_WITH_CBARG("--scan-serial|-S", + add_serial, NULL, &opt_add_serial, + "Serial port to probe for Serial FPGA Mining device"), +#endif + OPT_WITH_ARG("--scan-time|-s", + set_int_0_to_9999, opt_show_intval, &opt_scantime, + "Upper bound on time spent scanning current work, in seconds"), + OPT_WITH_CBARG("--sched-start", + set_sched_start, NULL, &opt_set_sched_start, + "Set a time of day in HH:MM to start mining (a once off without a stop time)"), + OPT_WITH_CBARG("--sched-stop", + set_sched_stop, NULL, &opt_set_sched_stop, + "Set a time of day in HH:MM to stop mining (will quit without a start time)"), + OPT_WITH_CBARG("--sharelog", + set_sharelog, NULL, &opt_set_sharelog, + "Append share log to file"), + OPT_WITH_ARG("--shares", + opt_set_intval, NULL, &opt_shares, + "Quit after mining N shares (default: unlimited)"), + OPT_WITH_ARG("--socks-proxy", + opt_set_charp, NULL, &opt_socks_proxy, + "Set socks4 proxy (host:port)"), + OPT_WITH_ARG("--suggest-diff", + opt_set_intval, NULL, &opt_suggest_diff, + "Suggest miner difficulty for pool to user (default: none)"), + OPT_WITH_ARG("--multi-version", + opt_set_intval, NULL, &opt_multi_version, + "Multi version"), +#ifdef HAVE_SYSLOG_H + OPT_WITHOUT_ARG("--syslog", + opt_set_bool, &use_syslog, + "Use system log for output messages (default: standard error)"), +#endif +#if defined(USE_BITFORCE) || defined(USE_MODMINER) || defined(USE_BFLSC) + OPT_WITH_CBARG("--temp-cutoff", + set_temp_cutoff, opt_show_intval, &opt_set_temp_cutoff, + "Temperature where a device will be automatically disabled, one value or comma separated list"), +#endif + OPT_WITHOUT_ARG("--text-only|-T", + opt_set_invbool, &use_curses, +#ifdef HAVE_CURSES + "Disable ncurses formatted screen output" +#else + opt_hidden +#endif + ), + OPT_WITH_ARG("--url|-o", + set_url, NULL, &opt_set_null, + "URL for bitcoin JSON-RPC server"), +#ifdef USE_USBUTILS + OPT_WITH_ARG("--usb", + opt_set_charp, NULL, &opt_usb_select, + "USB device selection"), + OPT_WITH_ARG("--usb-dump", + set_int_0_to_10, opt_show_intval, &opt_usbdump, + opt_hidden), + OPT_WITHOUT_ARG("--usb-list-all", + opt_set_bool, &opt_usb_list_all, + opt_hidden), +#endif + OPT_WITH_ARG("--user|-u", + set_user, NULL, &opt_set_null, + "Username for bitcoin JSON-RPC server"), + OPT_WITH_ARG("--userpass|-O", + set_userpass, NULL, &opt_set_null, + "Username:Password pair for bitcoin JSON-RPC server"), + OPT_WITHOUT_ARG("--verbose", + opt_set_bool, &opt_log_output, + "Log verbose output to stderr as well as status output"), + OPT_WITHOUT_ARG("--widescreen", + opt_set_bool, &opt_widescreen, + "Use extra wide display without toggling"), + OPT_WITHOUT_ARG("--worktime", + opt_set_bool, &opt_worktime, + "Display extra work time debug information"), + OPT_ENDTABLE +}; + +static char *load_config(const char *arg, void __maybe_unused *unused); + +static int fileconf_load; + +static char *parse_config(json_t *config, bool fileconf) +{ + static char err_buf[200]; + struct opt_table *opt; + const char *str; + json_t *val; + + if (fileconf && !fileconf_load) + fileconf_load = 1; + + for (opt = opt_config_table; opt->type != OPT_END; opt++) + { + char *p, *name; + + /* We don't handle subtables. */ + assert(!(opt->type & OPT_SUBTABLE)); + + if (!opt->names) + continue; + + /* Pull apart the option name(s). */ + name = strdup(opt->names); + for (p = strtok(name, "|"); p; p = strtok(NULL, "|")) + { + char *err = NULL; + + /* Ignore short options. */ + if (p[1] != '-') + continue; + + val = json_object_get(config, p+2); + if (!val) + continue; + + if ((opt->type & (OPT_HASARG | OPT_PROCESSARG)) && json_is_string(val)) + { + str = json_string_value(val); + err = opt->cb_arg(str, opt->u.arg); + if (opt->type == OPT_PROCESSARG) + opt_set_charp(str, opt->u.arg); + } + else if ((opt->type & (OPT_HASARG | OPT_PROCESSARG)) && json_is_array(val)) + { + json_t *arr_val; + size_t index; + + json_array_foreach(val, index, arr_val) + { + if (json_is_string(arr_val)) + { + str = json_string_value(arr_val); + err = opt->cb_arg(str, opt->u.arg); + if (opt->type == OPT_PROCESSARG) + opt_set_charp(str, opt->u.arg); + } + else if (json_is_object(arr_val)) + err = parse_config(arr_val, false); + if (err) + break; + } + } + else if ((opt->type & OPT_NOARG) && json_is_true(val)) + err = opt->cb(opt->u.arg); + else + err = "Invalid value"; + + if (err) + { + /* Allow invalid values to be in configuration + * file, just skipping over them provided the + * JSON is still valid after that. */ + if (fileconf) + { + applog(LOG_ERR, "Invalid config option %s: %s", p, err); + fileconf_load = -1; + } + else + { + snprintf(err_buf, sizeof(err_buf), "Parsing JSON option %s: %s", + p, err); + return err_buf; + } + } + } + free(name); + } + + val = json_object_get(config, JSON_INCLUDE_CONF); + if (val && json_is_string(val)) + return load_config(json_string_value(val), NULL); + + return NULL; +} + +char *cnfbuf = NULL; + +#ifdef HAVE_LIBCURL +char conf_web1[] = "http://"; +char conf_web2[] = "https://"; + +static char *load_web_config(const char *arg) +{ + json_t *val = json_web_config(arg); + + if (!val || !json_is_object(val)) + return JSON_WEB_ERROR; + + if (!cnfbuf) + cnfbuf = strdup(arg); + + config_loaded = true; + + return parse_config(val, true); +} +#endif + +static char *load_config(const char *arg, void __maybe_unused *unused) +{ + json_error_t err; + json_t *config; + char *json_error; + size_t siz; + +#ifdef HAVE_LIBCURL + if (strncasecmp(arg, conf_web1, sizeof(conf_web1)-1) == 0 || + strncasecmp(arg, conf_web2, sizeof(conf_web2)-1) == 0) + return load_web_config(arg); +#endif + + if (!cnfbuf) + cnfbuf = strdup(arg); + + if (++include_count > JSON_MAX_DEPTH) + return JSON_MAX_DEPTH_ERR; + + config = json_load_file(arg, 0, &err); + if (!json_is_object(config)) + { + siz = JSON_LOAD_ERROR_LEN + strlen(arg) + strlen(err.text); + json_error = malloc(siz); + if (!json_error) + quit(1, "Malloc failure in json error"); + + snprintf(json_error, siz, JSON_LOAD_ERROR, arg, err.text); + return json_error; + } + + config_loaded = true; + + /* Parse the config now, so we can override it. That can keep pointers + * so don't free config object. */ + return parse_config(config, true); +} + +static char *set_default_config(const char *arg) +{ + opt_set_charp(arg, &default_config); + + return NULL; +} + +void default_save_file(char *filename); + +static void load_default_config(void) +{ + cnfbuf = malloc(PATH_MAX); + + default_save_file(cnfbuf); + + if (!access(cnfbuf, R_OK)) + load_config(cnfbuf, NULL); + else + { + free(cnfbuf); + cnfbuf = NULL; + } +} + +extern const char *opt_argv0; + +static char *opt_verusage_and_exit(const char *extra) +{ + printf("%s\nBuilt with " +#ifdef USE_BMSC + "bmsc " +#endif +#ifdef USE_BITMAIN + "bitmain " +#endif +#ifdef USE_BITMAIN_C5 + "bitmain_c5 " +#endif +#ifdef USE_AVALON + "avalon " +#endif +#ifdef USE_AVALON2 + "avalon2 " +#endif +#ifdef USE_AVALON4 + "avalon4 " +#endif +#ifdef USE_BFLSC + "bflsc " +#endif +#ifdef USE_BITFORCE + "bitforce " +#endif +#ifdef USE_BITFURY + "bitfury " +#endif +#ifdef USE_COINTERRA + "cointerra " +#endif +#ifdef USE_DRILLBIT + "drillbit " +#endif +#ifdef USE_HASHFAST + "hashfast " +#endif +#ifdef USE_ICARUS + "icarus " +#endif +#ifdef USE_KLONDIKE + "klondike " +#endif +#ifdef USE_KNC + "KnC " +#endif +#ifdef USE_BAB + "BaB " +#endif +#ifdef USE_MINION + "minion " +#endif +#ifdef USE_MODMINER + "modminer " +#endif +#ifdef USE_BITMINE_A1 + "Bitmine.A1 " +#endif +#ifdef USE_SP10 + "spondoolies " +#endif +#ifdef USE_SP30 + "sp30 " +#endif + + "mining support.\n" + , packagename); + printf("%s", opt_usage(opt_argv0, extra)); + fflush(stdout); + exit(0); +} + +#if defined(USE_USBUTILS) +char *display_devs(int *ndevs) +{ + *ndevs = 0; + usb_all(0); + exit(*ndevs); +} +#endif + +/* These options are available from commandline only */ +static struct opt_table opt_cmdline_table[] = +{ + OPT_WITH_ARG("--config|-c", + load_config, NULL, &opt_set_null, + "Load a JSON-format configuration file\n" + "See example.conf for an example configuration."), + OPT_WITH_ARG("--default-config", + set_default_config, NULL, &opt_set_null, + "Specify the filename of the default config file\n" + "Loaded at start and used when saving without a name."), + OPT_WITHOUT_ARG("--help|-h", + opt_verusage_and_exit, NULL, + "Print this message"), +#if defined(USE_USBUTILS) + OPT_WITHOUT_ARG("--ndevs|-n", + display_devs, &nDevs, + "Display all USB devices and exit"), +#endif + OPT_WITHOUT_ARG("--version|-V", + opt_version_and_exit, packagename, + "Display version and exit"), + OPT_ENDTABLE +}; + +#ifdef HAVE_LIBCURL +static bool jobj_binary(const json_t *obj, const char *key, + void *buf, size_t buflen, bool required) +{ + const char *hexstr; + json_t *tmp; + + tmp = json_object_get(obj, key); + if (unlikely(!tmp)) + { + if (unlikely(required)) + applog(LOG_ERR, "JSON key '%s' not found", key); + return false; + } + hexstr = json_string_value(tmp); + if (unlikely(!hexstr)) + { + applog(LOG_ERR, "JSON key '%s' is not a string", key); + return false; + } + if (!hex2bin(buf, hexstr, buflen)) + return false; + + return true; +} +#endif + +static void calc_midstate(struct work *work) +{ + unsigned char data[64]; + uint32_t *data32 = (uint32_t *)data; + sha256_ctx ctx; + + flip64(data32, work->data); + sha256_init(&ctx); + sha256_update(&ctx, data, 64); + memcpy(work->midstate, ctx.h, 32); + endian_flip32(work->midstate, work->midstate); +} + +/* Returns the current value of total_work and increments it */ +static int total_work_inc(void) +{ + int ret; + + cg_wlock(&control_lock); + ret = total_work++; + cg_wunlock(&control_lock); + + return ret; +} + +static struct work *make_work(void) +{ + struct work *work = calloc(1, sizeof(struct work)); + + if (unlikely(!work)) + quit(1, "Failed to calloc work in make_work"); + + work->id = total_work_inc(); + + return work; +} + +/* This is the central place all work that is about to be retired should be + * cleaned to remove any dynamically allocated arrays within the struct */ +void clean_work(struct work *work) +{ + free(work->job_id); + free(work->ntime); + free(work->coinbase); + free(work->nonce1); + memset(work, 0, sizeof(struct work)); +} + +/* All dynamically allocated work structs should be freed here to not leak any + * ram from arrays allocated within the work struct */ +void _free_work(struct work *work) +{ + clean_work(work); + free(work); +} + +static void gen_hash(unsigned char *data, unsigned char *hash, int len); +static void calc_diff(struct work *work, double known); +char *workpadding = "000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000"; + +#ifdef HAVE_LIBCURL +/* Process transactions with GBT by storing the binary value of the first + * transaction, and the hashes of the remaining transactions since these + * remain constant with an altered coinbase when generating work. Must be + * entered under gbt_lock */ +static void gbt_merkle_bins(struct pool *pool, json_t *transaction_arr); + +static void __build_gbt_txns(struct pool *pool, json_t *res_val) +{ + json_t *txn_array; + + txn_array = json_object_get(res_val, "transactions"); + gbt_merkle_bins(pool, txn_array); +} + +static void __gbt_merkleroot(struct pool *pool, unsigned char *merkle_root) +{ + unsigned char merkle_sha[64]; + int i; + + gen_hash(pool->coinbase, merkle_root, pool->coinbase_len); + memcpy(merkle_sha, merkle_root, 32); + for (i = 0; i < pool->merkles; i++) + { + memcpy(merkle_sha + 32, pool->merklebin + i * 32, 32); + gen_hash(merkle_sha, merkle_root, 64); + memcpy(merkle_sha, merkle_root, 32); + } +} + +static bool work_decode(struct pool *pool, struct work *work, json_t *val); + +static void update_gbt(struct pool *pool) +{ + int rolltime; + json_t *val; + CURL *curl; + + curl = curl_easy_init(); + if (unlikely(!curl)) + quit (1, "CURL initialisation failed in update_gbt"); + + val = json_rpc_call(curl, pool->rpc_url, pool->rpc_userpass, + pool->rpc_req, true, false, &rolltime, pool, false); + + if (val) + { + struct work *work = make_work(); + bool rc = work_decode(pool, work, val); + + total_getworks++; + pool->getwork_requested++; + if (rc) + { + applog(LOG_DEBUG, "Successfully retrieved and updated GBT from pool %u %s", + pool->pool_no, pool->rpc_url); + if (pool == current_pool()) + opt_work_update = true; + } + else + { + applog(LOG_DEBUG, "Successfully retrieved but FAILED to decipher GBT from pool %u %s", + pool->pool_no, pool->rpc_url); + } + json_decref(val); + free_work(work); + } + else + { + applog(LOG_DEBUG, "FAILED to update GBT from pool %u %s", + pool->pool_no, pool->rpc_url); + } + curl_easy_cleanup(curl); +} + +static void gen_gbt_work(struct pool *pool, struct work *work) +{ + unsigned char merkleroot[32]; + struct timeval now; + uint64_t nonce2le; + + cgtime(&now); + if (now.tv_sec - pool->tv_lastwork.tv_sec > 60) + update_gbt(pool); + + cg_wlock(&pool->gbt_lock); + nonce2le = htole64(pool->nonce2); + memcpy(pool->coinbase + pool->nonce2_offset, &nonce2le, pool->n2size); + pool->nonce2++; + cg_dwlock(&pool->gbt_lock); + __gbt_merkleroot(pool, merkleroot); + + memcpy(work->data, &pool->gbt_version, 4); + memcpy(work->data + 4, pool->previousblockhash, 32); + memcpy(work->data + 4 + 32 + 32, &pool->curtime, 4); + memcpy(work->data + 4 + 32 + 32 + 4, &pool->gbt_bits, 4); + + memcpy(work->target, pool->gbt_target, 32); + + work->coinbase = bin2hex(pool->coinbase, pool->coinbase_len); + + /* For encoding the block data on submission */ + work->gbt_txns = pool->gbt_txns + 1; + + if (pool->gbt_workid) + work->job_id = strdup(pool->gbt_workid); + cg_runlock(&pool->gbt_lock); + + flip32(work->data + 4 + 32, merkleroot); + memset(work->data + 4 + 32 + 32 + 4 + 4, 0, 4); /* nonce */ + + hex2bin(work->data + 4 + 32 + 32 + 4 + 4 + 4, workpadding, 48); + + if (opt_debug) + { + char *header = bin2hex(work->data, 128); + + applog(LOG_DEBUG, "Generated GBT header %s", header); + applog(LOG_DEBUG, "Work coinbase %s", work->coinbase); + free(header); + } + + calc_midstate(work); + local_work++; + work->pool = pool; + work->gbt = true; + work->longpoll = false; + work->getwork_mode = GETWORK_MODE_GBT; + work->work_block = work_block; + /* Nominally allow a driver to ntime roll 60 seconds */ + work->drv_rolllimit = 60; + calc_diff(work, 0); + cgtime(&work->tv_staged); +} + +static bool gbt_decode(struct pool *pool, json_t *res_val) +{ + const char *previousblockhash; + const char *target; + const char *coinbasetxn; + const char *longpollid; + unsigned char hash_swap[32]; + int expires; + int version; + int curtime; + bool submitold; + const char *bits; + const char *workid; + int cbt_len, orig_len; + uint8_t *extra_len; + size_t cal_len; + + previousblockhash = json_string_value(json_object_get(res_val, "previousblockhash")); + target = json_string_value(json_object_get(res_val, "target")); + coinbasetxn = json_string_value(json_object_get(json_object_get(res_val, "coinbasetxn"), "data")); + longpollid = json_string_value(json_object_get(res_val, "longpollid")); + expires = json_integer_value(json_object_get(res_val, "expires")); + version = json_integer_value(json_object_get(res_val, "version")); + curtime = json_integer_value(json_object_get(res_val, "curtime")); + submitold = json_is_true(json_object_get(res_val, "submitold")); + bits = json_string_value(json_object_get(res_val, "bits")); + workid = json_string_value(json_object_get(res_val, "workid")); + + if (!previousblockhash || !target || !coinbasetxn || !longpollid || + !expires || !version || !curtime || !bits) + { + applog(LOG_ERR, "JSON failed to decode GBT"); + return false; + } + + applog(LOG_DEBUG, "previousblockhash: %s", previousblockhash); + applog(LOG_DEBUG, "target: %s", target); + applog(LOG_DEBUG, "coinbasetxn: %s", coinbasetxn); + applog(LOG_DEBUG, "longpollid: %s", longpollid); + applog(LOG_DEBUG, "expires: %d", expires); + applog(LOG_DEBUG, "version: %d", version); + applog(LOG_DEBUG, "curtime: %d", curtime); + applog(LOG_DEBUG, "submitold: %s", submitold ? "true" : "false"); + applog(LOG_DEBUG, "bits: %s", bits); + if (workid) + applog(LOG_DEBUG, "workid: %s", workid); + + cg_wlock(&pool->gbt_lock); + free(pool->coinbasetxn); + pool->coinbasetxn = strdup(coinbasetxn); + cbt_len = strlen(pool->coinbasetxn) / 2; + /* We add 8 bytes of extra data corresponding to nonce2 */ + pool->n2size = 8; + pool->coinbase_len = cbt_len + pool->n2size; + cal_len = pool->coinbase_len + 1; + align_len(&cal_len); + free(pool->coinbase); + pool->coinbase = calloc(cal_len, 1); + if (unlikely(!pool->coinbase)) + quit(1, "Failed to calloc pool coinbase in gbt_decode"); + hex2bin(pool->coinbase, pool->coinbasetxn, 42); + extra_len = (uint8_t *)(pool->coinbase + 41); + orig_len = *extra_len; + hex2bin(pool->coinbase + 42, pool->coinbasetxn + 84, orig_len); + *extra_len += pool->n2size; + hex2bin(pool->coinbase + 42 + *extra_len, pool->coinbasetxn + 84 + (orig_len * 2), + cbt_len - orig_len - 42); + pool->nonce2_offset = orig_len + 42; + + free(pool->longpollid); + pool->longpollid = strdup(longpollid); + free(pool->gbt_workid); + if (workid) + pool->gbt_workid = strdup(workid); + else + pool->gbt_workid = NULL; + + hex2bin(hash_swap, previousblockhash, 32); + swap256(pool->previousblockhash, hash_swap); + + hex2bin(hash_swap, target, 32); + swab256(pool->gbt_target, hash_swap); + + pool->gbt_expires = expires; + pool->gbt_version = htobe32(version); + pool->curtime = htobe32(curtime); + pool->submit_old = submitold; + + hex2bin((unsigned char *)&pool->gbt_bits, bits, 4); + + __build_gbt_txns(pool, res_val); + cg_wunlock(&pool->gbt_lock); + + return true; +} + +static bool getwork_decode(json_t *res_val, struct work *work) +{ + if (unlikely(!jobj_binary(res_val, "data", work->data, sizeof(work->data), true))) + { + applog(LOG_ERR, "JSON inval data"); + return false; + } + + if (!jobj_binary(res_val, "midstate", work->midstate, sizeof(work->midstate), false)) + { + // Calculate it ourselves + applog(LOG_DEBUG, "Calculating midstate locally"); + calc_midstate(work); + } + + if (unlikely(!jobj_binary(res_val, "target", work->target, sizeof(work->target), true))) + { + applog(LOG_ERR, "JSON inval target"); + return false; + } + return true; +} + +/* Returns whether the pool supports local work generation or not. */ +static bool pool_localgen(struct pool *pool) +{ + return (pool->has_stratum || pool->has_gbt || pool->gbt_solo); +} + +static void gbt_merkle_bins(struct pool *pool, json_t *transaction_arr) +{ + unsigned char *hashbin; + json_t *arr_val; + int i, j, binleft, binlen; + + free(pool->txn_data); + pool->txn_data = NULL; + pool->transactions = 0; + pool->merkles = 0; + pool->transactions = json_array_size(transaction_arr); + binlen = pool->transactions * 32 + 32; + hashbin = alloca(binlen + 32); + memset(hashbin, 0, 32); + binleft = binlen / 32; + if (pool->transactions) + { + int len = 0, ofs = 0; + const char *txn; + + for (i = 0; i < pool->transactions; i++) + { + arr_val = json_array_get(transaction_arr, i); + txn = json_string_value(json_object_get(arr_val, "data")); + if (!txn) + { + applog(LOG_ERR, "Pool %d json_string_value fail - cannot find transaction data", + pool->pool_no); + return; + } + len += strlen(txn); + } + + pool->txn_data = malloc(len + 1); + if (unlikely(!pool->txn_data)) + quit(1, "Failed to calloc txn_data in gbt_merkle_bins"); + pool->txn_data[len] = '\0'; + + for (i = 0; i < pool->transactions; i++) + { + unsigned char binswap[32]; + const char *hash; + + arr_val = json_array_get(transaction_arr, i); + hash = json_string_value(json_object_get(arr_val, "hash")); + txn = json_string_value(json_object_get(arr_val, "data")); + len = strlen(txn); + memcpy(pool->txn_data + ofs, txn, len); + ofs += len; + if (!hash) + { + unsigned char *txn_bin; + int txn_len; + + txn_len = len / 2; + txn_bin = malloc(txn_len); + if (!txn_bin) + quit(1, "Failed to malloc txn_bin in gbt_merkle_bins"); + hex2bin(txn_bin, txn, txn_len); + /* This is needed for pooled mining since only + * transaction data and not hashes are sent */ + gen_hash(txn_bin, hashbin + 32 + 32 * i, txn_len); + continue; + } + if (!hex2bin(binswap, hash, 32)) + { + applog(LOG_ERR, "Failed to hex2bin hash in gbt_merkle_bins"); + return; + } + swab256(hashbin + 32 + 32 * i, binswap); + } + } + if (binleft > 1) + { + while (42) + { + if (binleft == 1) + break; + memcpy(pool->merklebin + (pool->merkles * 32), hashbin + 32, 32); + pool->merkles++; + if (binleft % 2) + { + memcpy(hashbin + binlen, hashbin + binlen - 32, 32); + binlen += 32; + binleft++; + } + for (i = 32, j = 64; j < binlen; i += 32, j += 64) + { + gen_hash(hashbin + j, hashbin + i, 64); + } + binleft /= 2; + binlen = binleft * 32; + } + } + if (opt_debug) + { + char hashhex[68]; + + for (i = 0; i < pool->merkles; i++) + { + __bin2hex(hashhex, pool->merklebin + i * 32, 32); + applog(LOG_DEBUG, "MH%d %s",i, hashhex); + } + } + applog(LOG_INFO, "Stored %d transactions from pool %d", pool->transactions, + pool->pool_no); +} + +static double diff_from_target(void *target); + +static const char scriptsig_header[] = "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff"; +static unsigned char scriptsig_header_bin[41]; + +static bool gbt_solo_decode(struct pool *pool, json_t *res_val) +{ + json_t *transaction_arr, *coinbase_aux; + const char *previousblockhash; + unsigned char hash_swap[32]; + struct timeval now; + const char *target; + uint64_t coinbasevalue; + const char *flags; + const char *bits; + char header[228]; + int ofs = 0, len; + uint64_t *u64; + uint32_t *u32; + int version; + int curtime; + int height; + + previousblockhash = json_string_value(json_object_get(res_val, "previousblockhash")); + target = json_string_value(json_object_get(res_val, "target")); + transaction_arr = json_object_get(res_val, "transactions"); + version = json_integer_value(json_object_get(res_val, "version")); + curtime = json_integer_value(json_object_get(res_val, "curtime")); + bits = json_string_value(json_object_get(res_val, "bits")); + height = json_integer_value(json_object_get(res_val, "height")); + coinbasevalue = json_integer_value(json_object_get(res_val, "coinbasevalue")); + coinbase_aux = json_object_get(res_val, "coinbaseaux"); + flags = json_string_value(json_object_get(coinbase_aux, "flags")); + + if (!previousblockhash || !target || !version || !curtime || !bits || !coinbase_aux || !flags) + { + applog(LOG_ERR, "Pool %d JSON failed to decode GBT", pool->pool_no); + return false; + } + + applog(LOG_DEBUG, "previousblockhash: %s", previousblockhash); + applog(LOG_DEBUG, "target: %s", target); + applog(LOG_DEBUG, "version: %d", version); + applog(LOG_DEBUG, "curtime: %d", curtime); + applog(LOG_DEBUG, "bits: %s", bits); + applog(LOG_DEBUG, "height: %d", height); + applog(LOG_DEBUG, "flags: %s", flags); + + cg_wlock(&pool->gbt_lock); + hex2bin(hash_swap, previousblockhash, 32); + swap256(pool->previousblockhash, hash_swap); + __bin2hex(pool->prev_hash, pool->previousblockhash, 32); + + hex2bin(hash_swap, target, 32); + swab256(pool->gbt_target, hash_swap); + pool->sdiff = diff_from_target(pool->gbt_target); + + pool->gbt_version = htobe32(version); + pool->curtime = htobe32(curtime); + snprintf(pool->ntime, 9, "%08x", curtime); + snprintf(pool->bbversion, 9, "%08x", version); + snprintf(pool->nbit, 9, "%s", bits); + pool->nValue = coinbasevalue; + hex2bin((unsigned char *)&pool->gbt_bits, bits, 4); + gbt_merkle_bins(pool, transaction_arr); + pool->height = height; + + memset(pool->scriptsig_base, 0, 42); + ofs++; // Leave room for template length + + /* Put block height at start of template. */ + ofs += ser_number(pool->scriptsig_base + ofs, height); // max 5 + + /* Followed by flags */ + len = strlen(flags) / 2; + pool->scriptsig_base[ofs++] = len; + hex2bin(pool->scriptsig_base + ofs, flags, len); + ofs += len; + + /* Followed by timestamp */ + cgtime(&now); + pool->scriptsig_base[ofs++] = 0xfe; // Encode seconds as u32 + u32 = (uint32_t *)&pool->scriptsig_base[ofs]; + *u32 = htole32(now.tv_sec); + ofs += 4; // sizeof uint32_t + pool->scriptsig_base[ofs++] = 0xfe; // Encode usecs as u32 + u32 = (uint32_t *)&pool->scriptsig_base[ofs]; + *u32 = htole32(now.tv_usec); + ofs += 4; // sizeof uint32_t + + memcpy(pool->scriptsig_base + ofs, "\x09\x63\x67\x6d\x69\x6e\x65\x72\x34\x32", 10); + ofs += 10; + + /* Followed by extranonce size, fixed at 8 */ + pool->scriptsig_base[ofs++] = 8; + pool->nonce2_offset = 41 + ofs; + ofs += 8; + + if (opt_btc_sig) + { + len = strlen(opt_btc_sig); + if (len > 32) + len = 32; + pool->scriptsig_base[ofs++] = len; + memcpy(pool->scriptsig_base + ofs, opt_btc_sig, len); + ofs += len; + } + + pool->scriptsig_base[0] = ofs++; // Template length + pool->n1_len = ofs; + + len = 41 // prefix + + ofs // Template length + + 4 // txin sequence no + + 1 // transactions + + 8 // value + + 1 + 25 // txout + + 4; // lock + free(pool->coinbase); + pool->coinbase = calloc(len, 1); + if (unlikely(!pool->coinbase)) + quit(1, "Failed to calloc coinbase in gbt_solo_decode"); + + memcpy(pool->coinbase + 41, pool->scriptsig_base, ofs); + memcpy(pool->coinbase + 41 + ofs, "\xff\xff\xff\xff", 4); + pool->coinbase[41 + ofs + 4] = 1; + u64 = (uint64_t *)&(pool->coinbase[41 + ofs + 4 + 1]); + *u64 = htole64(coinbasevalue); + + pool->nonce2 = 0; + pool->n2size = 4; + pool->coinbase_len = 41 + ofs + 4 + 1 + 8 + 1 + 25 + 4; + cg_wunlock(&pool->gbt_lock); + + snprintf(header, 225, "%s%s%s%s%s%s%s", + pool->bbversion, + pool->prev_hash, + "0000000000000000000000000000000000000000000000000000000000000000", + pool->ntime, + pool->nbit, + "00000000", /* nonce */ + workpadding); + if (unlikely(!hex2bin(pool->header_bin, header, 112))) + quit(1, "Failed to hex2bin header in gbt_solo_decode"); + + return true; +} + +static bool work_decode(struct pool *pool, struct work *work, json_t *val) +{ + json_t *res_val = json_object_get(val, "result"); + bool ret = false; + + cgtime(&pool->tv_lastwork); + if (!res_val || json_is_null(res_val)) + { + applog(LOG_ERR, "JSON Failed to decode result"); + goto out; + } + + if (pool->gbt_solo) + { + if (unlikely(!gbt_solo_decode(pool, res_val))) + goto out; + ret = true; + goto out; + } + else if (pool->has_gbt) + { + if (unlikely(!gbt_decode(pool, res_val))) + goto out; + work->gbt = true; + ret = true; + goto out; + } + else if (unlikely(!getwork_decode(res_val, work))) + goto out; + + memset(work->hash, 0, sizeof(work->hash)); + + cgtime(&work->tv_staged); + + ret = true; + +out: + return ret; +} +#else /* HAVE_LIBCURL */ +/* Always true with stratum */ +#define pool_localgen(pool) (true) +#define json_rpc_call(curl, url, userpass, rpc_req, probe, longpoll, rolltime, pool, share) (NULL) +#define work_decode(pool, work, val) (false) +#define gen_gbt_work(pool, work) {} +#endif /* HAVE_LIBCURL */ + +int dev_from_id(int thr_id) +{ + struct cgpu_info *cgpu = get_thr_cgpu(thr_id); + + return cgpu->device_id; +} + +/* Create an exponentially decaying average over the opt_log_interval */ +void decay_time(double *f, double fadd, double fsecs, double interval) +{ + double ftotal, fprop; + + if (fsecs <= 0) + return; + fprop = 1.0 - 1 / (exp(fsecs / interval)); + ftotal = 1.0 + fprop; + *f += (fadd / fsecs * fprop); + *f /= ftotal; +} + +static int __total_staged(void) +{ + return HASH_COUNT(staged_work); +} + +static int total_staged(void) +{ + int ret; + + mutex_lock(stgd_lock); + ret = __total_staged(); + mutex_unlock(stgd_lock); + + return ret; +} + +#ifdef HAVE_CURSES +WINDOW *mainwin, *statuswin, *logwin; +#endif +double total_secs = 1.0; +double last_total_secs = 1.0; +static char statusline[256]; +/* logstart is where the log window should start */ +static int devcursor, logstart, logcursor; +#ifdef HAVE_CURSES +/* statusy is where the status window goes up to in cases where it won't fit at startup */ +static int statusy; +#endif + +#ifdef HAVE_CURSES +static inline void unlock_curses(void) +{ + mutex_unlock(&console_lock); +} + +static inline void lock_curses(void) +{ + mutex_lock(&console_lock); +} + +static bool curses_active_locked(void) +{ + bool ret; + + lock_curses(); + ret = curses_active; + if (!ret) + unlock_curses(); + return ret; +} +#endif + +/* Convert a uint64_t value into a truncated string for displaying with its + * associated suitable for Mega, Giga etc. Buf array needs to be long enough */ +static void suffix_string(uint64_t val, char *buf, size_t bufsiz, int sigdigits) +{ + const double dkilo = 1000.0; + const uint64_t kilo = 1000ull; + const uint64_t mega = 1000000ull; + const uint64_t giga = 1000000000ull; + const uint64_t tera = 1000000000000ull; + const uint64_t peta = 1000000000000000ull; + const uint64_t exa = 1000000000000000000ull; + char suffix[2] = ""; + bool decimal = true; + double dval; + + if (val >= exa) + { + val /= peta; + dval = (double)val / dkilo; + strcpy(suffix, "E"); + } + else if (val >= peta) + { + val /= tera; + dval = (double)val / dkilo; + strcpy(suffix, "P"); + } + else if (val >= tera) + { + val /= giga; + dval = (double)val / dkilo; + strcpy(suffix, "T"); + } + else if (val >= giga) + { + val /= mega; + dval = (double)val / dkilo; + strcpy(suffix, "G"); + } + else if (val >= mega) + { + val /= kilo; + dval = (double)val / dkilo; + strcpy(suffix, "M"); + } + else if (val >= kilo) + { + dval = (double)val / dkilo; + strcpy(suffix, "K"); + } + else + { + dval = val; + decimal = false; + } + + if (!sigdigits) + { + if (decimal) + snprintf(buf, bufsiz, "%.3g%s", dval, suffix); + else + snprintf(buf, bufsiz, "%d%s", (unsigned int)dval, suffix); + } + else + { + /* Always show sigdigits + 1, padded on right with zeroes + * followed by suffix */ + int ndigits = sigdigits - 1 - (dval > 0.0 ? floor(log10(dval)) : 0); + snprintf(buf, bufsiz, "%*.*f%s", sigdigits + 1, ndigits, dval, suffix); + + } +} + +double cgpu_runtime(struct cgpu_info *cgpu) +{ + struct timeval now; + double dev_runtime; + + if (cgpu->dev_start_tv.tv_sec == 0) + dev_runtime = total_secs; + else + { + cgtime(&now); + dev_runtime = tdiff(&now, &(cgpu->dev_start_tv)); + } + + if (dev_runtime < 1.0) + dev_runtime = 1.0; + return dev_runtime; +} + +double tsince_restart(void) +{ + struct timeval now; + + cgtime(&now); + return tdiff(&now, &restart_tv_start); +} + +double tsince_update(void) +{ + struct timeval now; + + cgtime(&now); + return tdiff(&now, &update_tv_start); +} + +static void get_statline(char *buf, size_t bufsiz, struct cgpu_info *cgpu) +{ + char displayed_hashes[16], displayed_rolling[16]; + double dev_runtime, wu; + uint64_t dh64, dr64; + + dev_runtime = cgpu_runtime(cgpu); + + wu = cgpu->diff1 / dev_runtime * 60.0; + + dh64 = (double)cgpu->total_mhashes / dev_runtime * 1000000ull; + dr64 = (double)cgpu->rolling * 1000000ull; + suffix_string(dh64, displayed_hashes, sizeof(displayed_hashes), 4); + suffix_string(dr64, displayed_rolling, sizeof(displayed_rolling), 4); + + snprintf(buf, bufsiz, "%s%d ", cgpu->drv->name, cgpu->device_id); + cgpu->drv->get_statline_before(buf, bufsiz, cgpu); + tailsprintf(buf, bufsiz, "(%ds):%s (avg):%sh/s | A:%.0f R:%.0f HW:%d WU:%.1f/m", + opt_log_interval, + displayed_rolling, + displayed_hashes, + cgpu->diff_accepted, + cgpu->diff_rejected, + cgpu->hw_errors, + wu); + cgpu->drv->get_statline(buf, bufsiz, cgpu); +} + +static bool shared_strategy(void) +{ + return (pool_strategy == POOL_LOADBALANCE || pool_strategy == POOL_BALANCE); +} + +#ifdef HAVE_CURSES +#define CURBUFSIZ 256 +#define cg_mvwprintw(win, y, x, fmt, ...) do { \ + char tmp42[CURBUFSIZ]; \ + snprintf(tmp42, sizeof(tmp42), fmt, ##__VA_ARGS__); \ + mvwprintw(win, y, x, "%s", tmp42); \ +} while (0) +#define cg_wprintw(win, fmt, ...) do { \ + char tmp42[CURBUFSIZ]; \ + snprintf(tmp42, sizeof(tmp42), fmt, ##__VA_ARGS__); \ + wprintw(win, "%s", tmp42); \ +} while (0) + +/* Must be called with curses mutex lock held and curses_active */ +static void curses_print_status(void) +{ + struct pool *pool = current_pool(); + int linewidth = opt_widescreen ? 100 : 80; + + wattron(statuswin, A_BOLD); + cg_mvwprintw(statuswin, 0, 0, " " PACKAGE " version " VERSION " - Started: %s", datestamp); + wattroff(statuswin, A_BOLD); + mvwhline(statuswin, 1, 0, '-', linewidth); + cg_mvwprintw(statuswin, 2, 0, " %s", statusline); + wclrtoeol(statuswin); + if (opt_widescreen) + { + cg_mvwprintw(statuswin, 3, 0, " A:%.0f R:%.0f HW:%d WU:%.1f/m |" + " ST: %d SS: %"PRId64" NB: %d LW: %d GF: %d RF: %d", + total_diff_accepted, total_diff_rejected, hw_errors, + total_diff1 / total_secs * 60, + total_staged(), total_stale, new_blocks, local_work, total_go, total_ro); + } + else if (alt_status) + { + cg_mvwprintw(statuswin, 3, 0, " ST: %d SS: %"PRId64" NB: %d LW: %d GF: %d RF: %d", + total_staged(), total_stale, new_blocks, local_work, total_go, total_ro); + } + else + { + cg_mvwprintw(statuswin, 3, 0, " A:%.0f R:%.0f HW:%d WU:%.1f/m", + total_diff_accepted, total_diff_rejected, hw_errors, + total_diff1 / total_secs * 60); + } + wclrtoeol(statuswin); + if (shared_strategy() && total_pools > 1) + { + cg_mvwprintw(statuswin, 4, 0, " Connected to multiple pools with%s block change notify", + have_longpoll ? "": "out"); + } + else if (pool->has_stratum) + { + cg_mvwprintw(statuswin, 4, 0, " Connected to %s diff %s with stratum as user %s", + pool->sockaddr_url, pool->diff, pool->rpc_user); + } + else + { + cg_mvwprintw(statuswin, 4, 0, " Connected to %s diff %s with%s %s as user %s", + pool->sockaddr_url, pool->diff, have_longpoll ? "": "out", + pool->has_gbt ? "GBT" : "LP", pool->rpc_user); + } + wclrtoeol(statuswin); + cg_mvwprintw(statuswin, 5, 0, " Block: %s... Diff:%s Started: %s Best share: %s ", + prev_block, block_diff, blocktime, best_share); + mvwhline(statuswin, 6, 0, '-', linewidth); + mvwhline(statuswin, statusy - 1, 0, '-', linewidth); +#ifdef USE_USBUTILS + cg_mvwprintw(statuswin, devcursor - 1, 1, "[U]SB management [P]ool management [S]ettings [D]isplay options [Q]uit"); +#else + cg_mvwprintw(statuswin, devcursor - 1, 1, "[P]ool management [S]ettings [D]isplay options [Q]uit"); +#endif +} + +static void adj_width(int var, int *length) +{ + if ((int)(log10(var) + 1) > *length) + (*length)++; +} + +static void adj_fwidth(float var, int *length) +{ + if ((int)(log10(var) + 1) > *length) + (*length)++; +} + +#define STATBEFORELEN 23 +const char blanks[] = " "; + +static void curses_print_devstatus(struct cgpu_info *cgpu, int devno, int count) +{ + static int devno_width = 1, dawidth = 1, drwidth = 1, hwwidth = 1, wuwidth = 1; + char logline[256], unique_id[12]; + struct timeval now; + double dev_runtime, wu; + unsigned int devstatlen; + + if (opt_compact) + return; + + if (devcursor + count > LINES - 2) + return; + + if (count >= most_devices) + return; + + if (cgpu->dev_start_tv.tv_sec == 0) + dev_runtime = total_secs; + else + { + cgtime(&now); + dev_runtime = tdiff(&now, &(cgpu->dev_start_tv)); + } + + if (dev_runtime < 1.0) + dev_runtime = 1.0; + + cgpu->utility = cgpu->accepted / dev_runtime * 60; + wu = cgpu->diff1 / dev_runtime * 60; + + wmove(statuswin,devcursor + count, 0); + adj_width(devno, &devno_width); + if (cgpu->unique_id) + { + unique_id[8] = '\0'; + memcpy(unique_id, blanks, 8); + strncpy(unique_id, cgpu->unique_id, 8); + } + else + sprintf(unique_id, "%-8d", cgpu->device_id); + cg_wprintw(statuswin, " %*d: %s %-8s: ", devno_width, devno, cgpu->drv->name, + unique_id); + logline[0] = '\0'; + cgpu->drv->get_statline_before(logline, sizeof(logline), cgpu); + devstatlen = strlen(logline); + if (devstatlen < STATBEFORELEN) + strncat(logline, blanks, STATBEFORELEN - devstatlen); + cg_wprintw(statuswin, "%s | ", logline); + + +#ifdef USE_USBUTILS + if (cgpu->usbinfo.nodev) + cg_wprintw(statuswin, "ZOMBIE"); + else +#endif + if (cgpu->status == LIFE_DEAD) + cg_wprintw(statuswin, "DEAD "); + else if (cgpu->status == LIFE_SICK) + cg_wprintw(statuswin, "SICK "); + else if (cgpu->deven == DEV_DISABLED) + cg_wprintw(statuswin, "OFF "); + else if (cgpu->deven == DEV_RECOVER) + cg_wprintw(statuswin, "REST "); + else if (opt_widescreen) + { + char displayed_hashes[16], displayed_rolling[16]; + uint64_t d64; + + d64 = (double)cgpu->total_mhashes / dev_runtime * 1000000ull; + suffix_string(d64, displayed_hashes, sizeof(displayed_hashes), 4); + d64 = (double)cgpu->rolling * 1000000ull; + suffix_string(d64, displayed_rolling, sizeof(displayed_rolling), 4); + adj_width(wu, &wuwidth); + adj_fwidth(cgpu->diff_accepted, &dawidth); + adj_fwidth(cgpu->diff_rejected, &drwidth); + adj_width(cgpu->hw_errors, &hwwidth); + cg_wprintw(statuswin, "%6s / %6sh/s WU:%*.1f/m " + "A:%*.0f R:%*.0f HW:%*d", + displayed_rolling, + displayed_hashes, wuwidth + 2, wu, + dawidth, cgpu->diff_accepted, + drwidth, cgpu->diff_rejected, + hwwidth, cgpu->hw_errors); + } + else if (!alt_status) + { + char displayed_hashes[16], displayed_rolling[16]; + uint64_t d64; + + d64 = (double)cgpu->total_mhashes / dev_runtime * 1000000ull; + suffix_string(d64, displayed_hashes, sizeof(displayed_hashes), 4); + d64 = (double)cgpu->rolling * 1000000ull; + suffix_string(d64, displayed_rolling, sizeof(displayed_rolling), 4); + adj_width(wu, &wuwidth); + cg_wprintw(statuswin, "%6s / %6sh/s WU:%*.1f/m", displayed_rolling, + displayed_hashes, wuwidth + 2, wu); + } + else + { + adj_fwidth(cgpu->diff_accepted, &dawidth); + adj_fwidth(cgpu->diff_rejected, &drwidth); + adj_width(cgpu->hw_errors, &hwwidth); + cg_wprintw(statuswin, "A:%*.0f R:%*.0f HW:%*d", + dawidth, cgpu->diff_accepted, + drwidth, cgpu->diff_rejected, + hwwidth, cgpu->hw_errors); + } + + logline[0] = '\0'; + cgpu->drv->get_statline(logline, sizeof(logline), cgpu); + cg_wprintw(statuswin, "%s", logline); + + wclrtoeol(statuswin); +} +#endif + +#ifdef HAVE_CURSES +/* Check for window resize. Called with curses mutex locked */ +static inline void change_logwinsize(void) +{ + int x, y, logx, logy; + + getmaxyx(mainwin, y, x); + if (x < 80 || y < 25) + return; + + if (y > statusy + 2 && statusy < logstart) + { + if (y - 2 < logstart) + statusy = y - 2; + else + statusy = logstart; + logcursor = statusy + 1; + mvwin(logwin, logcursor, 0); + wresize(statuswin, statusy, x); + } + + y -= logcursor; + getmaxyx(logwin, logy, logx); + /* Detect screen size change */ + if (x != logx || y != logy) + wresize(logwin, y, x); +} + +static void check_winsizes(void) +{ + if (!use_curses) + return; + if (curses_active_locked()) + { + int y, x; + + erase(); + x = getmaxx(statuswin); + if (logstart > LINES - 2) + statusy = LINES - 2; + else + statusy = logstart; + logcursor = statusy; + wresize(statuswin, statusy, x); + getmaxyx(mainwin, y, x); + y -= logcursor; + wresize(logwin, y, x); + mvwin(logwin, logcursor, 0); + unlock_curses(); + } +} + +static void disable_curses_windows(void); +static void enable_curses_windows(void); + +static void switch_logsize(bool __maybe_unused newdevs) +{ + if (curses_active_locked()) + { +#ifdef WIN32 + if (newdevs) + disable_curses_windows(); +#endif + if (opt_compact) + { + logstart = devcursor + 1; + logcursor = logstart + 1; + } + else + { + logstart = devcursor + most_devices + 1; + logcursor = logstart + 1; + } +#ifdef WIN32 + if (newdevs) + enable_curses_windows(); +#endif + unlock_curses(); + check_winsizes(); + } +} + +/* For mandatory printing when mutex is already locked */ +void _wlog(const char *str) +{ + wprintw(logwin, "%s", str); +} + +/* Mandatory printing */ +void _wlogprint(const char *str) +{ + if (curses_active_locked()) + { + wprintw(logwin, "%s", str); + unlock_curses(); + } +} +#endif + +#ifdef HAVE_CURSES +bool log_curses_only(int prio, const char *datetime, const char *str) +{ + bool high_prio; + + high_prio = (prio == LOG_WARNING || prio == LOG_ERR); + + if (curses_active_locked()) + { + if (!opt_loginput || high_prio) + { + wprintw(logwin, "%s%s\n", datetime, str); + if (high_prio) + { + touchwin(logwin); + wrefresh(logwin); + } + } + unlock_curses(); + return true; + } + return false; +} + +void clear_logwin(void) +{ + if (curses_active_locked()) + { + erase(); + wclear(logwin); + unlock_curses(); + } +} + +void logwin_update(void) +{ + if (curses_active_locked()) + { + touchwin(logwin); + wrefresh(logwin); + unlock_curses(); + } +} +#endif + +static void enable_pool(struct pool *pool) +{ + if (pool->enabled != POOL_ENABLED) + { + enabled_pools++; + pool->enabled = POOL_ENABLED; + } +} + +#ifdef HAVE_CURSES +static void disable_pool(struct pool *pool) +{ + if (pool->enabled == POOL_ENABLED) + enabled_pools--; + pool->enabled = POOL_DISABLED; +} +#endif + +static void reject_pool(struct pool *pool) +{ + if (pool->enabled == POOL_ENABLED) + enabled_pools--; + pool->enabled = POOL_REJECTING; +} + +static void restart_threads(void); + +/* Theoretically threads could race when modifying accepted and + * rejected values but the chance of two submits completing at the + * same time is zero so there is no point adding extra locking */ +static void +share_result(json_t *val, json_t *res, json_t *err, const struct work *work, + char *hashshow, bool resubmit, char *worktime) +{ + struct pool *pool = work->pool; + struct cgpu_info *cgpu; + + cgpu = get_thr_cgpu(work->thr_id); + + if (json_is_true(res) || (work->gbt && json_is_null(res))) + { + mutex_lock(&stats_lock); + cgpu->accepted++; + total_accepted++; + pool->accepted++; + cgpu->diff_accepted += work->work_difficulty; + total_diff_accepted += work->work_difficulty; + pool->diff_accepted += work->work_difficulty; + mutex_unlock(&stats_lock); + + pool->seq_rejects = 0; + cgpu->last_share_pool = pool->pool_no; + cgpu->last_share_pool_time = time(NULL); + cgpu->last_share_diff = work->work_difficulty; + pool->last_share_time = cgpu->last_share_pool_time; + pool->last_share_diff = work->work_difficulty; + applog(LOG_DEBUG, "PROOF OF WORK RESULT: true (yay!!!)"); + if (!QUIET) + { + if (total_pools > 1) + applog(LOG_NOTICE, "Accepted %s %s %d pool %d %s%s", + hashshow, cgpu->drv->name, cgpu->device_id, work->pool->pool_no, resubmit ? "(resubmit)" : "", worktime); + else + applog(LOG_NOTICE, "Accepted %s %s %d %s%s", + hashshow, cgpu->drv->name, cgpu->device_id, resubmit ? "(resubmit)" : "", worktime); + } + sharelog("accept", work); + if (opt_shares && total_diff_accepted >= opt_shares) + { + applog(LOG_WARNING, "Successfully mined %d accepted shares as requested and exiting.", opt_shares); + kill_work(); + return; + } + + /* Detect if a pool that has been temporarily disabled for + * continually rejecting shares has started accepting shares. + * This will only happen with the work returned from a + * longpoll */ + if (unlikely(pool->enabled == POOL_REJECTING)) + { + applog(LOG_WARNING, "Rejecting pool %d now accepting shares, re-enabling!", pool->pool_no); + enable_pool(pool); + switch_pools(NULL); + } + /* If we know we found the block we know better than anyone + * that new work is needed. */ + if (unlikely(work->block)) + restart_threads(); + } + else + { + mutex_lock(&stats_lock); + cgpu->rejected++; + total_rejected++; + pool->rejected++; + cgpu->diff_rejected += work->work_difficulty; + total_diff_rejected += work->work_difficulty; + pool->diff_rejected += work->work_difficulty; + pool->seq_rejects++; + mutex_unlock(&stats_lock); + + applog(LOG_DEBUG, "PROOF OF WORK RESULT: false (booooo)"); + if (!QUIET) + { + char where[20]; + char disposition[36] = "reject"; + char reason[32]; + + strcpy(reason, ""); + if (total_pools > 1) + snprintf(where, sizeof(where), "pool %d", work->pool->pool_no); + else + strcpy(where, ""); + + if (!work->gbt) + res = json_object_get(val, "reject-reason"); + if (res) + { + const char *reasontmp = json_string_value(res); + + size_t reasonLen = strlen(reasontmp); + if (reasonLen > 28) + reasonLen = 28; + reason[0] = ' '; + reason[1] = '('; + memcpy(2 + reason, reasontmp, reasonLen); + reason[reasonLen + 2] = ')'; + reason[reasonLen + 3] = '\0'; + memcpy(disposition + 7, reasontmp, reasonLen); + disposition[6] = ':'; + disposition[reasonLen + 7] = '\0'; + } + else if (work->stratum && err) + { + if (json_is_array(err)) + { + json_t *reason_val = json_array_get(err, 1); + char *reason_str; + + if (reason_val && json_is_string(reason_val)) + { + reason_str = (char *)json_string_value(reason_val); + snprintf(reason, 31, " (%s)", reason_str); + } + } + else if (json_is_string(err)) + { + const char *s = json_string_value(err); + snprintf(reason, 31, " (%s)", s); + } + } + + applog(LOG_NOTICE, "Rejected %s %s %d %s%s %s%s", + hashshow, cgpu->drv->name, cgpu->device_id, where, reason, resubmit ? "(resubmit)" : "", worktime); + sharelog(disposition, work); + } + + /* Once we have more than a nominal amount of sequential rejects, + * at least 10 and more than 3 mins at the current utility, + * disable the pool because some pool error is likely to have + * ensued. Do not do this if we know the share just happened to + * be stale due to networking delays. + */ + if (pool->seq_rejects > 10 && !work->stale && opt_disable_pool && enabled_pools > 1) + { + double utility = total_accepted / total_secs * 60; + + if (pool->seq_rejects > utility * 3 && enabled_pools > 1) + { + applog(LOG_WARNING, "Pool %d rejected %d sequential shares, disabling!", + pool->pool_no, pool->seq_rejects); + reject_pool(pool); + if (pool == current_pool()) + switch_pools(NULL); + pool->seq_rejects = 0; + } + } + } +} + +static void show_hash(struct work *work, char *hashshow) +{ + unsigned char rhash[32]; + char diffdisp[16]; + unsigned long h32; + uint32_t *hash32; + uint64_t uintdiff; + int ofs; + + swab256(rhash, work->hash); + for (ofs = 0; ofs <= 28; ofs ++) + { + if (rhash[ofs]) + break; + } + hash32 = (uint32_t *)(rhash + ofs); + h32 = be32toh(*hash32); + uintdiff = round(work->work_difficulty); + suffix_string(work->share_diff, diffdisp, sizeof (diffdisp), 0); + snprintf(hashshow, 64, "%08lx Diff %s/%"PRIu64"%s", h32, diffdisp, uintdiff, + work->block? " BLOCK!" : ""); +} + +#ifdef HAVE_LIBCURL +static void text_print_status(int thr_id) +{ + struct cgpu_info *cgpu; + char logline[256]; + + cgpu = get_thr_cgpu(thr_id); + if (cgpu) + { + get_statline(logline, sizeof(logline), cgpu); + printf("%s\n", logline); + } +} + +static void print_status(int thr_id) +{ + if (!curses_active) + text_print_status(thr_id); +} + +static bool submit_upstream_work(struct work *work, CURL *curl, bool resubmit) +{ + json_t *val, *res, *err; + char *s; + bool rc = false; + int thr_id = work->thr_id; + struct cgpu_info *cgpu; + struct pool *pool = work->pool; + int rolltime; + struct timeval tv_submit, tv_submit_reply; + char hashshow[64 + 4] = ""; + char worktime[200] = ""; + struct timeval now; + double dev_runtime; + + cgpu = get_thr_cgpu(thr_id); + + /* build JSON-RPC request */ + if (work->gbt) + { + char gbt_block[1024], varint[12]; + unsigned char data[80]; + + flip80(data, work->data); + __bin2hex(gbt_block, data, 80); // 160 length + + if (work->gbt_txns < 0xfd) + { + uint8_t val8 = work->gbt_txns; + + __bin2hex(varint, (const unsigned char *)&val8, 1); + } + else if (work->gbt_txns <= 0xffff) + { + uint16_t val16 = htole16(work->gbt_txns); + + strcat(gbt_block, "fd"); // +2 + __bin2hex(varint, (const unsigned char *)&val16, 2); + } + else + { + uint32_t val32 = htole32(work->gbt_txns); + + strcat(gbt_block, "fe"); // +2 + __bin2hex(varint, (const unsigned char *)&val32, 4); + } + strcat(gbt_block, varint); // +8 max + strcat(gbt_block, work->coinbase); + + s = malloc(1024); + if (unlikely(!s)) + quit(1, "Failed to malloc s in submit_upstream_work"); + sprintf(s, "{\"id\": 0, \"method\": \"submitblock\", \"params\": [\"%s", gbt_block); + /* Has submit/coinbase support */ + if (!pool->has_gbt) + { + cg_rlock(&pool->gbt_lock); + if (pool->txn_data) + s = realloc_strcat(s, pool->txn_data); + cg_runlock(&pool->gbt_lock); + } + if (work->job_id) + { + s = realloc_strcat(s, "\", {\"workid\": \""); + s = realloc_strcat(s, work->job_id); + s = realloc_strcat(s, "\"}]}"); + } + else + s = realloc_strcat(s, "\"]}"); + } + else + { + char *hexstr; + + endian_flip128(work->data, work->data); + + /* build hex string */ + hexstr = bin2hex(work->data, 118); + s = strdup("{\"method\": \"getwork\", \"params\": [ \""); + s = realloc_strcat(s, hexstr); + s = realloc_strcat(s, "\" ], \"id\":1}"); + free(hexstr); + } + applog(LOG_DEBUG, "DBG: sending %s submit RPC call: %s", pool->rpc_url, s); + s = realloc_strcat(s, "\n"); + + cgtime(&tv_submit); + /* issue JSON-RPC request */ + val = json_rpc_call(curl, pool->rpc_url, pool->rpc_userpass, s, false, false, &rolltime, pool, true); + cgtime(&tv_submit_reply); + free(s); + + if (unlikely(!val)) + { + applog(LOG_INFO, "submit_upstream_work json_rpc_call failed"); + if (!pool_tset(pool, &pool->submit_fail)) + { + total_ro++; + pool->remotefail_occasions++; + if (opt_lowmem) + { + applog(LOG_WARNING, "Pool %d communication failure, discarding shares", pool->pool_no); + goto out; + } + applog(LOG_WARNING, "Pool %d communication failure, caching submissions", pool->pool_no); + } + cgsleep_ms(5000); + goto out; + } + else if (pool_tclear(pool, &pool->submit_fail)) + applog(LOG_WARNING, "Pool %d communication resumed, submitting work", pool->pool_no); + + res = json_object_get(val, "result"); + err = json_object_get(val, "error"); + + if (!QUIET) + { + show_hash(work, hashshow); + + if (opt_worktime) + { + char workclone[20]; + struct tm *tm, tm_getwork, tm_submit_reply; + double getwork_time = tdiff((struct timeval *)&(work->tv_getwork_reply), + (struct timeval *)&(work->tv_getwork)); + double getwork_to_work = tdiff((struct timeval *)&(work->tv_work_start), + (struct timeval *)&(work->tv_getwork_reply)); + double work_time = tdiff((struct timeval *)&(work->tv_work_found), + (struct timeval *)&(work->tv_work_start)); + double work_to_submit = tdiff(&tv_submit, + (struct timeval *)&(work->tv_work_found)); + double submit_time = tdiff(&tv_submit_reply, &tv_submit); + int diffplaces = 3; + + time_t tmp_time = work->tv_getwork.tv_sec; + tm = localtime(&tmp_time); + memcpy(&tm_getwork, tm, sizeof(struct tm)); + tmp_time = tv_submit_reply.tv_sec; + tm = localtime(&tmp_time); + memcpy(&tm_submit_reply, tm, sizeof(struct tm)); + + if (work->clone) + { + snprintf(workclone, sizeof(workclone), "C:%1.3f", + tdiff((struct timeval *)&(work->tv_cloned), + (struct timeval *)&(work->tv_getwork_reply))); + } + else + strcpy(workclone, "O"); + + if (work->work_difficulty < 1) + diffplaces = 6; + + snprintf(worktime, sizeof(worktime), + " <-%08lx.%08lx M:%c D:%1.*f G:%02d:%02d:%02d:%1.3f %s (%1.3f) W:%1.3f (%1.3f) S:%1.3f R:%02d:%02d:%02d", + (unsigned long)be32toh(*(uint32_t *)&(work->data[28])), + (unsigned long)be32toh(*(uint32_t *)&(work->data[24])), + work->getwork_mode, diffplaces, work->work_difficulty, + tm_getwork.tm_hour, tm_getwork.tm_min, + tm_getwork.tm_sec, getwork_time, workclone, + getwork_to_work, work_time, work_to_submit, submit_time, + tm_submit_reply.tm_hour, tm_submit_reply.tm_min, + tm_submit_reply.tm_sec); + } + } + + share_result(val, res, err, work, hashshow, resubmit, worktime); + + if (cgpu->dev_start_tv.tv_sec == 0) + dev_runtime = total_secs; + else + { + cgtime(&now); + dev_runtime = tdiff(&now, &(cgpu->dev_start_tv)); + } + + if (dev_runtime < 1.0) + dev_runtime = 1.0; + + cgpu->utility = cgpu->accepted / dev_runtime * 60; + + if (!opt_realquiet) + print_status(thr_id); + if (!want_per_device_stats) + { + char logline[256]; + + get_statline(logline, sizeof(logline), cgpu); + applog(LOG_INFO, "%s", logline); + } + + json_decref(val); + + rc = true; +out: + return rc; +} + +static bool get_upstream_work(struct work *work, CURL *curl) +{ + struct pool *pool = work->pool; + struct cgminer_pool_stats *pool_stats = &(pool->cgminer_pool_stats); + struct timeval tv_elapsed; + json_t *val = NULL; + bool rc = false; + char *url; + + url = pool->rpc_url; + + applog(LOG_DEBUG, "DBG: sending %s get RPC call: %s", url, pool->rpc_req); + + cgtime(&work->tv_getwork); + + val = json_rpc_call(curl, url, pool->rpc_userpass, pool->rpc_req, false, + false, &work->rolltime, pool, false); + pool_stats->getwork_attempts++; + + if (likely(val)) + { + rc = work_decode(pool, work, val); + if (unlikely(!rc)) + applog(LOG_DEBUG, "Failed to decode work in get_upstream_work"); + } + else + applog(LOG_DEBUG, "Failed json_rpc_call in get_upstream_work"); + + cgtime(&work->tv_getwork_reply); + timersub(&(work->tv_getwork_reply), &(work->tv_getwork), &tv_elapsed); + pool_stats->getwork_wait_rolling += ((double)tv_elapsed.tv_sec + ((double)tv_elapsed.tv_usec / 1000000)) * 0.63; + pool_stats->getwork_wait_rolling /= 1.63; + + timeradd(&tv_elapsed, &(pool_stats->getwork_wait), &(pool_stats->getwork_wait)); + if (timercmp(&tv_elapsed, &(pool_stats->getwork_wait_max), >)) + { + pool_stats->getwork_wait_max.tv_sec = tv_elapsed.tv_sec; + pool_stats->getwork_wait_max.tv_usec = tv_elapsed.tv_usec; + } + if (timercmp(&tv_elapsed, &(pool_stats->getwork_wait_min), <)) + { + pool_stats->getwork_wait_min.tv_sec = tv_elapsed.tv_sec; + pool_stats->getwork_wait_min.tv_usec = tv_elapsed.tv_usec; + } + pool_stats->getwork_calls++; + + work->pool = pool; + work->longpoll = false; + work->getwork_mode = GETWORK_MODE_POOL; + calc_diff(work, 0); + total_getworks++; + pool->getwork_requested++; + + if (likely(val)) + json_decref(val); + + return rc; +} +#endif /* HAVE_LIBCURL */ + +/* Specifies whether we can use this pool for work or not. */ +static bool pool_unworkable(struct pool *pool) +{ + if (pool->idle) + return true; + if (pool->enabled != POOL_ENABLED) + return true; + if (pool->has_stratum && !pool->stratum_active) + return true; + return false; +} + +/* In balanced mode, the amount of diff1 solutions per pool is monitored as a + * rolling average per 10 minutes and if pools start getting more, it biases + * away from them to distribute work evenly. The share count is reset to the + * rolling average every 10 minutes to not send all work to one pool after it + * has been disabled/out for an extended period. */ +static struct pool *select_balanced(struct pool *cp) +{ + int i, lowest = cp->shares; + struct pool *ret = cp; + + for (i = 0; i < total_pools; i++) + { + struct pool *pool = pools[i]; + + if (pool_unworkable(pool)) + continue; + if (pool->shares < lowest) + { + lowest = pool->shares; + ret = pool; + } + } + + ret->shares++; + return ret; +} + +static struct pool *priority_pool(int choice); +static bool pool_unusable(struct pool *pool); + +/* Select any active pool in a rotating fashion when loadbalance is chosen if + * it has any quota left. */ +static inline struct pool *select_pool(bool lagging) +{ + static int rotating_pool = 0; + struct pool *pool, *cp; + bool avail = false; + int tested, i; + + cp = current_pool(); + + if (pool_strategy == POOL_BALANCE) + { + pool = select_balanced(cp); + goto out; + } + + if (pool_strategy != POOL_LOADBALANCE && (!lagging || opt_fail_only)) + { + pool = cp; + goto out; + } + else + pool = NULL; + + for (i = 0; i < total_pools; i++) + { + struct pool *tp = pools[i]; + + if (tp->quota_used < tp->quota_gcd) + { + avail = true; + break; + } + } + + /* There are no pools with quota, so reset them. */ + if (!avail) + { + for (i = 0; i < total_pools; i++) + pools[i]->quota_used = 0; + if (++rotating_pool >= total_pools) + rotating_pool = 0; + } + + /* Try to find the first pool in the rotation that is usable */ + tested = 0; + while (!pool && tested++ < total_pools) + { + pool = pools[rotating_pool]; + if (pool->quota_used++ < pool->quota_gcd) + { + if (!pool_unworkable(pool)) + break; + /* Failover-only flag for load-balance means distribute + * unused quota to priority pool 0. */ + if (opt_fail_only) + priority_pool(0)->quota_used--; + } + pool = NULL; + if (++rotating_pool >= total_pools) + rotating_pool = 0; + } + + /* If there are no alive pools with quota, choose according to + * priority. */ + if (!pool) + { + for (i = 0; i < total_pools; i++) + { + struct pool *tp = priority_pool(i); + + if (!pool_unusable(tp)) + { + pool = tp; + break; + } + } + } + + /* If still nothing is usable, use the current pool */ + if (!pool) + pool = cp; +out: + applog(LOG_DEBUG, "Selecting pool %d for work", pool->pool_no); + return pool; +} + +/* truediffone == 0x00000000FFFF0000000000000000000000000000000000000000000000000000 + * Generate a 256 bit binary LE target by cutting up diff into 64 bit sized + * portions or vice versa. */ +static const double truediffone = 26959535291011309493156476344723991336010898738574164086137773096960.0; +static const double bits192 = 6277101735386680763835789423207666416102355444464034512896.0; +static const double bits128 = 340282366920938463463374607431768211456.0; +static const double bits64 = 18446744073709551616.0; + +/* Converts a little endian 256 bit value to a double */ +static double le256todouble(const void *target) +{ + uint64_t *data64; + double dcut64; + + data64 = (uint64_t *)(target + 24); + dcut64 = le64toh(*data64) * bits192; + + data64 = (uint64_t *)(target + 16); + dcut64 += le64toh(*data64) * bits128; + + data64 = (uint64_t *)(target + 8); + dcut64 += le64toh(*data64) * bits64; + + data64 = (uint64_t *)(target); + dcut64 += le64toh(*data64); + + return dcut64; +} + +static double diff_from_target(void *target) +{ + double d64, dcut64; + + d64 = truediffone; + dcut64 = le256todouble(target); + if (unlikely(!dcut64)) + dcut64 = 1; + return d64 / dcut64; +} + +/* + * Calculate the work->work_difficulty based on the work->target + */ +static void calc_diff(struct work *work, double known) +{ + struct cgminer_pool_stats *pool_stats = &(work->pool->cgminer_pool_stats); + double difficulty; + uint64_t uintdiff; + + if (known) + work->work_difficulty = known; + else + work->work_difficulty = diff_from_target(work->target); + + difficulty = work->work_difficulty; + + pool_stats->last_diff = difficulty; + uintdiff = round(difficulty); + suffix_string(uintdiff, work->pool->diff, sizeof(work->pool->diff), 0); + + if (difficulty == pool_stats->min_diff) + pool_stats->min_diff_count++; + else if (difficulty < pool_stats->min_diff || pool_stats->min_diff == 0) + { + pool_stats->min_diff = difficulty; + pool_stats->min_diff_count = 1; + } + + if (difficulty == pool_stats->max_diff) + pool_stats->max_diff_count++; + else if (difficulty > pool_stats->max_diff) + { + pool_stats->max_diff = difficulty; + pool_stats->max_diff_count = 1; + } +} + +static unsigned char bench_hidiff_bins[16][160]; +static unsigned char bench_lodiff_bins[16][160]; +static unsigned char bench_target[32]; + +/* Iterate over the lo and hi diff benchmark work items such that we find one + * diff 32+ share every 32 work items. */ +static void get_benchmark_work(struct work *work) +{ + work->work_difficulty = 32; + memcpy(work->target, bench_target, 32); + work->drv_rolllimit = 0; + work->mandatory = true; + work->pool = pools[0]; + cgtime(&work->tv_getwork); + copy_time(&work->tv_getwork_reply, &work->tv_getwork); + work->getwork_mode = GETWORK_MODE_BENCHMARK; +} + +static void benchfile_dspwork(struct work *work, uint32_t nonce) +{ + char buf[1024]; + uint32_t dn; + int i; + + dn = 0; + for (i = 0; i < 4; i++) + { + dn *= 0x100; + dn += nonce & 0xff; + nonce /= 0x100; + } + + if ((sizeof(work->data) * 2 + 1) > sizeof(buf)) + quithere(1, "BENCHFILE Invalid buf size"); + + __bin2hex(buf, work->data, sizeof(work->data)); + + applog(LOG_ERR, "BENCHFILE nonce %u=0x%08x for work=%s", + (unsigned int)dn, (unsigned int)dn, buf); + +} + +static bool benchfile_get_work(struct work *work) +{ + char buf[1024]; + char item[1024]; + bool got = false; + + if (!benchfile_in) + { + if (opt_benchfile) + benchfile_in = fopen(opt_benchfile, "r"); + else + quit(1, "BENCHFILE Invalid benchfile NULL"); + + if (!benchfile_in) + quit(1, "BENCHFILE Failed to open benchfile '%s'", opt_benchfile); + + benchfile_line = 0; + + if (!fgets(buf, 1024, benchfile_in)) + quit(1, "BENCHFILE Failed to read benchfile '%s'", opt_benchfile); + + got = true; + benchfile_work = 0; + } + + if (!got) + { + if (!fgets(buf, 1024, benchfile_in)) + { + if (benchfile_work == 0) + quit(1, "BENCHFILE No work in benchfile '%s'", opt_benchfile); + fclose(benchfile_in); + benchfile_in = NULL; + return benchfile_get_work(work); + } + } + + do + { + benchfile_line++; + + // Empty lines and lines starting with '#' or '/' are ignored + if (*buf != '\0' && *buf != '#' && *buf != '/') + { + char *commas[BENCHWORK_COUNT]; + int i, j, len; + long nonce_time; + + commas[0] = buf; + for (i = 1; i < BENCHWORK_COUNT; i++) + { + commas[i] = strchr(commas[i-1], ','); + if (!commas[i]) + { + quit(1, "BENCHFILE Invalid input file line %d" + " - field count is %d but should be %d", + benchfile_line, i, BENCHWORK_COUNT); + } + len = commas[i] - commas[i-1]; + if (benchfile_data[i-1].length && + (len != benchfile_data[i-1].length)) + { + quit(1, "BENCHFILE Invalid input file line %d " + "field %d (%s) length is %d but should be %d", + benchfile_line, i, + benchfile_data[i-1].name, + len, benchfile_data[i-1].length); + } + + *(commas[i]++) = '\0'; + } + + // NonceTime may have LF's etc + len = strlen(commas[BENCHWORK_NONCETIME]); + if (len < benchfile_data[BENCHWORK_NONCETIME].length) + { + quit(1, "BENCHFILE Invalid input file line %d field %d" + " (%s) length is %d but should be least %d", + benchfile_line, BENCHWORK_NONCETIME+1, + benchfile_data[BENCHWORK_NONCETIME].name, len, + benchfile_data[BENCHWORK_NONCETIME].length); + } + + sprintf(item, "0000000%c", commas[BENCHWORK_VERSION][0]); + + j = strlen(item); + for (i = benchfile_data[BENCHWORK_PREVHASH].length-8; i >= 0; i -= 8) + { + sprintf(&(item[j]), "%.8s", &commas[BENCHWORK_PREVHASH][i]); + j += 8; + } + + for (i = benchfile_data[BENCHWORK_MERKLEROOT].length-8; i >= 0; i -= 8) + { + sprintf(&(item[j]), "%.8s", &commas[BENCHWORK_MERKLEROOT][i]); + j += 8; + } + + nonce_time = atol(commas[BENCHWORK_NONCETIME]); + + sprintf(&(item[j]), "%08lx", nonce_time); + j += 8; + + strcpy(&(item[j]), commas[BENCHWORK_DIFFBITS]); + j += benchfile_data[BENCHWORK_DIFFBITS].length; + + memset(work, 0, sizeof(*work)); + + hex2bin(work->data, item, j >> 1); + + calc_midstate(work); + + benchfile_work++; + + return true; + } + } + while (fgets(buf, 1024, benchfile_in)); + + if (benchfile_work == 0) + quit(1, "BENCHFILE No work in benchfile '%s'", opt_benchfile); + fclose(benchfile_in); + benchfile_in = NULL; + return benchfile_get_work(work); +} + +static void get_benchfile_work(struct work *work) +{ + benchfile_get_work(work); + work->mandatory = true; + work->pool = pools[0]; + cgtime(&work->tv_getwork); + copy_time(&work->tv_getwork_reply, &work->tv_getwork); + work->getwork_mode = GETWORK_MODE_BENCHMARK; + calc_diff(work, 0); +} + +#ifdef HAVE_CURSES +static void disable_curses_windows(void) +{ + leaveok(logwin, false); + leaveok(statuswin, false); + leaveok(mainwin, false); + nocbreak(); + echo(); + delwin(logwin); + delwin(statuswin); +} + +/* Force locking of curses console_lock on shutdown since a dead thread might + * have grabbed the lock. */ +static bool curses_active_forcelocked(void) +{ + bool ret; + + mutex_trylock(&console_lock); + ret = curses_active; + if (!ret) + unlock_curses(); + return ret; +} + +static void disable_curses(void) +{ + if (curses_active_forcelocked()) + { + use_curses = false; + curses_active = false; + disable_curses_windows(); + delwin(mainwin); + endwin(); +#ifdef WIN32 + // Move the cursor to after curses output. + HANDLE hout = GetStdHandle(STD_OUTPUT_HANDLE); + CONSOLE_SCREEN_BUFFER_INFO csbi; + COORD coord; + + if (GetConsoleScreenBufferInfo(hout, &csbi)) + { + coord.X = 0; + coord.Y = csbi.dwSize.Y - 1; + SetConsoleCursorPosition(hout, coord); + } +#endif + unlock_curses(); + } +} +#endif + +static void kill_timeout(struct thr_info *thr) +{ + cg_completion_timeout(&thr_info_cancel, thr, 1000); +} + +static void kill_mining(void) +{ + struct thr_info *thr; + int i; + + forcelog(LOG_DEBUG, "Killing off mining threads"); + /* Kill the mining threads*/ + for (i = 0; i < mining_threads; i++) + { + pthread_t *pth = NULL; + + thr = get_thread(i); + if (thr && PTH(thr) != 0L) + pth = &thr->pth; + thr_info_cancel(thr); +#ifndef WIN32 + if (pth && *pth) + pthread_join(*pth, NULL); +#else + if (pth && pth->p) + pthread_join(*pth, NULL); +#endif + } +} + +static void __kill_work(void) +{ + struct thr_info *thr; + int i; + + if (!successful_connect) + return; + + forcelog(LOG_INFO, "Received kill message"); + +#ifdef USE_USBUTILS + /* Best to get rid of it first so it doesn't + * try to create any new devices */ + forcelog(LOG_DEBUG, "Killing off HotPlug thread"); + thr = &control_thr[hotplug_thr_id]; + kill_timeout(thr); +#endif + + forcelog(LOG_DEBUG, "Killing off watchpool thread"); + /* Kill the watchpool thread */ + thr = &control_thr[watchpool_thr_id]; + kill_timeout(thr); + + forcelog(LOG_DEBUG, "Killing off watchdog thread"); + /* Kill the watchdog thread */ + thr = &control_thr[watchdog_thr_id]; + kill_timeout(thr); + + forcelog(LOG_DEBUG, "Shutting down mining threads"); + for (i = 0; i < mining_threads; i++) + { + struct cgpu_info *cgpu; + + thr = get_thread(i); + if (!thr) + continue; + cgpu = thr->cgpu; + if (!cgpu) + continue; + + cgpu->shutdown = true; + } + + sleep(1); + + cg_completion_timeout(&kill_mining, NULL, 3000); + + /* Stop the others */ + forcelog(LOG_DEBUG, "Killing off API thread"); + thr = &control_thr[api_thr_id]; + kill_timeout(thr); + +#ifdef USE_USBUTILS + /* Release USB resources in case it's a restart + * and not a QUIT */ + forcelog(LOG_DEBUG, "Releasing all USB devices"); + cg_completion_timeout(&usb_cleanup, NULL, 1000); + + forcelog(LOG_DEBUG, "Killing off usbres thread"); + thr = &control_thr[usbres_thr_id]; + kill_timeout(thr); +#endif + +} + +/* This should be the common exit path */ +void kill_work(void) +{ + cg_completion_timeout(&__kill_work, NULL, 5000); + + quit(0, "Shutdown signal received."); +} + +static +#ifdef WIN32 +const +#endif +char **initial_args; + +static void clean_up(bool restarting); + +void app_restart(void) +{ + applog(LOG_WARNING, "Attempting to restart %s", packagename); + + cg_completion_timeout(&__kill_work, NULL, 5000); + clean_up(true); + +#if defined(unix) || defined(__APPLE__) + if (forkpid > 0) + { + kill(forkpid, SIGTERM); + forkpid = 0; + } +#endif + + execv(initial_args[0], (EXECV_2ND_ARG_TYPE)initial_args); + applog(LOG_WARNING, "Failed to restart application"); +} + +static void sighandler(int __maybe_unused sig) +{ + /* Restore signal handlers so we can still quit if kill_work fails */ + sigaction(SIGTERM, &termhandler, NULL); + sigaction(SIGINT, &inthandler, NULL); + kill_work(); +} + +static void _stage_work(struct work *work); + +#define stage_work(WORK) do { \ + _stage_work(WORK); \ + WORK = NULL; \ +} while (0) + +/* Adjust an existing char ntime field with a relative noffset */ +static void modify_ntime(char *ntime, int noffset) +{ + unsigned char bin[4]; + uint32_t h32, *be32 = (uint32_t *)bin; + + hex2bin(bin, ntime, 4); + h32 = be32toh(*be32) + noffset; + *be32 = htobe32(h32); + __bin2hex(ntime, bin, 4); +} + +void roll_work(struct work *work) +{ + uint32_t *work_ntime; + uint32_t ntime; + + work_ntime = (uint32_t *)(work->data + 68); + ntime = be32toh(*work_ntime); + ntime++; + *work_ntime = htobe32(ntime); + local_work++; + work->rolls++; + work->nonce = 0; + applog(LOG_DEBUG, "Successfully rolled work"); + /* Change the ntime field if this is stratum work */ + if (work->ntime) + modify_ntime(work->ntime, 1); + + /* This is now a different work item so it needs a different ID for the + * hashtable */ + work->id = total_work_inc(); +} + +struct work *make_clone(struct work *work) +{ + struct work *work_clone = copy_work(work); + + work_clone->clone = true; + cgtime((struct timeval *)&(work_clone->tv_cloned)); + work_clone->longpoll = false; + work_clone->mandatory = false; + /* Make cloned work appear slightly older to bias towards keeping the + * master work item which can be further rolled */ + work_clone->tv_staged.tv_sec -= 1; + + return work_clone; +} + +#ifdef HAVE_LIBCURL +/* Called with pool_lock held. Recruit an extra curl if none are available for + * this pool. */ +static void recruit_curl(struct pool *pool) +{ + struct curl_ent *ce = calloc(sizeof(struct curl_ent), 1); + + if (unlikely(!ce)) + quit(1, "Failed to calloc in recruit_curl"); + + ce->curl = curl_easy_init(); + if (unlikely(!ce->curl)) + quit(1, "Failed to init in recruit_curl"); + + list_add(&ce->node, &pool->curlring); + pool->curls++; +} + +/* Grab an available curl if there is one. If not, then recruit extra curls + * unless we are in a submit_fail situation, or we have opt_delaynet enabled + * and there are already 5 curls in circulation. Limit total number to the + * number of mining threads per pool as well to prevent blasting a pool during + * network delays/outages. */ +static struct curl_ent *pop_curl_entry(struct pool *pool) +{ + int curl_limit = opt_delaynet ? 5 : (mining_threads + opt_queue) * 2; + bool recruited = false; + struct curl_ent *ce; + + mutex_lock(&pool->pool_lock); +retry: + if (!pool->curls) + { + recruit_curl(pool); + recruited = true; + } + else if (list_empty(&pool->curlring)) + { + if (pool->curls >= curl_limit) + { + pthread_cond_wait(&pool->cr_cond, &pool->pool_lock); + goto retry; + } + else + { + recruit_curl(pool); + recruited = true; + } + } + ce = list_entry(pool->curlring.next, struct curl_ent, node); + list_del(&ce->node); + mutex_unlock(&pool->pool_lock); + + if (recruited) + applog(LOG_DEBUG, "Recruited curl for pool %d", pool->pool_no); + return ce; +} + +static void push_curl_entry(struct curl_ent *ce, struct pool *pool) +{ + mutex_lock(&pool->pool_lock); + list_add_tail(&ce->node, &pool->curlring); + cgtime(&ce->tv); + pthread_cond_broadcast(&pool->cr_cond); + mutex_unlock(&pool->pool_lock); +} + +static bool stale_work(struct work *work, bool share); + +static inline bool should_roll(struct work *work) +{ + struct timeval now; + time_t expiry; + + if (work->pool != current_pool() && pool_strategy != POOL_LOADBALANCE && pool_strategy != POOL_BALANCE) + return false; + + if (work->rolltime > opt_scantime) + expiry = work->rolltime; + else + expiry = opt_scantime; + expiry = expiry * 2 / 3; + + /* We shouldn't roll if we're unlikely to get one shares' duration + * work out of doing so */ + cgtime(&now); + if (now.tv_sec - work->tv_staged.tv_sec > expiry) + return false; + + return true; +} + +/* Limit rolls to 7000 to not beyond 2 hours in the future where bitcoind will + * reject blocks as invalid. */ +static inline bool can_roll(struct work *work) +{ + return (!work->stratum && work->pool && work->rolltime && !work->clone && + work->rolls < 7000 && !stale_work(work, false)); +} + +static void *submit_work_thread(void *userdata) +{ + struct work *work = (struct work *)userdata; + struct pool *pool = work->pool; + bool resubmit = false; + struct curl_ent *ce; + + pthread_detach(pthread_self()); + + RenameThread("SubmitWork"); + + applog(LOG_DEBUG, "Creating extra submit work thread"); + + ce = pop_curl_entry(pool); + /* submit solution to bitcoin via JSON-RPC */ + while (!submit_upstream_work(work, ce->curl, resubmit)) + { + if (opt_lowmem) + { + applog(LOG_NOTICE, "Pool %d share being discarded to minimise memory cache", pool->pool_no); + break; + } + resubmit = true; + if (stale_work(work, true)) + { + applog(LOG_NOTICE, "Pool %d share became stale while retrying submit, discarding", pool->pool_no); + + mutex_lock(&stats_lock); + total_stale++; + pool->stale_shares++; + total_diff_stale += work->work_difficulty; + pool->diff_stale += work->work_difficulty; + mutex_unlock(&stats_lock); + + free_work(work); + break; + } + + /* pause, then restart work-request loop */ + applog(LOG_INFO, "json_rpc_call failed on submit_work, retrying"); + } + push_curl_entry(ce, pool); + + return NULL; +} + +static bool clone_available(void) +{ + struct work *work_clone = NULL, *work, *tmp; + bool cloned = false; + + mutex_lock(stgd_lock); + if (!staged_rollable) + goto out_unlock; + + HASH_ITER(hh, staged_work, work, tmp) + { + if (can_roll(work) && should_roll(work)) + { + roll_work(work); + work_clone = make_clone(work); + roll_work(work); + cloned = true; + break; + } + } + +out_unlock: + mutex_unlock(stgd_lock); + + if (cloned) + { + applog(LOG_DEBUG, "Pushing cloned available work to stage thread"); + stage_work(work_clone); + } + return cloned; +} + +/* Clones work by rolling it if possible, and returning a clone instead of the + * original work item which gets staged again to possibly be rolled again in + * the future */ +static struct work *clone_work(struct work *work) +{ + int mrs = mining_threads + opt_queue - total_staged(); + struct work *work_clone; + bool cloned; + + if (mrs < 1) + return work; + + cloned = false; + work_clone = make_clone(work); + while (mrs-- > 0 && can_roll(work) && should_roll(work)) + { + applog(LOG_DEBUG, "Pushing rolled converted work to stage thread"); + stage_work(work_clone); + roll_work(work); + work_clone = make_clone(work); + /* Roll it again to prevent duplicates should this be used + * directly later on */ + roll_work(work); + cloned = true; + } + + if (cloned) + { + stage_work(work); + return work_clone; + } + + free_work(work_clone); + + return work; +} + +#else /* HAVE_LIBCURL */ +static void *submit_work_thread(void __maybe_unused *userdata) +{ + pthread_detach(pthread_self()); + return NULL; +} +#endif /* HAVE_LIBCURL */ + +/* Return an adjusted ntime if we're submitting work that a device has + * internally offset the ntime. */ +static char *offset_ntime(const char *ntime, int noffset) +{ + unsigned char bin[4]; + uint32_t h32, *be32 = (uint32_t *)bin; + + hex2bin(bin, ntime, 4); + h32 = be32toh(*be32) + noffset; + *be32 = htobe32(h32); + + return bin2hex(bin, 4); +} + +/* Duplicates any dynamically allocated arrays within the work struct to + * prevent a copied work struct from freeing ram belonging to another struct */ +static void _copy_work(struct work *work, const struct work *base_work, int noffset) +{ + uint32_t id = work->id; + + clean_work(work); + memcpy(work, base_work, sizeof(struct work)); + /* Keep the unique new id assigned during make_work to prevent copied + * work from having the same id. */ + work->id = id; + if (base_work->job_id) + work->job_id = strdup(base_work->job_id); + if (base_work->nonce1) + work->nonce1 = strdup(base_work->nonce1); + if (base_work->ntime) + { + /* If we are passed an noffset the binary work->data ntime and + * the work->ntime hex string need to be adjusted. */ + if (noffset) + { + uint32_t *work_ntime = (uint32_t *)(work->data + 68); + uint32_t ntime = be32toh(*work_ntime); + + ntime += noffset; + *work_ntime = htobe32(ntime); + work->ntime = offset_ntime(base_work->ntime, noffset); + } + else + work->ntime = strdup(base_work->ntime); + } + else if (noffset) + { + uint32_t *work_ntime = (uint32_t *)(work->data + 68); + uint32_t ntime = be32toh(*work_ntime); + + ntime += noffset; + *work_ntime = htobe32(ntime); + } + if (base_work->coinbase) + work->coinbase = strdup(base_work->coinbase); +#ifdef USE_BITMAIN_C5 + work->version = base_work->version; +#endif +} + +void set_work_ntime(struct work *work, int ntime) +{ + uint32_t *work_ntime = (uint32_t *)(work->data + 68); + + *work_ntime = htobe32(ntime); + if (work->ntime) + { + free(work->ntime); + work->ntime = bin2hex((unsigned char *)work_ntime, 4); + } +} + +/* Generates a copy of an existing work struct, creating fresh heap allocations + * for all dynamically allocated arrays within the struct. noffset is used for + * when a driver has internally rolled the ntime, noffset is a relative value. + * The macro copy_work() calls this function with an noffset of 0. */ +struct work *copy_work_noffset(struct work *base_work, int noffset) +{ + struct work *work = make_work(); + + _copy_work(work, base_work, noffset); + + return work; +} + +void pool_died(struct pool *pool) +{ + if (!pool_tset(pool, &pool->idle)) + { + cgtime(&pool->tv_idle); + if (pool == current_pool()) + { + applog(LOG_WARNING, "Pool %d %s not responding!", pool->pool_no, pool->rpc_url); + switch_pools(NULL); + } + else + applog(LOG_INFO, "Pool %d %s failed to return work", pool->pool_no, pool->rpc_url); + } +} + +static bool stale_work(struct work *work, bool share) +{ + struct timeval now; + time_t work_expiry; + struct pool *pool; + int getwork_delay; + + if (opt_benchmark || opt_benchfile) + return false; + + if (work->work_block != work_block) + { + applog(LOG_DEBUG, "Work stale due to block mismatch"); + return true; + } + + /* Technically the rolltime should be correct but some pools + * advertise a broken expire= that is lower than a meaningful + * scantime */ + if (work->rolltime > opt_scantime) + work_expiry = work->rolltime; + else + work_expiry = opt_expiry; + + pool = work->pool; + + if (!share && pool->has_stratum) + { + bool same_job; + + if (!pool->stratum_active || !pool->stratum_notify) + { + applog(LOG_DEBUG, "Work stale due to stratum inactive"); + return true; + } + + same_job = true; + + cg_rlock(&pool->data_lock); + if (strcmp(work->job_id, pool->swork.job_id)) + same_job = false; + cg_runlock(&pool->data_lock); + + if (!same_job) + { + applog(LOG_DEBUG, "Work stale due to stratum job_id mismatch"); + return true; + } + } + + /* Factor in the average getwork delay of this pool, rounding it up to + * the nearest second */ + getwork_delay = pool->cgminer_pool_stats.getwork_wait_rolling * 5 + 1; + work_expiry -= getwork_delay; + if (unlikely(work_expiry < 5)) + work_expiry = 5; + + cgtime(&now); + if ((now.tv_sec - work->tv_staged.tv_sec) >= work_expiry) + { + applog(LOG_DEBUG, "Work stale due to expiry"); + return true; + } + + if (opt_fail_only && !share && pool != current_pool() && !work->mandatory && + pool_strategy != POOL_LOADBALANCE && pool_strategy != POOL_BALANCE) + { + applog(LOG_DEBUG, "Work stale due to fail only pool mismatch"); + return true; + } + + return false; +} + +uint64_t share_diff(const struct work *work) +{ + bool new_best = false; + double d64, s64; + uint64_t ret; + + d64 = truediffone; + s64 = le256todouble(work->hash); + if (unlikely(!s64)) + s64 = 0; + + ret = round(d64 / s64); + + cg_wlock(&control_lock); + if (unlikely(ret > best_diff)) + { + new_best = true; + best_diff = ret; + suffix_string(best_diff, best_share, sizeof(best_share), 0); + } + if (unlikely(ret > work->pool->best_diff)) + work->pool->best_diff = ret; + cg_wunlock(&control_lock); + + if (unlikely(new_best)) + applog(LOG_INFO, "New best share: %s", best_share); + + return ret; +} + +uint64_t share_ndiff(const struct work *work) +{ + double d64, s64; + uint64_t ret = 0; + + if(work != NULL) + { + d64 = truediffone; + s64 = le256todouble(work->hash); + if (unlikely(!s64)) + { + ret = 0; + } + else + { + ret = (d64 / s64); + } + } + return ret; +} + +static void regen_hash(struct work *work) +{ + uint32_t *data32 = (uint32_t *)(work->data); + unsigned char swap[80]; + uint32_t *swap32 = (uint32_t *)swap; + unsigned char hash1[32]; + + flip80(swap32, data32); + sha256(swap, 80, hash1); + sha256(hash1, 32, (unsigned char *)(work->hash)); +} + +static bool cnx_needed(struct pool *pool); + +/* Find the pool that currently has the highest priority */ +static struct pool *priority_pool(int choice) +{ + struct pool *ret = NULL; + int i; + + for (i = 0; i < total_pools; i++) + { + struct pool *pool = pools[i]; + + if (pool->prio == choice) + { + ret = pool; + break; + } + } + + if (unlikely(!ret)) + { + applog(LOG_ERR, "WTF No pool %d found!", choice); + return pools[choice]; + } + return ret; +} + +/* Specifies whether we can switch to this pool or not. */ +static bool pool_unusable(struct pool *pool) +{ + if (pool->idle) + return true; + if (pool->enabled != POOL_ENABLED) + return true; + return false; +} + +void switch_pools(struct pool *selected) +{ + struct pool *pool, *last_pool; + int i, pool_no, next_pool; + + cg_wlock(&control_lock); + last_pool = currentpool; + pool_no = currentpool->pool_no; + + /* Switch selected to pool number 0 and move the rest down */ + if (selected) + { + if (selected->prio != 0) + { + for (i = 0; i < total_pools; i++) + { + pool = pools[i]; + if (pool->prio < selected->prio) + pool->prio++; + } + selected->prio = 0; + } + } + + switch (pool_strategy) + { + /* All of these set to the master pool */ + case POOL_BALANCE: + case POOL_FAILOVER: + case POOL_LOADBALANCE: + for (i = 0; i < total_pools; i++) + { + pool = priority_pool(i); + if (pool_unusable(pool)) + continue; + pool_no = pool->pool_no; + break; + } + break; + /* Both of these simply increment and cycle */ + case POOL_ROUNDROBIN: + case POOL_ROTATE: + if (selected && !selected->idle) + { + pool_no = selected->pool_no; + break; + } + next_pool = pool_no; + /* Select the next alive pool */ + for (i = 1; i < total_pools; i++) + { + next_pool++; + if (next_pool >= total_pools) + next_pool = 0; + pool = pools[next_pool]; + if (pool_unusable(pool)) + continue; + pool_no = next_pool; + break; + } + break; + default: + break; + } + + currentpool = pools[pool_no]; + pool = currentpool; + cg_wunlock(&control_lock); + + /* Set the lagging flag to avoid pool not providing work fast enough + * messages in failover only mode since we have to get all fresh work + * as in restart_threads */ + if (opt_fail_only) + pool_tset(pool, &pool->lagging); + + if (pool != last_pool && pool_strategy != POOL_LOADBALANCE && pool_strategy != POOL_BALANCE) + { + applog(LOG_WARNING, "Switching to pool %d %s", pool->pool_no, pool->rpc_url); + if (pool_localgen(pool) || opt_fail_only) + clear_pool_work(last_pool); + } + + mutex_lock(&lp_lock); + pthread_cond_broadcast(&lp_cond); + mutex_unlock(&lp_lock); + +} + +void _discard_work(struct work *work) +{ + if (!work->clone && !work->rolls && !work->mined) + { + if (work->pool) + { + work->pool->discarded_work++; + work->pool->quota_used--; + work->pool->works--; + } + total_discarded++; + //applog(LOG_DEBUG, "Discarded work"); + } + else + applog(LOG_DEBUG, "Discarded cloned or rolled work"); + free_work(work); +} + +static void wake_gws(void) +{ + mutex_lock(stgd_lock); + pthread_cond_signal(&gws_cond); + mutex_unlock(stgd_lock); +} + +static void discard_stale(void) +{ + struct work *work, *tmp; + int stale = 0; + + mutex_lock(stgd_lock); + HASH_ITER(hh, staged_work, work, tmp) + { + if (stale_work(work, false)) + { + HASH_DEL(staged_work, work); + discard_work(work); + stale++; + } + } + pthread_cond_signal(&gws_cond); + mutex_unlock(stgd_lock); + + if (stale) + applog(LOG_DEBUG, "Discarded %d stales that didn't match current hash", stale); +} + +/* A generic wait function for threads that poll that will wait a specified + * time tdiff waiting on the pthread conditional that is broadcast when a + * work restart is required. Returns the value of pthread_cond_timedwait + * which is zero if the condition was met or ETIMEDOUT if not. + */ +int restart_wait(struct thr_info *thr, unsigned int mstime) +{ + struct timeval now, then, tdiff; + struct timespec abstime; + int rc; + + tdiff.tv_sec = mstime / 1000; + tdiff.tv_usec = mstime * 1000 - (tdiff.tv_sec * 1000000); + cgtime(&now); + timeradd(&now, &tdiff, &then); + abstime.tv_sec = then.tv_sec; + abstime.tv_nsec = then.tv_usec * 1000; + + mutex_lock(&restart_lock); + if (thr->work_restart) + rc = 0; + else + rc = pthread_cond_timedwait(&restart_cond, &restart_lock, &abstime); + mutex_unlock(&restart_lock); + + return rc; +} + +static void *restart_thread(void __maybe_unused *arg) +{ + struct pool *cp = current_pool(); + struct cgpu_info *cgpu; + int i, mt; + pthread_detach(pthread_self()); + /* Artificially set the lagging flag to avoid pool not providing work + * fast enough messages after every long poll */ + pool_tset(cp, &cp->lagging); + /* Discard staged work that is now stale */ + discard_stale(); + rd_lock(&mining_thr_lock); + mt = mining_threads; + rd_unlock(&mining_thr_lock); + for (i = 0; i < mt; i++) + { + cgpu = mining_thr[i]->cgpu; + if (unlikely(!cgpu)) + continue; + if (cgpu->deven != DEV_ENABLED) + continue; + mining_thr[i]->work_restart = true; + flush_queue(cgpu); + cgpu->drv->flush_work(cgpu); + } + mutex_lock(&restart_lock); + pthread_cond_broadcast(&restart_cond); + mutex_unlock(&restart_lock); +#ifdef USE_USBUTILS + /* Cancels any cancellable usb transfers. Flagged as such it means they + * are usualy waiting on a read result and it's safe to abort the read + * early. */ + cancel_usb_transfers(); +#endif + return NULL; +} + +/* In order to prevent a deadlock via the various drv->flush_work + * implementations we send the restart messages via a separate thread. */ +static void restart_threads(void) +{ + pthread_t rthread; + + cgtime(&restart_tv_start); + if (unlikely(pthread_create(&rthread, NULL, restart_thread, NULL))) + quit(1, "Failed to create restart thread"); +} + +static void signal_work_update(void) +{ + int i; + + applog(LOG_INFO, "Work update message received"); + + cgtime(&update_tv_start); + rd_lock(&mining_thr_lock); + for (i = 0; i < mining_threads; i++) + mining_thr[i]->work_update = true; + rd_unlock(&mining_thr_lock); +} + +static void set_curblock(char *hexstr, unsigned char *bedata) +{ + int ofs; + + cg_wlock(&ch_lock); + cgtime(&block_timeval); + strcpy(current_hash, hexstr); + memcpy(current_block, bedata, 32); + get_timestamp(blocktime, sizeof(blocktime), &block_timeval); + cg_wunlock(&ch_lock); + + for (ofs = 0; ofs <= 56; ofs++) + { + if (memcmp(¤t_hash[ofs], "0", 1)) + break; + } + strncpy(prev_block, ¤t_hash[ofs], 8); + prev_block[8] = '\0'; + + applog(LOG_INFO, "New block: %s... diff %s", current_hash, block_diff); +} + +/* Search to see if this string is from a block that has been seen before */ +static bool block_exists(char *hexstr) +{ + struct block *s; + + rd_lock(&blk_lock); + HASH_FIND_STR(blocks, hexstr, s); + rd_unlock(&blk_lock); + + if (s) + return true; + return false; +} + +/* Tests if this work is from a block that has been seen before */ +static inline bool from_existing_block(struct work *work) +{ + char *hexstr = bin2hex(work->data + 8, 18); + bool ret; + + ret = block_exists(hexstr); + free(hexstr); + return ret; +} + +static int block_sort(struct block *blocka, struct block *blockb) +{ + return blocka->block_no - blockb->block_no; +} + +/* Decode the current block difficulty which is in packed form */ +static void set_blockdiff(const struct work *work) +{ + uint8_t pow = work->data[72]; + int powdiff = (8 * (0x1d - 3)) - (8 * (pow - 3)); + uint32_t diff32 = be32toh(*((uint32_t *)(work->data + 72))) & 0x00FFFFFF; + double numerator = 0xFFFFULL << powdiff; + double ddiff = numerator / (double)diff32; + + if (unlikely(current_diff != ddiff)) + { + suffix_string(ddiff, block_diff, sizeof(block_diff), 0); + current_diff = ddiff; + applog(LOG_NOTICE, "Network diff set to %s", block_diff); + } +} + +static bool test_work_current(struct work *work) +{ + struct pool *pool = work->pool; + unsigned char bedata[32]; + char hexstr[68]; + bool ret = true; + + if (work->mandatory) + return ret; + + swap256(bedata, work->data + 4); + __bin2hex(hexstr, bedata, 32); + + /* Search to see if this block exists yet and if not, consider it a + * new block and set the current block details to this one */ + if (!block_exists(hexstr)) + { + struct block *s = calloc(sizeof(struct block), 1); + int deleted_block = 0; + + if (unlikely(!s)) + quit (1, "test_work_current OOM"); + strcpy(s->hash, hexstr); + s->block_no = new_blocks++; + + wr_lock(&blk_lock); + /* Only keep the last hour's worth of blocks in memory since + * work from blocks before this is virtually impossible and we + * want to prevent memory usage from continually rising */ + if (HASH_COUNT(blocks) > 6) + { + struct block *oldblock; + + HASH_SORT(blocks, block_sort); + oldblock = blocks; + deleted_block = oldblock->block_no; + HASH_DEL(blocks, oldblock); + free(oldblock); + } + HASH_ADD_STR(blocks, hash, s); + set_blockdiff(work); + wr_unlock(&blk_lock); + + if (deleted_block) + applog(LOG_DEBUG, "Deleted block %d from database", deleted_block); + set_curblock(hexstr, bedata); + /* Copy the information to this pool's prev_block since it + * knows the new block exists. */ + memcpy(pool->prev_block, bedata, 32); + if (unlikely(new_blocks == 1)) + { + ret = false; + goto out; + } + + work->work_block = ++work_block; + + if (work->longpoll) + { + if (work->stratum) + { + applog(LOG_NOTICE, "Stratum from pool %d detected new block", + pool->pool_no); + } + else + { + applog(LOG_NOTICE, "%sLONGPOLL from pool %d detected new block", + work->gbt ? "GBT " : "", work->pool->pool_no); + } + } + else if (have_longpoll && !pool->gbt_solo) + applog(LOG_NOTICE, "New block detected on network before pool notification"); + else if (!pool->gbt_solo) + applog(LOG_NOTICE, "New block detected on network"); + restart_threads(); + } + else + { + if (memcmp(pool->prev_block, bedata, 32)) + { + /* Work doesn't match what this pool has stored as + * prev_block. Let's see if the work is from an old + * block or the pool is just learning about a new + * block. */ + if (memcmp(bedata, current_block, 32)) + { + /* Doesn't match current block. It's stale */ + applog(LOG_DEBUG, "Stale data from pool %d", pool->pool_no); + ret = false; + } + else + { + /* Work is from new block and pool is up now + * current. */ + applog(LOG_INFO, "Pool %d now up to date", pool->pool_no); + memcpy(pool->prev_block, bedata, 32); + } + } +#if 0 + /* This isn't ideal, this pool is still on an old block but + * accepting shares from it. To maintain fair work distribution + * we work on it anyway. */ + if (memcmp(bedata, current_block, 32)) + applog(LOG_DEBUG, "Pool %d still on old block", pool->pool_no); +#endif + if (work->longpoll) + { + work->work_block = ++work_block; + if (shared_strategy() || work->pool == current_pool()) + { + if (work->stratum) + { + applog(LOG_NOTICE, "Stratum from pool %d requested work restart", + pool->pool_no); + } + else + { + applog(LOG_NOTICE, "%sLONGPOLL from pool %d requested work restart", + work->gbt ? "GBT " : "", work->pool->pool_no); + } + restart_threads(); + } + } + } +out: + work->longpoll = false; + + return ret; +} + +static int tv_sort(struct work *worka, struct work *workb) +{ + return worka->tv_staged.tv_sec - workb->tv_staged.tv_sec; +} + +static bool work_rollable(struct work *work) +{ + return (!work->clone && work->rolltime); +} + +static bool hash_push(struct work *work) +{ + bool rc = true; + + mutex_lock(stgd_lock); + if (work_rollable(work)) + staged_rollable++; + if (likely(!getq->frozen)) + { + HASH_ADD_INT(staged_work, id, work); + HASH_SORT(staged_work, tv_sort); + } + else + rc = false; + pthread_cond_broadcast(&getq->cond); + mutex_unlock(stgd_lock); + + return rc; +} + +static void _stage_work(struct work *work) +{ + applog(LOG_DEBUG, "Pushing work from pool %d to hash queue", work->pool->pool_no); + work->work_block = work_block; + test_work_current(work); + work->pool->works++; + hash_push(work); +} + +#ifdef HAVE_CURSES +int curses_int(const char *query) +{ + int ret; + char *cvar; + + cvar = curses_input(query); + ret = atoi(cvar); + free(cvar); + return ret; +} +#endif + +#ifdef HAVE_CURSES +static bool input_pool(bool live); +#endif + +#ifdef HAVE_CURSES +static void display_pool_summary(struct pool *pool) +{ + double efficiency = 0.0; + + if (curses_active_locked()) + { + wlog("Pool: %s\n", pool->rpc_url); + if (pool->solved) + wlog("SOLVED %d BLOCK%s!\n", pool->solved, pool->solved > 1 ? "S" : ""); + if (!pool->has_stratum) + wlog("%s own long-poll support\n", pool->hdr_path ? "Has" : "Does not have"); + wlog(" Queued work requests: %d\n", pool->getwork_requested); + wlog(" Share submissions: %"PRId64"\n", pool->accepted + pool->rejected); + wlog(" Accepted shares: %"PRId64"\n", pool->accepted); + wlog(" Rejected shares: %"PRId64"\n", pool->rejected); + wlog(" Accepted difficulty shares: %1.f\n", pool->diff_accepted); + wlog(" Rejected difficulty shares: %1.f\n", pool->diff_rejected); + if (pool->accepted || pool->rejected) + wlog(" Reject ratio: %.1f%%\n", (double)(pool->rejected * 100) / (double)(pool->accepted + pool->rejected)); + efficiency = pool->getwork_requested ? pool->accepted * 100.0 / pool->getwork_requested : 0.0; + if (!pool_localgen(pool)) + wlog(" Efficiency (accepted / queued): %.0f%%\n", efficiency); + + wlog(" Items worked on: %d\n", pool->works); + wlog(" Discarded work due to new blocks: %d\n", pool->discarded_work); + wlog(" Stale submissions discarded due to new blocks: %d\n", pool->stale_shares); + wlog(" Unable to get work from server occasions: %d\n", pool->getfail_occasions); + wlog(" Submitting work remotely delay occasions: %d\n\n", pool->remotefail_occasions); + unlock_curses(); + } +} +#endif + +/* We can't remove the memory used for this struct pool because there may + * still be work referencing it. We just remove it from the pools list */ +void remove_pool(struct pool *pool) +{ + int i, last_pool = total_pools - 1; + struct pool *other; + + /* Boost priority of any lower prio than this one */ + for (i = 0; i < total_pools; i++) + { + other = pools[i]; + if (other->prio > pool->prio) + other->prio--; + } + + if (pool->pool_no < last_pool) + { + /* Swap the last pool for this one */ + (pools[last_pool])->pool_no = pool->pool_no; + pools[pool->pool_no] = pools[last_pool]; + } + /* Give it an invalid number */ + pool->pool_no = total_pools; + pool->removed = true; + total_pools--; +} + +/* add a mutex if this needs to be thread safe in the future */ +static struct JE +{ + char *buf; + struct JE *next; +} *jedata = NULL; + +static void json_escape_free() +{ + struct JE *jeptr = jedata; + struct JE *jenext; + + jedata = NULL; + + while (jeptr) + { + jenext = jeptr->next; + free(jeptr->buf); + free(jeptr); + jeptr = jenext; + } +} + +static char *json_escape(char *str) +{ + struct JE *jeptr; + char *buf, *ptr; + + /* 2x is the max, may as well just allocate that */ + ptr = buf = malloc(strlen(str) * 2 + 1); + + jeptr = malloc(sizeof(*jeptr)); + + jeptr->buf = buf; + jeptr->next = jedata; + jedata = jeptr; + + while (*str) + { + if (*str == '\\' || *str == '"') + *(ptr++) = '\\'; + + *(ptr++) = *(str++); + } + + *ptr = '\0'; + + return buf; +} + +void write_config(FILE *fcfg) +{ + struct opt_table *opt; + int i; + + /* Write pool values */ + fputs("{\n\"pools\" : [", fcfg); + for(i = 0; i < total_pools; i++) + { + struct pool *pool = priority_pool(i); + + if (pool->quota != 1) + { + fprintf(fcfg, "%s\n\t{\n\t\t\"quota\" : \"%s%s%s%d;%s\",", i > 0 ? "," : "", + pool->rpc_proxy ? json_escape((char *)proxytype(pool->rpc_proxytype)) : "", + pool->rpc_proxy ? json_escape(pool->rpc_proxy) : "", + pool->rpc_proxy ? "|" : "", + pool->quota, + json_escape(pool->rpc_url)); + } + else + { + fprintf(fcfg, "%s\n\t{\n\t\t\"url\" : \"%s%s%s%s\",", i > 0 ? "," : "", + pool->rpc_proxy ? json_escape((char *)proxytype(pool->rpc_proxytype)) : "", + pool->rpc_proxy ? json_escape(pool->rpc_proxy) : "", + pool->rpc_proxy ? "|" : "", + json_escape(pool->rpc_url)); + } + fprintf(fcfg, "\n\t\t\"user\" : \"%s\",", json_escape(pool->rpc_user)); + fprintf(fcfg, "\n\t\t\"pass\" : \"%s\"\n\t}", json_escape(pool->rpc_pass)); + } + fputs("\n]\n", fcfg); + + /* Simple bool,int and char options */ + for (opt = opt_config_table; opt->type != OPT_END; opt++) + { + char *p, *name = strdup(opt->names); + + for (p = strtok(name, "|"); p; p = strtok(NULL, "|")) + { + if (p[1] != '-') + continue; + + if (opt->desc == opt_hidden) + continue; + + if (opt->type & OPT_NOARG && + ((void *)opt->cb == (void *)opt_set_bool || (void *)opt->cb == (void *)opt_set_invbool) && + (*(bool *)opt->u.arg == ((void *)opt->cb == (void *)opt_set_bool))) + { + fprintf(fcfg, ",\n\"%s\" : true", p+2); + continue; + } + + if (opt->type & OPT_HASARG && + ((void *)opt->cb_arg == (void *)opt_set_intval || + (void *)opt->cb_arg == (void *)set_int_0_to_9999 || + (void *)opt->cb_arg == (void *)set_int_1_to_65535 || + (void *)opt->cb_arg == (void *)set_int_0_to_10 || + (void *)opt->cb_arg == (void *)set_int_1_to_10 || + (void *)opt->cb_arg == (void *)set_int_0_to_100 || + (void *)opt->cb_arg == (void *)set_int_0_to_255 || + (void *)opt->cb_arg == (void *)set_int_0_to_200 || + (void *)opt->cb_arg == (void *)set_int_0_to_4 || + (void *)opt->cb_arg == (void *)set_int_32_to_63 || + (void *)opt->cb_arg == (void *)set_int_22_to_55 || + (void *)opt->cb_arg == (void *)set_int_42_to_65)) + { + fprintf(fcfg, ",\n\"%s\" : \"%d\"", p+2, *(int *)opt->u.arg); + continue; + } + + if (opt->type & OPT_HASARG && + (((void *)opt->cb_arg == (void *)set_float_125_to_500) || + (void *)opt->cb_arg == (void *)set_float_100_to_250)) + { + fprintf(fcfg, ",\n\"%s\" : \"%.1f\"", p+2, *(float *)opt->u.arg); + continue; + } + + if (opt->type & (OPT_HASARG | OPT_PROCESSARG) && + (opt->u.arg != &opt_set_null)) + { + char *carg = *(char **)opt->u.arg; + + if (carg) + fprintf(fcfg, ",\n\"%s\" : \"%s\"", p+2, json_escape(carg)); + continue; + } + } + free(name); + } + + /* Special case options */ + if (pool_strategy == POOL_BALANCE) + fputs(",\n\"balance\" : true", fcfg); + if (pool_strategy == POOL_LOADBALANCE) + fputs(",\n\"load-balance\" : true", fcfg); + if (pool_strategy == POOL_ROUNDROBIN) + fputs(",\n\"round-robin\" : true", fcfg); + if (pool_strategy == POOL_ROTATE) + fprintf(fcfg, ",\n\"rotate\" : \"%d\"", opt_rotate_period); + fputs("\n}\n", fcfg); + + json_escape_free(); +} + +void zero_bestshare(void) +{ + int i; + + best_diff = 0; + memset(best_share, 0, 8); + suffix_string(best_diff, best_share, sizeof(best_share), 0); + + for (i = 0; i < total_pools; i++) + { + struct pool *pool = pools[i]; + pool->best_diff = 0; + } +} + +static struct timeval tv_hashmeter; +static time_t hashdisplay_t; + +void zero_stats(void) +{ + int i; + + cgtime(&total_tv_start); + copy_time(&tv_hashmeter, &total_tv_start); + total_rolling = 0; + rolling1 = 0; + rolling5 = 0; + rolling15 = 0; + total_mhashes_done = 0; + + for(i = 0; i < CG_LOCAL_MHASHES_MAX_NUM; i++) + { + g_local_mhashes_dones[i] = 0; + } + g_local_mhashes_index = 0; + g_max_fan = 0; + g_max_temp = 0; + total_getworks = 0; + total_accepted = 0; + total_rejected = 0; + hw_errors = 0; + total_stale = 0; + total_discarded = 0; + local_work = 0; + total_go = 0; + total_ro = 0; + total_secs = 1.0; + last_total_secs = 1.0; + total_diff1 = 0; + found_blocks = 0; + total_diff_accepted = 0; + total_diff_rejected = 0; + total_diff_stale = 0; + + for (i = 0; i < total_pools; i++) + { + struct pool *pool = pools[i]; + + pool->getwork_requested = 0; + pool->accepted = 0; + pool->rejected = 0; + pool->stale_shares = 0; + pool->discarded_work = 0; + pool->getfail_occasions = 0; + pool->remotefail_occasions = 0; + pool->last_share_time = 0; + pool->diff1 = 0; + pool->diff_accepted = 0; + pool->diff_rejected = 0; + pool->diff_stale = 0; + pool->last_share_diff = 0; + } + + zero_bestshare(); + + for (i = 0; i < total_devices; ++i) + { + struct cgpu_info *cgpu = get_devices(i); + + copy_time(&cgpu->dev_start_tv, &total_tv_start); + + mutex_lock(&hash_lock); + cgpu->total_mhashes = 0; + cgpu->accepted = 0; + cgpu->rejected = 0; + cgpu->hw_errors = 0; + cgpu->utility = 0.0; + cgpu->last_share_pool_time = 0; + cgpu->diff1 = 0; + cgpu->diff_accepted = 0; + cgpu->diff_rejected = 0; + cgpu->last_share_diff = 0; + mutex_unlock(&hash_lock); + + /* Don't take any locks in the driver zero stats function, as + * it's called async from everything else and we don't want to + * deadlock. */ + cgpu->drv->zero_stats(cgpu); + } +} + +static void set_highprio(void) +{ +#ifndef WIN32 + int ret = nice(-10); + + if (!ret) + applog(LOG_DEBUG, "Unable to set thread to high priority"); +#else + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST); +#endif +} + +static void set_lowprio(void) +{ +#ifndef WIN32 + int ret = nice(10); + + if (!ret) + applog(LOG_INFO, "Unable to set thread to low priority"); +#else + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_LOWEST); +#endif +} + +#ifdef HAVE_CURSES +static void display_pools(void) +{ + struct pool *pool; + int selected, i; + char input; + + opt_loginput = true; + immedok(logwin, true); + clear_logwin(); +updated: + for (i = 0; i < total_pools; i++) + { + pool = pools[i]; + + if (pool == current_pool()) + wattron(logwin, A_BOLD); + if (pool->enabled != POOL_ENABLED) + wattron(logwin, A_DIM); + wlogprint("%d: ", pool->pool_no); + switch (pool->enabled) + { + case POOL_ENABLED: + wlogprint("Enabled "); + break; + case POOL_DISABLED: + wlogprint("Disabled "); + break; + case POOL_REJECTING: + wlogprint("Rejecting "); + break; + } + wlogprint("%s Quota %d Prio %d: %s User:%s\n", + pool->idle? "Dead" : "Alive", + pool->quota, + pool->prio, + pool->rpc_url, pool->rpc_user); + wattroff(logwin, A_BOLD | A_DIM); + } +retry: + wlogprint("\nCurrent pool management strategy: %s\n", + strategies[pool_strategy].s); + if (pool_strategy == POOL_ROTATE) + wlogprint("Set to rotate every %d minutes\n", opt_rotate_period); + wlogprint("[F]ailover only %s\n", opt_fail_only ? "enabled" : "disabled"); + wlogprint("Pool [A]dd [R]emove [D]isable [E]nable [Q]uota change\n"); + wlogprint("[C]hange management strategy [S]witch pool [I]nformation\n"); + wlogprint("Or press any other key to continue\n"); + logwin_update(); + input = getch(); + + if (!strncasecmp(&input, "a", 1)) + { + input_pool(true); + goto updated; + } + else if (!strncasecmp(&input, "r", 1)) + { + if (total_pools <= 1) + { + wlogprint("Cannot remove last pool"); + goto retry; + } + selected = curses_int("Select pool number"); + if (selected < 0 || selected >= total_pools) + { + wlogprint("Invalid selection\n"); + goto retry; + } + pool = pools[selected]; + if (pool == current_pool()) + switch_pools(NULL); + if (pool == current_pool()) + { + wlogprint("Unable to remove pool due to activity\n"); + goto retry; + } + disable_pool(pool); + remove_pool(pool); + goto updated; + } + else if (!strncasecmp(&input, "s", 1)) + { + selected = curses_int("Select pool number"); + if (selected < 0 || selected >= total_pools) + { + wlogprint("Invalid selection\n"); + goto retry; + } + pool = pools[selected]; + enable_pool(pool); + switch_pools(pool); + goto updated; + } + else if (!strncasecmp(&input, "d", 1)) + { + if (enabled_pools <= 1) + { + wlogprint("Cannot disable last pool"); + goto retry; + } + selected = curses_int("Select pool number"); + if (selected < 0 || selected >= total_pools) + { + wlogprint("Invalid selection\n"); + goto retry; + } + pool = pools[selected]; + disable_pool(pool); + if (pool == current_pool()) + switch_pools(NULL); + goto updated; + } + else if (!strncasecmp(&input, "e", 1)) + { + selected = curses_int("Select pool number"); + if (selected < 0 || selected >= total_pools) + { + wlogprint("Invalid selection\n"); + goto retry; + } + pool = pools[selected]; + enable_pool(pool); + if (pool->prio < current_pool()->prio) + switch_pools(pool); + goto updated; + } + else if (!strncasecmp(&input, "c", 1)) + { + for (i = 0; i <= TOP_STRATEGY; i++) + wlogprint("%d: %s\n", i, strategies[i].s); + selected = curses_int("Select strategy number type"); + if (selected < 0 || selected > TOP_STRATEGY) + { + wlogprint("Invalid selection\n"); + goto retry; + } + if (selected == POOL_ROTATE) + { + opt_rotate_period = curses_int("Select interval in minutes"); + + if (opt_rotate_period < 0 || opt_rotate_period > 9999) + { + opt_rotate_period = 0; + wlogprint("Invalid selection\n"); + goto retry; + } + } + pool_strategy = selected; + switch_pools(NULL); + goto updated; + } + else if (!strncasecmp(&input, "i", 1)) + { + selected = curses_int("Select pool number"); + if (selected < 0 || selected >= total_pools) + { + wlogprint("Invalid selection\n"); + goto retry; + } + pool = pools[selected]; + display_pool_summary(pool); + goto retry; + } + else if (!strncasecmp(&input, "q", 1)) + { + selected = curses_int("Select pool number"); + if (selected < 0 || selected >= total_pools) + { + wlogprint("Invalid selection\n"); + goto retry; + } + pool = pools[selected]; + selected = curses_int("Set quota"); + if (selected < 0) + { + wlogprint("Invalid negative quota\n"); + goto retry; + } + pool->quota = selected; + adjust_quota_gcd(); + goto updated; + } + else if (!strncasecmp(&input, "f", 1)) + { + opt_fail_only ^= true; + goto updated; + } + else + clear_logwin(); + + immedok(logwin, false); + opt_loginput = false; +} + +static void display_options(void) +{ + int selected; + char input; + + opt_loginput = true; + immedok(logwin, true); + clear_logwin(); +retry: + wlogprint("[N]ormal [C]lear [S]ilent mode (disable all output)\n"); + wlogprint("[D]ebug:%s\n[P]er-device:%s\n[Q]uiet:%s\n[V]erbose:%s\n" + "[R]PC debug:%s\n[W]orkTime details:%s\nco[M]pact: %s\n" + "[T]oggle status switching:%s\n" + "w[I]descreen:%s\n" + "[Z]ero statistics\n" + "[L]og interval:%d\n", + opt_debug ? "on" : "off", + want_per_device_stats? "on" : "off", + opt_quiet ? "on" : "off", + opt_log_output ? "on" : "off", + opt_protocol ? "on" : "off", + opt_worktime ? "on" : "off", + opt_compact ? "on" : "off", + switch_status ? "enabled" : "disabled", + opt_widescreen ? "enabled" : "disabled", + opt_log_interval); + wlogprint("Select an option or any other key to return\n"); + logwin_update(); + input = getch(); + if (!strncasecmp(&input, "q", 1)) + { + opt_quiet ^= true; + wlogprint("Quiet mode %s\n", opt_quiet ? "enabled" : "disabled"); + goto retry; + } + else if (!strncasecmp(&input, "v", 1)) + { + opt_log_output ^= true; + if (opt_log_output) + opt_quiet = false; + wlogprint("Verbose mode %s\n", opt_log_output ? "enabled" : "disabled"); + goto retry; + } + else if (!strncasecmp(&input, "n", 1)) + { + opt_log_output = false; + opt_debug = false; + opt_quiet = false; + opt_protocol = false; + opt_compact = false; + want_per_device_stats = false; + wlogprint("Output mode reset to normal\n"); + switch_logsize(false); + goto retry; + } + else if (!strncasecmp(&input, "d", 1)) + { + opt_debug ^= true; + opt_log_output = opt_debug; + if (opt_debug) + opt_quiet = false; + wlogprint("Debug mode %s\n", opt_debug ? "enabled" : "disabled"); + goto retry; + } + else if (!strncasecmp(&input, "m", 1)) + { + opt_compact ^= true; + wlogprint("Compact mode %s\n", opt_compact ? "enabled" : "disabled"); + switch_logsize(false); + goto retry; + } + else if (!strncasecmp(&input, "p", 1)) + { + want_per_device_stats ^= true; + opt_log_output = want_per_device_stats; + wlogprint("Per-device stats %s\n", want_per_device_stats ? "enabled" : "disabled"); + goto retry; + } + else if (!strncasecmp(&input, "r", 1)) + { + opt_protocol ^= true; + if (opt_protocol) + opt_quiet = false; + wlogprint("RPC protocol debugging %s\n", opt_protocol ? "enabled" : "disabled"); + goto retry; + } + else if (!strncasecmp(&input, "c", 1)) + clear_logwin(); + else if (!strncasecmp(&input, "l", 1)) + { + selected = curses_int("Interval in seconds"); + if (selected < 0 || selected > 9999) + { + wlogprint("Invalid selection\n"); + goto retry; + } + opt_log_interval = selected; + wlogprint("Log interval set to %d seconds\n", opt_log_interval); + goto retry; + } + else if (!strncasecmp(&input, "s", 1)) + { + opt_realquiet = true; + } + else if (!strncasecmp(&input, "w", 1)) + { + opt_worktime ^= true; + wlogprint("WorkTime details %s\n", opt_worktime ? "enabled" : "disabled"); + goto retry; + } + else if (!strncasecmp(&input, "t", 1)) + { + switch_status ^= true; + goto retry; + } + else if (!strncasecmp(&input, "i", 1)) + { + opt_widescreen ^= true; + goto retry; + } + else if (!strncasecmp(&input, "z", 1)) + { + zero_stats(); + goto retry; + } + else + clear_logwin(); + + immedok(logwin, false); + opt_loginput = false; +} +#endif + +void default_save_file(char *filename) +{ + if (default_config && *default_config) + { + strcpy(filename, default_config); + return; + } + +#if defined(unix) || defined(__APPLE__) + if (getenv("HOME") && *getenv("HOME")) + { + strcpy(filename, getenv("HOME")); + strcat(filename, "/"); + } + else + strcpy(filename, ""); + strcat(filename, ".bmminer/"); + mkdir(filename, 0777); +#else + strcpy(filename, ""); +#endif + strcat(filename, def_conf); +} + +#ifdef HAVE_CURSES +static void set_options(void) +{ + int selected; + char input; + + opt_loginput = true; + immedok(logwin, true); + clear_logwin(); +retry: + wlogprint("[Q]ueue: %d\n[S]cantime: %d\n[E]xpiry: %d\n" + "[W]rite config file\n[C]gminer restart\n", + opt_queue, opt_scantime, opt_expiry); + wlogprint("Select an option or any other key to return\n"); + logwin_update(); + input = getch(); + + if (!strncasecmp(&input, "q", 1)) + { + selected = curses_int("Extra work items to queue"); + if (selected < 0 || selected > 9999) + { + wlogprint("Invalid selection\n"); + goto retry; + } + opt_queue = selected; + if (opt_queue < max_queue) + max_queue = opt_queue; + goto retry; + } + else if (!strncasecmp(&input, "s", 1)) + { + selected = curses_int("Set scantime in seconds"); + if (selected < 0 || selected > 9999) + { + wlogprint("Invalid selection\n"); + goto retry; + } + opt_scantime = selected; + goto retry; + } + else if (!strncasecmp(&input, "e", 1)) + { + selected = curses_int("Set expiry time in seconds"); + if (selected < 0 || selected > 9999) + { + wlogprint("Invalid selection\n"); + goto retry; + } + opt_expiry = selected; + goto retry; + } + else if (!strncasecmp(&input, "w", 1)) + { + FILE *fcfg; + char *str, filename[PATH_MAX], prompt[PATH_MAX + 50]; + + default_save_file(filename); + snprintf(prompt, sizeof(prompt), "Config filename to write (Enter for default) [%s]", filename); + str = curses_input(prompt); + if (strcmp(str, "-1")) + { + struct stat statbuf; + + strcpy(filename, str); + free(str); + if (!stat(filename, &statbuf)) + { + wlogprint("File exists, overwrite?\n"); + input = getch(); + if (strncasecmp(&input, "y", 1)) + goto retry; + } + } + else + free(str); + fcfg = fopen(filename, "w"); + if (!fcfg) + { + wlogprint("Cannot open or create file\n"); + goto retry; + } + write_config(fcfg); + fclose(fcfg); + goto retry; + + } + else if (!strncasecmp(&input, "c", 1)) + { + wlogprint("Are you sure?\n"); + input = getch(); + if (!strncasecmp(&input, "y", 1)) + app_restart(); + else + clear_logwin(); + } + else + clear_logwin(); + + immedok(logwin, false); + opt_loginput = false; +} + +#ifdef USE_USBUTILS +static void mt_enable(struct thr_info *mythr) +{ + cgsem_post(&mythr->sem); +} + +static void set_usb(void) +{ + int selected, i, mt, enabled, disabled, zombie, total, blacklisted; + struct cgpu_info *cgpu; + struct thr_info *thr; + double val; + char input; + + opt_loginput = true; + immedok(logwin, true); + clear_logwin(); + +retry: + enabled = 0; + disabled = 0; + zombie = 0; + total = 0; + blacklisted = 0; + + rd_lock(&mining_thr_lock); + mt = mining_threads; + rd_unlock(&mining_thr_lock); + + for (i = 0; i < mt; i++) + { + cgpu = mining_thr[i]->cgpu; + if (unlikely(!cgpu)) + continue; + if (cgpu->usbinfo.nodev) + zombie++; + else if (cgpu->deven == DEV_DISABLED) + disabled++; + else + enabled++; + if (cgpu->blacklisted) + blacklisted++; + total++; + } + wlogprint("Hotplug interval:%d\n", hotplug_time); + wlogprint("%d USB devices, %d enabled, %d disabled, %d zombie\n", + total, enabled, disabled, zombie); + wlogprint("[S]ummary of device information\n"); + wlogprint("[E]nable device\n"); + wlogprint("[D]isable device\n"); + wlogprint("[U]nplug to allow hotplug restart\n"); + wlogprint("[R]eset device USB\n"); + wlogprint("[L]ist all known devices\n"); + wlogprint("[B]lacklist current device from current instance of bmminer\n"); + wlogprint("[W]hitelist previously blacklisted device\n"); + wlogprint("[H]otplug interval (0 to disable)\n"); + wlogprint("Select an option or any other key to return\n"); + logwin_update(); + input = getch(); + + if (!strncasecmp(&input, "s", 1)) + { + selected = curses_int("Select device number"); + if (selected < 0 || selected >= mt) + { + wlogprint("Invalid selection\n"); + goto retry; + } + cgpu = mining_thr[selected]->cgpu; + wlogprint("Device %03u:%03u\n", cgpu->usbinfo.bus_number, cgpu->usbinfo.device_address); + wlogprint("Name %s\n", cgpu->drv->name); + wlogprint("ID %d\n", cgpu->device_id); + wlogprint("Enabled: %s\n", cgpu->deven != DEV_DISABLED ? "Yes" : "No"); + wlogprint("Temperature %.1f\n", cgpu->temp); + wlogprint("MHS av %.0f\n", cgpu->total_mhashes / cgpu_runtime(cgpu)); + wlogprint("Accepted %d\n", cgpu->accepted); + wlogprint("Rejected %d\n", cgpu->rejected); + wlogprint("Hardware Errors %d\n", cgpu->hw_errors); + wlogprint("Last Share Pool %d\n", cgpu->last_share_pool_time > 0 ? cgpu->last_share_pool : -1); + wlogprint("Total MH %.1f\n", cgpu->total_mhashes); + wlogprint("Diff1 Work %"PRId64"\n", cgpu->diff1); + wlogprint("Difficulty Accepted %.1f\n", cgpu->diff_accepted); + wlogprint("Difficulty Rejected %.1f\n", cgpu->diff_rejected); + wlogprint("Last Share Difficulty %.1f\n", cgpu->last_share_diff); + wlogprint("No Device: %s\n", cgpu->usbinfo.nodev ? "True" : "False"); + wlogprint("Last Valid Work %"PRIu64"\n", (uint64_t)cgpu->last_device_valid_work); + val = 0; + if (cgpu->hw_errors + cgpu->diff1) + val = cgpu->hw_errors / (cgpu->hw_errors + cgpu->diff1); + wlogprint("Device Hardware %.1f%%\n", val); + val = 0; + if (cgpu->diff1) + val = cgpu->diff_rejected / cgpu->diff1; + wlogprint("Device Rejected %.1f%%\n", val); + goto retry; + } + else if (!strncasecmp(&input, "e", 1)) + { + selected = curses_int("Select device number"); + if (selected < 0 || selected >= mt) + { + wlogprint("Invalid selection\n"); + goto retry; + } + cgpu = mining_thr[selected]->cgpu; + if (cgpu->usbinfo.nodev) + { + wlogprint("Device removed, unable to re-enable!\n"); + goto retry; + } + thr = get_thread(selected); + cgpu->deven = DEV_ENABLED; + mt_enable(thr); + goto retry; + } + else if (!strncasecmp(&input, "d", 1)) + { + selected = curses_int("Select device number"); + if (selected < 0 || selected >= mt) + { + wlogprint("Invalid selection\n"); + goto retry; + } + cgpu = mining_thr[selected]->cgpu; + cgpu->deven = DEV_DISABLED; + goto retry; + } + else if (!strncasecmp(&input, "u", 1)) + { + selected = curses_int("Select device number"); + if (selected < 0 || selected >= mt) + { + wlogprint("Invalid selection\n"); + goto retry; + } + cgpu = mining_thr[selected]->cgpu; + if (cgpu->usbinfo.nodev) + { + wlogprint("Device already removed, unable to unplug!\n"); + goto retry; + } + usb_nodev(cgpu); + goto retry; + } + else if (!strncasecmp(&input, "r", 1)) + { + selected = curses_int("Select device number"); + if (selected < 0 || selected >= mt) + { + wlogprint("Invalid selection\n"); + goto retry; + } + cgpu = mining_thr[selected]->cgpu; + if (cgpu->usbinfo.nodev) + { + wlogprint("Device already removed, unable to reset!\n"); + goto retry; + } + usb_reset(cgpu); + goto retry; + } + else if (!strncasecmp(&input, "b", 1)) + { + selected = curses_int("Select device number"); + if (selected < 0 || selected >= mt) + { + wlogprint("Invalid selection\n"); + goto retry; + } + cgpu = mining_thr[selected]->cgpu; + if (cgpu->usbinfo.nodev) + { + wlogprint("Device already removed, unable to blacklist!\n"); + goto retry; + } + blacklist_cgpu(cgpu); + goto retry; + } + else if (!strncasecmp(&input, "w", 1)) + { + if (!blacklisted) + { + wlogprint("No blacklisted devices!\n"); + goto retry; + } + wlogprint("Blacklisted devices:\n"); + for (i = 0; i < mt; i++) + { + cgpu = mining_thr[i]->cgpu; + if (unlikely(!cgpu)) + continue; + if (cgpu->blacklisted) + { + wlogprint("%d: %s %d %03u:%03u\n", i, cgpu->drv->name, + cgpu->device_id, cgpu->usbinfo.bus_number, + cgpu->usbinfo.device_address); + } + } + selected = curses_int("Select device number"); + if (selected < 0 || selected >= mt) + { + wlogprint("Invalid selection\n"); + goto retry; + } + cgpu = mining_thr[selected]->cgpu; + if (!cgpu->blacklisted) + { + wlogprint("Device not blacklisted, unable to whitelist\n"); + goto retry; + } + whitelist_cgpu(cgpu); + goto retry; + } + else if (!strncasecmp(&input, "h", 1)) + { + selected = curses_int("Select hotplug interval in seconds (0 to disable)"); + if (selected < 0 || selected > 9999) + { + wlogprint("Invalid value\n"); + goto retry; + } + hotplug_time = selected; + goto retry; + } + else if (!strncasecmp(&input, "l", 1)) + { + usb_list(); + goto retry; + } + else + clear_logwin(); + + immedok(logwin, false); + opt_loginput = false; +} +#endif + +static void *input_thread(void __maybe_unused *userdata) +{ + pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); + + RenameThread("Input"); + + if (!curses_active) + return NULL; + + while (1) + { + char input; + + input = getch(); + if (!strncasecmp(&input, "q", 1)) + { + kill_work(); + return NULL; + } + else if (!strncasecmp(&input, "d", 1)) + display_options(); + else if (!strncasecmp(&input, "p", 1)) + display_pools(); + else if (!strncasecmp(&input, "s", 1)) + set_options(); +#ifdef USE_USBUTILS + else if (!strncasecmp(&input, "u", 1)) + set_usb(); +#endif + if (opt_realquiet) + { + disable_curses(); + break; + } + } + + return NULL; +} +#endif + +static void *api_thread(void *userdata) +{ + struct thr_info *mythr = userdata; + + pthread_detach(pthread_self()); + pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); + + RenameThread("API"); + + set_lowprio(); + api(api_thr_id); + + PTH(mythr) = 0L; + + return NULL; +} + +/* Sole work devices are serialised wrt calling get_work so they report in on + * each pass through their scanhash function as well as in get_work whereas + * queued work devices work asynchronously so get them to report in and out + * only across get_work. */ +static void thread_reportin(struct thr_info *thr) +{ + thr->getwork = false; + cgtime(&thr->last); + thr->cgpu->status = LIFE_WELL; + thr->cgpu->device_last_well = time(NULL); +} + +/* Tell the watchdog thread this thread is waiting on get work and should not + * be restarted */ +static void thread_reportout(struct thr_info *thr) +{ + thr->getwork = true; + cgtime(&thr->last); + thr->cgpu->status = LIFE_WELL; + thr->cgpu->device_last_well = time(NULL); +} + +static void hashmeter(int thr_id, uint64_t hashes_done) +{ + bool showlog = false; + double tv_tdiff; + time_t now_t; + int diff_t; + + uint64_t local_mhashes_done = 0; + uint64_t local_mhashes_done_avg = 0; + int local_mhashes_done_count = 0; + int i = 0; + + cgtime(&total_tv_end); + tv_tdiff = tdiff(&total_tv_end, &tv_hashmeter); + now_t = total_tv_end.tv_sec; + diff_t = now_t - hashdisplay_t; + if (diff_t >= opt_log_interval) + { + alt_status ^= switch_status; + hashdisplay_t = now_t; + showlog = true; + } + else if (thr_id < 0) + { + /* hashmeter is called by non-mining threads in case nothing + * has reported in to allow hashrate to converge to zero , but + * we only update if it has been more than opt_log_interval */ + return; + } + copy_time(&tv_hashmeter, &total_tv_end); + + if (thr_id >= 0) + { + struct thr_info *thr = get_thread(thr_id); + struct cgpu_info *cgpu = thr->cgpu; + double device_tdiff, thr_mhs; + + /* Update the last time this thread reported in */ + copy_time(&thr->last, &total_tv_end); + cgpu->device_last_well = now_t; + device_tdiff = tdiff(&total_tv_end, &cgpu->last_message_tv); + copy_time(&cgpu->last_message_tv, &total_tv_end); + thr_mhs = (double)hashes_done / device_tdiff / 1000000; + applog(LOG_DEBUG, "[thread %d: %"PRIu64" hashes, %.1f mhash/sec]", + thr_id, hashes_done, thr_mhs); + hashes_done /= 1000000; + + mutex_lock(&hash_lock); + cgpu->total_mhashes += hashes_done; + decay_time(&cgpu->rolling, hashes_done, device_tdiff, opt_log_interval); + decay_time(&cgpu->rolling1, hashes_done, device_tdiff, 60.0); + decay_time(&cgpu->rolling5, hashes_done, device_tdiff, 300.0); + decay_time(&cgpu->rolling15, hashes_done, device_tdiff, 900.0); + mutex_unlock(&hash_lock); + + if (want_per_device_stats && showlog) + { + char logline[256]; + + get_statline(logline, sizeof(logline), cgpu); + if (!curses_active) + { + printf("%s \r", logline); + fflush(stdout); + } + else + applog(LOG_INFO, "%s", logline); + } + } + else + { + /* No device has reported in, we have been called from the + * watchdog thread so decay all the hashrates */ + mutex_lock(&hash_lock); + for (thr_id = 0; thr_id < mining_threads; thr_id++) + { + struct thr_info *thr = get_thread(thr_id); + struct cgpu_info *cgpu = thr->cgpu; + double device_tdiff = tdiff(&total_tv_end, &cgpu->last_message_tv); + + copy_time(&cgpu->last_message_tv, &total_tv_end); + decay_time(&cgpu->rolling, 0, device_tdiff, opt_log_interval); + decay_time(&cgpu->rolling1, 0, device_tdiff, 60.0); + decay_time(&cgpu->rolling5, 0, device_tdiff, 300.0); + decay_time(&cgpu->rolling15, 0, device_tdiff, 900.0); + } + mutex_unlock(&hash_lock); + } + + mutex_lock(&hash_lock); + total_mhashes_done += hashes_done; + if(showlog) + { + g_local_mhashes_index++; + if(g_local_mhashes_index >= CG_LOCAL_MHASHES_MAX_NUM) + g_local_mhashes_index = 0; + + for(i = 0; i < CG_LOCAL_MHASHES_MAX_NUM; i++) + { + if(g_local_mhashes_dones[i] >= 0) + { + local_mhashes_done_avg += g_local_mhashes_dones[i]; + //applog(LOG_DEBUG, "g_local_mhashes_dones[%d] = %f,%d", i, g_local_mhashes_dones[i],g_local_mhashes_index); + local_mhashes_done_count++; + } + } + + if(local_mhashes_done_count > 0) + { + local_mhashes_done = local_mhashes_done_avg / local_mhashes_done_count; + } + else + { + local_mhashes_done = hashes_done; + } + + decay_time(&total_rolling, local_mhashes_done, opt_log_interval, opt_log_interval); + decay_time(&rolling1, hashes_done, tv_tdiff, 60.0); + decay_time(&rolling5, hashes_done,tv_tdiff, 300.0); + decay_time(&rolling15, hashes_done, tv_tdiff, 900.0); + global_hashrate = llround(total_rolling) * 1000000; + g_local_mhashes_dones[g_local_mhashes_index] = 0; + } + g_local_mhashes_dones[g_local_mhashes_index] += hashes_done; + total_secs = tdiff(&total_tv_end, &total_tv_start); + + if(total_secs - last_total_secs > 86400) + { + applog(LOG_ERR, "bmminer time error total_secs = %d last_total_secs = %d", total_secs, last_total_secs); + mutex_unlock(&hash_lock); + zero_stats(); + mutex_lock(&hash_lock); + } + else + { + last_total_secs = total_secs; + } + if (showlog) + { + char displayed_hashes[16], displayed_rolling[16]; + char displayed_r1[16], displayed_r5[16], displayed_r15[16]; + uint64_t d64; + + d64 = (double)total_mhashes_done / total_secs * 1000000ull; + suffix_string(d64, displayed_hashes, sizeof(displayed_hashes), 4); + d64 = (double)total_rolling * 1000000ull; + g_displayed_rolling = total_rolling / 1000.0; + suffix_string(d64, displayed_rolling, sizeof(displayed_rolling), 4); + d64 = (double)rolling1 * 1000000ull; + suffix_string(d64, displayed_r1, sizeof(displayed_rolling), 4); + d64 = (double)rolling5 * 1000000ull; + suffix_string(d64, displayed_r5, sizeof(displayed_rolling), 4); + d64 = (double)rolling15 * 1000000ull; + suffix_string(d64, displayed_r15, sizeof(displayed_rolling), 4); + + snprintf(statusline, sizeof(statusline), + "(%ds):%s (1m):%s (5m):%s (15m):%s (avg):%sh/s", + opt_log_interval, displayed_rolling, displayed_r1, displayed_r5, + displayed_r15, displayed_hashes); + } + mutex_unlock(&hash_lock); + + if (showlog) + { + if (!curses_active) + { + printf("%s \r", statusline); + fflush(stdout); + } + else + applog(LOG_INFO, "%s", statusline); + } +} + +static void stratum_share_result(json_t *val, json_t *res_val, json_t *err_val, + struct stratum_share *sshare) +{ + struct work *work = sshare->work; + time_t now_t = time(NULL); + char hashshow[64]; + int srdiff; + + srdiff = now_t - sshare->sshare_sent; + if (opt_debug || srdiff > 0) + { + applog(LOG_INFO, "Pool %d stratum share result lag time %d seconds", + work->pool->pool_no, srdiff); + } + show_hash(work, hashshow); + share_result(val, res_val, err_val, work, hashshow, false, ""); +} + +/* Parses stratum json responses and tries to find the id that the request + * matched to and treat it accordingly. */ +static bool parse_stratum_response(struct pool *pool, char *s) +{ + json_t *val = NULL, *err_val, *res_val, *id_val; + struct stratum_share *sshare = NULL; + json_error_t err; + bool ret = false; + int id; + + val = JSON_LOADS(s, &err); + if (!val) + { + applog(LOG_INFO,"JSON decode failed(%d): %s", err.line, err.text); + goto out; + } + + res_val = json_object_get(val, "result"); + err_val = json_object_get(val, "error"); + id_val = json_object_get(val, "id"); + + if (json_is_null(id_val) || !id_val) + { + char *ss; + + if (err_val) + ss = json_dumps(err_val, JSON_INDENT(3)); + else + ss = strdup("(unknown reason)"); + + /*applog(LOG_INFO,*/printf("JSON-RPC non method decode failed: %s", ss); + + free(ss); + + goto out; + } + + id = json_integer_value(id_val); + + mutex_lock(&sshare_lock); + HASH_FIND_INT(stratum_shares, &id, sshare); + if (sshare) + { + HASH_DEL(stratum_shares, sshare); + pool->sshares--; + } + mutex_unlock(&sshare_lock); + + if (!sshare) + { + double pool_diff; + + if (!res_val) + goto out; + /* Since the share is untracked, we can only guess at what the + * work difficulty is based on the current pool diff. */ + cg_rlock(&pool->data_lock); + pool_diff = pool->sdiff; + cg_runlock(&pool->data_lock); + + if (json_is_true(res_val)) + { + applog(LOG_NOTICE, "Accepted untracked stratum share from pool %d", pool->pool_no); + + /* We don't know what device this came from so we can't + * attribute the work to the relevant cgpu */ + mutex_lock(&stats_lock); + total_accepted++; + pool->accepted++; + total_diff_accepted += pool_diff; + pool->diff_accepted += pool_diff; + mutex_unlock(&stats_lock); + } + else + { + applog(LOG_NOTICE, "Rejected untracked stratum share from pool %d", pool->pool_no); + + mutex_lock(&stats_lock); + total_rejected++; + pool->rejected++; + total_diff_rejected += pool_diff; + pool->diff_rejected += pool_diff; + mutex_unlock(&stats_lock); + } + goto out; + } + stratum_share_result(val, res_val, err_val, sshare); + free_work(sshare->work); + free(sshare); + + ret = true; +out: + if (val) + json_decref(val); + + return ret; +} + +void clear_stratum_shares(struct pool *pool) +{ + struct stratum_share *sshare, *tmpshare; + double diff_cleared = 0; + int cleared = 0; + + mutex_lock(&sshare_lock); + HASH_ITER(hh, stratum_shares, sshare, tmpshare) + { + if (sshare->work->pool == pool) + { + HASH_DEL(stratum_shares, sshare); + diff_cleared += sshare->work->work_difficulty; + free_work(sshare->work); + pool->sshares--; + free(sshare); + cleared++; + } + } + mutex_unlock(&sshare_lock); + + if (cleared) + { + applog(LOG_WARNING, "Lost %d shares due to stratum disconnect on pool %d", cleared, pool->pool_no); + pool->stale_shares += cleared; + total_stale += cleared; + pool->diff_stale += diff_cleared; + total_diff_stale += diff_cleared; + } +} + +void clear_pool_work(struct pool *pool) +{ + struct work *work, *tmp; + int cleared = 0; + + mutex_lock(stgd_lock); + HASH_ITER(hh, staged_work, work, tmp) + { + if (work->pool == pool) + { + HASH_DEL(staged_work, work); + free_work(work); + cleared++; + } + } + mutex_unlock(stgd_lock); + + if (cleared) + applog(LOG_INFO, "Cleared %d work items due to stratum disconnect on pool %d", cleared, pool->pool_no); +} + +static int cp_prio(void) +{ + int prio; + + cg_rlock(&control_lock); + prio = currentpool->prio; + cg_runlock(&control_lock); + + return prio; +} + +/* We only need to maintain a secondary pool connection when we need the + * capacity to get work from the backup pools while still on the primary */ +static bool cnx_needed(struct pool *pool) +{ + struct pool *cp; + + if (pool->enabled != POOL_ENABLED) + return false; + + /* Balance strategies need all pools online */ + if (pool_strategy == POOL_BALANCE) + return true; + if (pool_strategy == POOL_LOADBALANCE) + return true; + + /* Idle stratum pool needs something to kick it alive again */ + if (pool->has_stratum && pool->idle) + return true; + + /* Getwork pools without opt_fail_only need backup pools up to be able + * to leak shares */ + cp = current_pool(); + if (cp == pool) + return true; + if (!pool_localgen(cp) && (!opt_fail_only || !cp->hdr_path)) + return true; + /* If we're waiting for a response from shares submitted, keep the + * connection open. */ + if (pool->sshares) + return true; + /* If the pool has only just come to life and is higher priority than + * the current pool keep the connection open so we can fail back to + * it. */ + if (pool_strategy == POOL_FAILOVER && pool->prio < cp_prio()) + return true; + /* We've run out of work, bring anything back to life. */ + if (no_work) + return true; + return false; +} + +static void wait_lpcurrent(struct pool *pool); +static void pool_resus(struct pool *pool); +static void gen_stratum_work(struct pool *pool, struct work *work); + +void stratum_resumed(struct pool *pool) +{ + if (pool_tclear(pool, &pool->idle)) + { + applog(LOG_INFO, "Stratum connection to pool %d resumed", pool->pool_no); + pool_resus(pool); + } +} + +static bool supports_resume(struct pool *pool) +{ + bool ret; + + cg_rlock(&pool->data_lock); + ret = (pool->sessionid != NULL); + cg_runlock(&pool->data_lock); + + return ret; +} + +/* One stratum receive thread per pool that has stratum waits on the socket + * checking for new messages and for the integrity of the socket connection. We + * reset the connection based on the integrity of the receive side only as the + * send side will eventually expire data it fails to send. */ +static void *stratum_rthread(void *userdata) +{ + struct pool *pool = (struct pool *)userdata; + char threadname[16]; + + pthread_detach(pthread_self()); + + snprintf(threadname, sizeof(threadname), "%d/RStratum", pool->pool_no); + RenameThread(threadname); + + while (42) + { + struct timeval timeout; + int sel_ret; + fd_set rd; + char *s; + + if (unlikely(pool->removed)) + { + suspend_stratum(pool); + break; + } + + /* Check to see whether we need to maintain this connection + * indefinitely or just bring it up when we switch to this + * pool */ + if (!sock_full(pool) && !cnx_needed(pool)) + { + suspend_stratum(pool); + clear_stratum_shares(pool); + clear_pool_work(pool); + + wait_lpcurrent(pool); + while (!restart_stratum(pool)) + { + if (pool->removed) + goto out; + if (enabled_pools > 1) + cgsleep_ms(30000); + else + cgsleep_ms(3000); + } + } + + FD_ZERO(&rd); + FD_SET(pool->sock, &rd); + timeout.tv_sec = 90; + timeout.tv_usec = 0; + + /* The protocol specifies that notify messages should be sent + * every minute so if we fail to receive any for 90 seconds we + * assume the connection has been dropped and treat this pool + * as dead */ + if (!sock_full(pool) && (sel_ret = select(pool->sock + 1, &rd, NULL, NULL, &timeout)) < 1) + { + applog(LOG_DEBUG, "Stratum select failed on pool %d with value %d", pool->pool_no, sel_ret); + s = NULL; + } + else + s = recv_line(pool); + if (!s) + { + applog(LOG_NOTICE, "Stratum connection to pool %d interrupted", pool->pool_no); + pool->getfail_occasions++; + total_go++; + + /* If the socket to our stratum pool disconnects, all + * tracked submitted shares are lost and we will leak + * the memory if we don't discard their records. */ + if (!supports_resume(pool) || opt_lowmem) + clear_stratum_shares(pool); + clear_pool_work(pool); + if (pool == current_pool()) + restart_threads(); + + while (!restart_stratum(pool)) + { + if (pool->removed) + goto out; + cgsleep_ms(30000); + } + continue; + } + + /* Check this pool hasn't died while being a backup pool and + * has not had its idle flag cleared */ + stratum_resumed(pool); + if (!parse_method(pool, s) && !parse_stratum_response(pool, s)) + applog(LOG_INFO, "Unknown stratum msg: %s", s); + else if (pool->swork.clean) + { + struct work *work = make_work(); + + /* Generate a single work item to update the current + * block database */ + pool->swork.clean = false; + gen_stratum_work(pool, work); + work->longpoll = true; + /* Return value doesn't matter. We're just informing + * that we may need to restart. */ + test_work_current(work); + free_work(work); + } + free(s); + } + +out: + return NULL; +} + +/* Each pool has one stratum send thread for sending shares to avoid many + * threads being created for submission since all sends need to be serialised + * anyway. */ +static void *stratum_sthread(void *userdata) +{ + struct pool *pool = (struct pool *)userdata; + uint64_t last_nonce2 = 0; + uint32_t last_nonce = 0; + char threadname[16]; + + pthread_detach(pthread_self()); + + snprintf(threadname, sizeof(threadname), "%d/SStratum", pool->pool_no); + RenameThread(threadname); + + pool->stratum_q = tq_new(); + if (!pool->stratum_q) + quit(1, "Failed to create stratum_q in stratum_sthread"); + + while (42) + { + char noncehex[12], nonce2hex[20], s[1024]; + struct stratum_share *sshare; + uint32_t *hash32, nonce; + unsigned char nonce2[8]; + uint64_t *nonce2_64; + struct work *work; + bool submitted; + + if (unlikely(pool->removed)) + break; + + work = tq_pop(pool->stratum_q, NULL); + if (unlikely(!work)) + quit(1, "Stratum q returned empty work"); + + if (unlikely(work->nonce2_len > 8)) + { + applog(LOG_ERR, "Pool %d asking for inappropriately long nonce2 length %d", + pool->pool_no, (int)work->nonce2_len); + applog(LOG_ERR, "Not attempting to submit shares"); + free_work(work); + continue; + } + + nonce = *((uint32_t *)(work->data + 76)); + nonce2_64 = (uint64_t *)nonce2; + *nonce2_64 = htole64(work->nonce2); + /* Filter out duplicate shares */ + if (unlikely(nonce == last_nonce && *nonce2_64 == last_nonce2)) + { + applog(LOG_INFO, "Filtering duplicate share to pool %d", + pool->pool_no); + free_work(work); + continue; + } + last_nonce = nonce; + last_nonce2 = *nonce2_64; + __bin2hex(noncehex, (const unsigned char *)&nonce, 4); + __bin2hex(nonce2hex, nonce2, work->nonce2_len); + + sshare = calloc(sizeof(struct stratum_share), 1); + hash32 = (uint32_t *)work->hash; + submitted = false; + + sshare->sshare_time = time(NULL); + /* This work item is freed in parse_stratum_response */ + sshare->work = work; + memset(s, 0, 1024); + + mutex_lock(&sshare_lock); + /* Give the stratum share a unique id */ + sshare->id = swork_id++; + mutex_unlock(&sshare_lock); + if(pool->support_vil) + { + snprintf(s, sizeof(s), + "{\"params\": [\"%s\", \"%s\", \"%s\", \"%s\", \"%s\", \"%d\"], \"id\": %d, \"method\": \"mining.submit\"}", + pool->rpc_user, work->job_id, nonce2hex, work->ntime, noncehex,work->version,sshare->id); + } + else + snprintf(s, sizeof(s), + "{\"params\": [\"%s\", \"%s\", \"%s\", \"%s\", \"%s\"], \"id\": %d, \"method\": \"mining.submit\"}", + pool->rpc_user, work->job_id, nonce2hex, work->ntime, noncehex, sshare->id); + + applog(LOG_INFO, "Submitting share %08lx to pool %d", + (long unsigned int)htole32(hash32[6]), pool->pool_no); + + /* Try resubmitting for up to 2 minutes if we fail to submit + * once and the stratum pool nonce1 still matches suggesting + * we may be able to resume. */ + while (time(NULL) < sshare->sshare_time + 120) + { + bool sessionid_match; + + if (likely(stratum_send(pool, s, strlen(s)))) + { + mutex_lock(&sshare_lock); + HASH_ADD_INT(stratum_shares, id, sshare); + pool->sshares++; + mutex_unlock(&sshare_lock); + + if (pool_tclear(pool, &pool->submit_fail)) + applog(LOG_WARNING, "Pool %d communication resumed, submitting work", pool->pool_no); + applog(LOG_DEBUG, "Successfully submitted, adding to stratum_shares db"); + submitted = true; + break; + } + if (!pool_tset(pool, &pool->submit_fail) && cnx_needed(pool)) + { + applog(LOG_WARNING, "Pool %d stratum share submission failure", pool->pool_no); + total_ro++; + pool->remotefail_occasions++; + } + + if (opt_lowmem) + { + applog(LOG_DEBUG, "Lowmem option prevents resubmitting stratum share"); + break; + } + + cg_rlock(&pool->data_lock); + sessionid_match = (pool->nonce1 && !strcmp(work->nonce1, pool->nonce1)); + cg_runlock(&pool->data_lock); + + if (!sessionid_match) + { + applog(LOG_DEBUG, "No matching session id for resubmitting stratum share"); + break; + } + /* Retry every 5 seconds */ + sleep(5); + } + + if (unlikely(!submitted)) + { + applog(LOG_DEBUG, "Failed to submit stratum share, discarding"); + free_work(work); + free(sshare); + pool->stale_shares++; + total_stale++; + } + else + { + int ssdiff; + + sshare->sshare_sent = time(NULL); + ssdiff = sshare->sshare_sent - sshare->sshare_time; + if (opt_debug || ssdiff > 0) + { + applog(LOG_INFO, "Pool %d stratum share submission lag time %d seconds", + pool->pool_no, ssdiff); + } + } + } + + /* Freeze the work queue but don't free up its memory in case there is + * work still trying to be submitted to the removed pool. */ + tq_freeze(pool->stratum_q); + + return NULL; +} + +static void init_stratum_threads(struct pool *pool) +{ + have_longpoll = true; + + if (unlikely(pthread_create(&pool->stratum_sthread, NULL, stratum_sthread, (void *)pool))) + quit(1, "Failed to create stratum sthread"); + if (unlikely(pthread_create(&pool->stratum_rthread, NULL, stratum_rthread, (void *)pool))) + quit(1, "Failed to create stratum rthread"); +} + +static void *longpoll_thread(void *userdata); + +static bool stratum_works(struct pool *pool) +{ + applog(LOG_INFO, "Testing pool %d stratum %s", pool->pool_no, pool->stratum_url); + check_extranonce_option(pool, pool->stratum_url); + if (!extract_sockaddr(pool->stratum_url, &pool->sockaddr_url, &pool->stratum_port)) + return false; + + if (!initiate_stratum(pool)) + return false; + + return true; +} + +#ifdef HAVE_LIBCURL +static void __setup_gbt_solo(struct pool *pool) +{ + cg_wlock(&pool->gbt_lock); + memcpy(pool->coinbase, scriptsig_header_bin, 41); + pool->coinbase[41 + pool->n1_len + 4 + 1 + 8] = 25; + memcpy(pool->coinbase + 41 + pool->n1_len + 4 + 1 + 8 + 1, pool->script_pubkey, 25); + cg_wunlock(&pool->gbt_lock); +} + +static bool setup_gbt_solo(CURL *curl, struct pool *pool) +{ + char s[256]; + int uninitialised_var(rolltime); + bool ret = false; + json_t *val = NULL, *res_val, *valid_val; + + if (!opt_btc_address) + { + applog(LOG_ERR, "No BTC address specified, unable to mine solo on %s", + pool->rpc_url); + goto out; + } + snprintf(s, 256, "{\"method\": \"validateaddress\", \"params\": [\"%s\"]}\n", opt_btc_address); + val = json_rpc_call(curl, pool->rpc_url, pool->rpc_userpass, s, true, + false, &rolltime, pool, false); + if (!val) + goto out; + res_val = json_object_get(val, "result"); + if (!res_val) + goto out; + valid_val = json_object_get(res_val, "isvalid"); + if (!valid_val) + goto out; + if (!json_is_true(valid_val)) + { + applog(LOG_ERR, "Bitcoin address %s is NOT valid", opt_btc_address); + goto out; + } + applog(LOG_NOTICE, "Solo mining to valid address: %s", opt_btc_address); + ret = true; + address_to_pubkeyhash(pool->script_pubkey, opt_btc_address); + hex2bin(scriptsig_header_bin, scriptsig_header, 41); + __setup_gbt_solo(pool); + + if (opt_debug) + { + char *cb = bin2hex(pool->coinbase, pool->coinbase_len); + + applog(LOG_DEBUG, "Pool %d coinbase %s", pool->pool_no, cb); + free(cb); + } + pool->gbt_curl = curl_easy_init(); + if (unlikely(!pool->gbt_curl)) + quit(1, "GBT CURL initialisation failed"); + +out: + if (val) + json_decref(val); + return ret; +} +#else +static bool setup_gbt_solo(CURL __maybe_unused *curl, struct pool __maybe_unused *pool) +{ + return false; +} +#endif + +static void pool_start_lp(struct pool *pool) +{ + if (!pool->lp_started) + { + pool->lp_started = true; + if (unlikely(pthread_create(&pool->longpoll_thread, NULL, longpoll_thread, (void *)pool))) + quit(1, "Failed to create pool longpoll thread"); + } +} + +static bool pool_active(struct pool *pool, bool pinging) +{ + struct timeval tv_getwork, tv_getwork_reply; + json_t *val = NULL; + bool ret = false; + CURL *curl; + int uninitialised_var(rolltime); + + if (pool->has_gbt) + applog(LOG_DEBUG, "Retrieving block template from pool %s", pool->rpc_url); + else + applog(LOG_INFO, "Testing pool %s", pool->rpc_url); + + /* This is the central point we activate stratum when we can */ +retry_stratum: + if (pool->has_stratum) + { + /* We create the stratum thread for each pool just after + * successful authorisation. Once the init flag has been set + * we never unset it and the stratum thread is responsible for + * setting/unsetting the active flag */ + bool init = pool_tset(pool, &pool->stratum_init); + + if (!init) + { + bool ret = initiate_stratum(pool) && auth_stratum(pool); + extranonce_subscribe_stratum(pool); + if (ret) + init_stratum_threads(pool); + else + pool_tclear(pool, &pool->stratum_init); + return ret; + } + return pool->stratum_active; + } + + curl = curl_easy_init(); + if (unlikely(!curl)) + { + applog(LOG_ERR, "CURL initialisation failed"); + return false; + } + + /* Probe for GBT support on first pass */ + if (!pool->probed) + { + applog(LOG_DEBUG, "Probing for GBT support"); + val = json_rpc_call(curl, pool->rpc_url, pool->rpc_userpass, + gbt_req, true, false, &rolltime, pool, false); + if (val) + { + bool append = false, submit = false, transactions = false; + json_t *res_val, *mutables; + int i, mutsize = 0; + + res_val = json_object_get(val, "result"); + if (res_val) + { + mutables = json_object_get(res_val, "mutable"); + mutsize = json_array_size(mutables); + } + + for (i = 0; i < mutsize; i++) + { + json_t *arrval = json_array_get(mutables, i); + + if (json_is_string(arrval)) + { + const char *mutable = json_string_value(arrval); + + if (!strncasecmp(mutable, "coinbase/append", 15)) + append = true; + else if (!strncasecmp(mutable, "submit/coinbase", 15)) + submit = true; + else if (!strncasecmp(mutable, "transactions", 12)) + transactions = true; + } + } + json_decref(val); + + /* Only use GBT if it supports coinbase append and + * submit coinbase */ + if (append && submit) + { + pool->has_gbt = true; + pool->rpc_req = gbt_req; + } + else if (transactions) + { + pool->gbt_solo = true; + pool->rpc_req = gbt_solo_req; + } + } + /* Reset this so we can probe fully just after this. It will be + * set to true that time.*/ + pool->probed = false; + + if (pool->has_gbt) + applog(LOG_DEBUG, "GBT coinbase + append support found, switching to GBT protocol"); + else if (pool->gbt_solo) + applog(LOG_DEBUG, "GBT coinbase without append found, switching to GBT solo protocol"); + else + applog(LOG_DEBUG, "No GBT coinbase + append support found, using getwork protocol"); + } + + cgtime(&tv_getwork); + val = json_rpc_call(curl, pool->rpc_url, pool->rpc_userpass, + pool->rpc_req, true, false, &rolltime, pool, false); + cgtime(&tv_getwork_reply); + + /* Detect if a http getwork pool has an X-Stratum header at startup, + * and if so, switch to that in preference to getwork if it works */ + if (pool->stratum_url && !opt_fix_protocol && stratum_works(pool)) + { + applog(LOG_NOTICE, "Switching pool %d %s to %s", pool->pool_no, pool->rpc_url, pool->stratum_url); + if (!pool->rpc_url) + pool->rpc_url = strdup(pool->stratum_url); + pool->has_stratum = true; + curl_easy_cleanup(curl); + + goto retry_stratum; + } + + if (val) + { + struct work *work = make_work(); + bool rc; + + rc = work_decode(pool, work, val); + if (rc) + { + if (pool->gbt_solo) + { + ret = setup_gbt_solo(curl, pool); + if (ret) + pool_start_lp(pool); + free_work(work); + goto out; + } + applog(LOG_DEBUG, "Successfully retrieved and deciphered work from pool %u %s", + pool->pool_no, pool->rpc_url); + work->pool = pool; + work->rolltime = rolltime; + copy_time(&work->tv_getwork, &tv_getwork); + copy_time(&work->tv_getwork_reply, &tv_getwork_reply); + work->getwork_mode = GETWORK_MODE_TESTPOOL; + calc_diff(work, 0); + applog(LOG_DEBUG, "Pushing pooltest work to base pool"); + + stage_work(work); + total_getworks++; + pool->getwork_requested++; + ret = true; + } + else + { + applog(LOG_DEBUG, "Successfully retrieved but FAILED to decipher work from pool %u %s", + pool->pool_no, pool->rpc_url); + free_work(work); + } + + if (pool->lp_url) + goto out; + + /* Decipher the longpoll URL, if any, and store it in ->lp_url */ + if (pool->hdr_path) + { + char *copy_start, *hdr_path; + bool need_slash = false; + size_t siz; + + hdr_path = pool->hdr_path; + if (strstr(hdr_path, "://")) + { + pool->lp_url = hdr_path; + hdr_path = NULL; + } + else + { + /* absolute path, on current server */ + copy_start = (*hdr_path == '/') ? (hdr_path + 1) : hdr_path; + if (pool->rpc_url[strlen(pool->rpc_url) - 1] != '/') + need_slash = true; + + siz = strlen(pool->rpc_url) + strlen(copy_start) + 2; + pool->lp_url = malloc(siz); + if (!pool->lp_url) + { + applog(LOG_ERR, "Malloc failure in pool_active"); + return false; + } + + snprintf(pool->lp_url, siz, "%s%s%s", pool->rpc_url, need_slash ? "/" : "", copy_start); + } + } + else + pool->lp_url = NULL; + + pool_start_lp(pool); + } + else + { + /* If we failed to parse a getwork, this could be a stratum + * url without the prefix stratum+tcp:// so let's check it */ + if (initiate_stratum(pool)) + { + pool->has_stratum = true; + goto retry_stratum; + } + applog(LOG_DEBUG, "FAILED to retrieve work from pool %u %s", + pool->pool_no, pool->rpc_url); + if (!pinging && !pool->idle) + applog(LOG_WARNING, "Pool %u slow/down or URL or credentials invalid", pool->pool_no); + } +out: + if (val) + json_decref(val); + curl_easy_cleanup(curl); + return ret; +} + +static void pool_resus(struct pool *pool) +{ + pool->seq_getfails = 0; + if (pool_strategy == POOL_FAILOVER && pool->prio < cp_prio()) + applog(LOG_WARNING, "Pool %d %s alive, testing stability", pool->pool_no, pool->rpc_url); + else + applog(LOG_INFO, "Pool %d %s alive", pool->pool_no, pool->rpc_url); +} + +static bool work_filled; +static bool work_emptied; + +/* If this is called non_blocking, it will return NULL for work so that must + * be handled. */ +static struct work *hash_pop(bool blocking) +{ + struct work *work = NULL, *tmp; + int hc; + + mutex_lock(stgd_lock); + if (!HASH_COUNT(staged_work)) + { + /* Increase the queue if we reach zero and we know we can reach + * the maximum we're asking for. */ + if (work_filled && max_queue < opt_queue) + { + max_queue++; + work_filled = false; + } + work_emptied = true; + if (!blocking) + goto out_unlock; + do + { + struct timespec then; + struct timeval now; + int rc; + + cgtime(&now); + then.tv_sec = now.tv_sec + 10; + then.tv_nsec = now.tv_usec * 1000; + pthread_cond_signal(&gws_cond); + rc = pthread_cond_timedwait(&getq->cond, stgd_lock, &then); + /* Check again for !no_work as multiple threads may be + * waiting on this condition and another may set the + * bool separately. */ + if (rc && !no_work) + { + no_work = true; + applog(LOG_WARNING, "Waiting for work to be available from pools."); + } + } + while (!HASH_COUNT(staged_work)); + } + + if (no_work) + { + applog(LOG_WARNING, "Work available from pools, resuming."); + no_work = false; + } + + hc = HASH_COUNT(staged_work); + /* Find clone work if possible, to allow masters to be reused */ + if (hc > staged_rollable) + { + HASH_ITER(hh, staged_work, work, tmp) + { + if (!work_rollable(work)) + break; + } + } + else + work = staged_work; + HASH_DEL(staged_work, work); + if (work_rollable(work)) + staged_rollable--; + + /* Signal the getwork scheduler to look for more work */ + pthread_cond_signal(&gws_cond); + + /* Signal hash_pop again in case there are mutliple hash_pop waiters */ + pthread_cond_signal(&getq->cond); + + /* Keep track of last getwork grabbed */ + last_getwork = time(NULL); +out_unlock: + mutex_unlock(stgd_lock); + + return work; +} + +static void gen_hash(unsigned char *data, unsigned char *hash, int len) +{ + unsigned char hash1[32]; + + sha256(data, len, hash1); + sha256(hash1, 32, hash); +} + +void set_target(unsigned char *dest_target, double diff) +{ + unsigned char target[32]; + uint64_t *data64, h64; + double d64, dcut64; + + if (unlikely(diff == 0.0)) + { + /* This shouldn't happen but best we check to prevent a crash */ + applog(LOG_ERR, "Diff zero passed to set_target"); + diff = 1.0; + } + + d64 = truediffone; + d64 /= diff; + + dcut64 = d64 / bits192; + h64 = dcut64; + data64 = (uint64_t *)(target + 24); + *data64 = htole64(h64); + dcut64 = h64; + dcut64 *= bits192; + d64 -= dcut64; + + dcut64 = d64 / bits128; + h64 = dcut64; + data64 = (uint64_t *)(target + 16); + *data64 = htole64(h64); + dcut64 = h64; + dcut64 *= bits128; + d64 -= dcut64; + + dcut64 = d64 / bits64; + h64 = dcut64; + data64 = (uint64_t *)(target + 8); + *data64 = htole64(h64); + dcut64 = h64; + dcut64 *= bits64; + d64 -= dcut64; + + h64 = d64; + data64 = (uint64_t *)(target); + *data64 = htole64(h64); + + if (opt_debug) + { + char *htarget = bin2hex(target, 32); + + applog(LOG_DEBUG, "Generated target %s", htarget); + free(htarget); + } + memcpy(dest_target, target, 32); +} + +#if defined (USE_AVALON2) || defined (USE_AVALON4) || defined (USE_HASHRATIO) +bool submit_nonce2_nonce(struct thr_info *thr, struct pool *pool, struct pool *real_pool, + uint32_t nonce2, uint32_t nonce, uint32_t ntime) +{ + const int thr_id = thr->id; + struct cgpu_info *cgpu = thr->cgpu; + struct device_drv *drv = cgpu->drv; + struct work *work = make_work(); + bool ret; + + cg_wlock(&pool->data_lock); + pool->nonce2 = nonce2; + cg_wunlock(&pool->data_lock); + + gen_stratum_work(pool, work); + while (ntime--) + { + roll_work(work); + } + + work->pool = real_pool; + + work->thr_id = thr_id; + work->work_block = work_block; + work->pool->works++; + + work->mined = true; + work->device_diff = MIN(drv->max_diff, work->work_difficulty); + work->device_diff = MAX(drv->min_diff, work->device_diff); + + ret = submit_nonce(thr, work, nonce); + free_work(work); + return ret; +} +#endif + + +#if defined USE_BITMAIN_C5 +void get_work_by_nonce2(struct thr_info *thr, struct work **work,struct pool *pool, struct pool *real_pool, + uint64_t nonce2, uint32_t ntime, uint32_t version) +{ + *work = make_work(); + const int thr_id = thr->id; + struct cgpu_info *cgpu = thr->cgpu; + struct device_drv *drv = cgpu->drv; + cg_wlock(&pool->data_lock); + pool->nonce2 = nonce2; + //if(pool->support_vil) + version = Swap32(version); + memcpy(pool->header_bin, &version, 4); + cg_wunlock(&pool->data_lock); + + gen_stratum_work(pool, *work); + + (*work)->pool = real_pool; + + (*work)->thr_id = thr_id; + (*work)->work_block = work_block; + (*work)->pool->works++; + + (*work)->mined = true; + (*work)->version = version; +} + +#endif + +/* Generates stratum based work based on the most recent notify information + * from the pool. This will keep generating work while a pool is down so we use + * other means to detect when the pool has died in stratum_thread */ +static void gen_stratum_work(struct pool *pool, struct work *work) +{ + unsigned char merkle_root[32], merkle_sha[64]; + uint32_t *data32, *swap32; + uint64_t nonce2le; + int i; + + cg_wlock(&pool->data_lock); + + /* Update coinbase. Always use an LE encoded nonce2 to fill in values + * from left to right and prevent overflow errors with small n2sizes */ + nonce2le = htole64(pool->nonce2); + memcpy(pool->coinbase + pool->nonce2_offset, &nonce2le, pool->n2size); + work->nonce2 = pool->nonce2++; + work->nonce2_len = pool->n2size; + + /* Downgrade to a read lock to read off the pool variables */ + cg_dwlock(&pool->data_lock); + + /* Generate merkle root */ + gen_hash(pool->coinbase, merkle_root, pool->coinbase_len); + memcpy(merkle_sha, merkle_root, 32); + for (i = 0; i < pool->merkles; i++) + { + memcpy(merkle_sha + 32, pool->swork.merkle_bin[i], 32); + gen_hash(merkle_sha, merkle_root, 64); + memcpy(merkle_sha, merkle_root, 32); + } + data32 = (uint32_t *)merkle_sha; + swap32 = (uint32_t *)merkle_root; + flip32(swap32, data32); + + /* Copy the data template from header_bin */ + memcpy(work->data, pool->header_bin, 112); + memcpy(work->data + 36, merkle_root, 32); + + /* Store the stratum work diff to check it still matches the pool's + * stratum diff when submitting shares */ + work->sdiff = pool->sdiff; + + /* Copy parameters required for share submission */ + work->job_id = strdup(pool->swork.job_id); + work->nonce1 = strdup(pool->nonce1); + work->ntime = strdup(pool->ntime); + cg_runlock(&pool->data_lock); + + if (opt_debug) + { + char *header, *merkle_hash; + + header = bin2hex(work->data, 112); + merkle_hash = bin2hex((const unsigned char *)merkle_root, 32); + //applog(LOG_DEBUG, "Generated stratum merkle %s", merkle_hash); + //applog(LOG_DEBUG, "Generated stratum header %s", header); + //applog(LOG_DEBUG, "Work job_id %s nonce2 %"PRIu64" ntime %s", work->job_id, + // work->nonce2, work->ntime); + free(header); + free(merkle_hash); + } + + calc_midstate(work); + set_target(work->target, work->sdiff); + + local_work++; + if((time(NULL) - local_work_lasttime) > 5) + { + int diff = local_work - local_work_last; + //applog(LOG_DEBUG, "local_work 5s gen work count:%d", diff/(time(NULL) - local_work_lasttime)); + local_work_lasttime = time(NULL); + local_work_last = local_work; + } + + work->pool = pool; + work->stratum = true; + work->nonce = 0; + work->longpoll = false; + work->getwork_mode = GETWORK_MODE_STRATUM; + work->work_block = work_block; + /* Nominally allow a driver to ntime roll 60 seconds */ + work->drv_rolllimit = 60; + calc_diff(work, work->sdiff); + + cgtime(&work->tv_staged); +} + +#ifdef HAVE_LIBCURL +static void gen_solo_work(struct pool *pool, struct work *work); + +/* Use the one instance of gbt_curl, protecting the bool with the gbt_lock but + * avoiding holding the lock once we've set the bool. */ +static void get_gbt_curl(struct pool *pool, int poll) +{ + cg_ilock(&pool->gbt_lock); + while (pool->gbt_curl_inuse) + { + cg_uilock(&pool->gbt_lock); + cgsleep_ms(poll); + cg_ilock(&pool->gbt_lock); + } + cg_ulock(&pool->gbt_lock); + pool->gbt_curl_inuse = true; + cg_wunlock(&pool->gbt_lock); +} + +/* No need for locking here */ +static inline void release_gbt_curl(struct pool *pool) +{ + pool->gbt_curl_inuse = false; +} + +static void update_gbt_solo(struct pool *pool) +{ + struct work *work = make_work(); + int rolltime; + json_t *val; + + get_gbt_curl(pool, 10); +retry: + /* Bitcoind doesn't like many open RPC connections. */ + curl_easy_setopt(pool->gbt_curl, CURLOPT_FORBID_REUSE, 1); + val = json_rpc_call(pool->gbt_curl, pool->rpc_url, pool->rpc_userpass, pool->rpc_req, + true, false, &rolltime, pool, false); + + if (likely(val)) + { + bool rc = work_decode(pool, work, val); + + if (rc) + { + __setup_gbt_solo(pool); + gen_solo_work(pool, work); + stage_work(work); + } + else + free_work(work); + json_decref(val); + } + else + { + applog(LOG_DEBUG, "Pool %d json_rpc_call failed on get gbt, retrying in 5s", + pool->pool_no); + if (++pool->seq_getfails > 5) + { + pool_died(pool); + goto out; + } + cgsleep_ms(5000); + goto retry; + } +out: + release_gbt_curl(pool); +} + +static void gen_solo_work(struct pool *pool, struct work *work) +{ + unsigned char merkle_root[32], merkle_sha[64]; + uint32_t *data32, *swap32; + struct timeval now; + uint64_t nonce2le; + int i; + + cgtime(&now); + if (now.tv_sec - pool->tv_lastwork.tv_sec > 60) + update_gbt_solo(pool); + + cg_wlock(&pool->gbt_lock); + + /* Update coinbase. Always use an LE encoded nonce2 to fill in values + * from left to right and prevent overflow errors with small n2sizes */ + nonce2le = htole64(pool->nonce2); + memcpy(pool->coinbase + pool->nonce2_offset, &nonce2le, pool->n2size); + work->nonce2 = pool->nonce2++; + work->nonce2_len = pool->n2size; + work->gbt_txns = pool->transactions + 1; + + /* Downgrade to a read lock to read off the pool variables */ + cg_dwlock(&pool->gbt_lock); + work->coinbase = bin2hex(pool->coinbase, pool->coinbase_len); + /* Generate merkle root */ + gen_hash(pool->coinbase, merkle_root, pool->coinbase_len); + memcpy(merkle_sha, merkle_root, 32); + for (i = 0; i < pool->merkles; i++) + { + unsigned char *merkle_bin; + + merkle_bin = pool->merklebin + (i * 32); + memcpy(merkle_sha + 32, merkle_bin, 32); + gen_hash(merkle_sha, merkle_root, 64); + memcpy(merkle_sha, merkle_root, 32); + } + data32 = (uint32_t *)merkle_sha; + swap32 = (uint32_t *)merkle_root; + flip32(swap32, data32); + + /* Copy the data template from header_bin */ + memcpy(work->data, pool->header_bin, 112); + memcpy(work->data + 36, merkle_root, 32); + + work->sdiff = pool->sdiff; + + /* Copy parameters required for share submission */ + work->ntime = strdup(pool->ntime); + memcpy(work->target, pool->gbt_target, 32); + cg_runlock(&pool->gbt_lock); + + if (opt_debug) + { + char *header, *merkle_hash; + + header = bin2hex(work->data, 112); + merkle_hash = bin2hex((const unsigned char *)merkle_root, 32); + applog(LOG_DEBUG, "Generated GBT solo merkle %s", merkle_hash); + applog(LOG_DEBUG, "Generated GBT solo header %s", header); + applog(LOG_DEBUG, "Work nonce2 %"PRIu64" ntime %s", work->nonce2, + work->ntime); + free(header); + free(merkle_hash); + } + + calc_midstate(work); + + local_work++; + work->gbt = true; + work->pool = pool; + work->nonce = 0; + work->longpoll = false; + work->getwork_mode = GETWORK_MODE_SOLO; + work->work_block = work_block; + /* Nominally allow a driver to ntime roll 60 seconds */ + work->drv_rolllimit = 60; + calc_diff(work, work->sdiff); + + cgtime(&work->tv_staged); +} +#endif + +/* The time difference in seconds between when this device last got work via + * get_work() and generated a valid share. */ +int share_work_tdiff(struct cgpu_info *cgpu) +{ + return last_getwork - cgpu->last_device_valid_work; +} + +static void set_benchmark_work(struct cgpu_info *cgpu, struct work *work) +{ + cgpu->lodiff += cgpu->direction; + if (cgpu->lodiff < 1) + cgpu->direction = 1; + if (cgpu->lodiff > 15) + { + cgpu->direction = -1; + if (++cgpu->hidiff > 15) + cgpu->hidiff = 0; + memcpy(work, &bench_hidiff_bins[cgpu->hidiff][0], 160); + } + else + memcpy(work, &bench_lodiff_bins[cgpu->lodiff][0], 160); +} + +struct work *get_work(struct thr_info *thr, const int thr_id) +{ + struct cgpu_info *cgpu = thr->cgpu; + struct work *work = NULL; + time_t diff_t; + + thread_reportout(thr); + applog(LOG_DEBUG, "Popping work from get queue to get work"); + diff_t = time(NULL); + while (!work) + { + work = hash_pop(true); + if (stale_work(work, false)) + { + discard_work(work); + wake_gws(); + } + } + diff_t = time(NULL) - diff_t; + /* Since this is a blocking function, we need to add grace time to + * the device's last valid work to not make outages appear to be + * device failures. */ + if (diff_t > 0) + { + applog(LOG_DEBUG, "Get work blocked for %d seconds", (int)diff_t); + cgpu->last_device_valid_work += diff_t; + } + applog(LOG_DEBUG, "Got work from get queue to get work for thread %d", thr_id); + + work->thr_id = thr_id; + if (opt_benchmark) + set_benchmark_work(cgpu, work); + + thread_reportin(thr); + work->mined = true; + work->device_diff = MIN(cgpu->drv->max_diff, work->work_difficulty); + work->device_diff = MAX(cgpu->drv->min_diff, work->device_diff); + return work; +} + +/* Submit a copy of the tested, statistic recorded work item asynchronously */ +static void submit_work_async(struct work *work) +{ + struct pool *pool = work->pool; + pthread_t submit_thread; + + cgtime(&work->tv_work_found); + if (opt_benchmark) + { + struct cgpu_info *cgpu = get_thr_cgpu(work->thr_id); + + mutex_lock(&stats_lock); + cgpu->accepted++; + total_accepted++; + pool->accepted++; + cgpu->diff_accepted += work->work_difficulty; + total_diff_accepted += work->work_difficulty; + pool->diff_accepted += work->work_difficulty; + mutex_unlock(&stats_lock); + + applog(LOG_NOTICE, "Accepted %s %d benchmark share nonce %08x", + cgpu->drv->name, cgpu->device_id, *(uint32_t *)(work->data + 64 + 12)); + return; + } + + if (stale_work(work, true)) + { + if (opt_submit_stale) + applog(LOG_NOTICE, "Pool %d stale share detected, submitting as user requested", pool->pool_no); + else if (pool->submit_old) + applog(LOG_NOTICE, "Pool %d stale share detected, submitting as pool requested", pool->pool_no); + else + { + applog(LOG_NOTICE, "Pool %d stale share detected, discarding", pool->pool_no); + sharelog("discard", work); + + mutex_lock(&stats_lock); + total_stale++; + pool->stale_shares++; + total_diff_stale += work->work_difficulty; + pool->diff_stale += work->work_difficulty; + mutex_unlock(&stats_lock); + + free_work(work); + return; + } + work->stale = true; + } + + if (work->stratum) + { + applog(LOG_DEBUG, "Pushing pool %d work to stratum queue", pool->pool_no); + if (unlikely(!tq_push(pool->stratum_q, work))) + { + applog(LOG_DEBUG, "Discarding work from removed pool"); + free_work(work); + } + } + else + { + applog(LOG_DEBUG, "Pushing submit work to work thread"); + if (unlikely(pthread_create(&submit_thread, NULL, submit_work_thread, (void *)work))) + quit(1, "Failed to create submit_work_thread"); + } +} + +void inc_hw_errors(struct thr_info *thr) +{ + forcelog(LOG_INFO, "%s %d: invalid nonce - HW error", thr->cgpu->drv->name, + thr->cgpu->device_id); + + mutex_lock(&stats_lock); + hw_errors++; + thr->cgpu->hw_errors++; + mutex_unlock(&stats_lock); + + thr->cgpu->drv->hw_error(thr); +} + +void inc_hw_errors_with_diff(struct thr_info *thr, int diff) +{ + applog(LOG_ERR, "%s%d: invalid nonce - HW error", thr->cgpu->drv->name, + thr->cgpu->device_id); + + mutex_lock(&stats_lock); + hw_errors += diff ; + thr->cgpu->hw_errors += diff; + mutex_unlock(&stats_lock); + + thr->cgpu->drv->hw_error(thr); +} + + + +void inc_dev_status(int max_fan, int max_temp) +{ + mutex_lock(&stats_lock); + g_max_fan = max_fan; + g_max_temp = max_temp; + mutex_unlock(&stats_lock); +} + +/* Fills in the work nonce and builds the output data in work->hash */ +static void rebuild_nonce(struct work *work, uint32_t nonce) +{ + uint32_t *work_nonce = (uint32_t *)(work->data + 64 + 12); + + *work_nonce = htole32(nonce); + + regen_hash(work); +} + +/* For testing a nonce against diff 1 */ +bool test_nonce(struct work *work, uint32_t nonce) +{ + uint32_t *hash_32 = (uint32_t *)(work->hash + 28); + + rebuild_nonce(work, nonce); + return (*hash_32 == 0); +} + +/* For testing a nonce against an arbitrary diff */ +bool test_nonce_diff(struct work *work, uint32_t nonce, double diff) +{ + uint64_t *hash64 = (uint64_t *)(work->hash + 24), diff64; + + rebuild_nonce(work, nonce); + diff64 = 0x00000000ffff0000ULL; + diff64 /= diff; + + return (le64toh(*hash64) <= diff64); +} + +static void update_work_stats(struct thr_info *thr, struct work *work) +{ + double test_diff = current_diff; + + work->share_diff = share_diff(work); + + if (unlikely(work->share_diff >= test_diff)) + { + work->block = true; + work->pool->solved++; + found_blocks++; + work->mandatory = true; + applog(LOG_NOTICE, "Found block for pool %d!", work->pool->pool_no); + } + + mutex_lock(&stats_lock); + total_diff1 += work->device_diff; + thr->cgpu->diff1 += work->device_diff; + work->pool->diff1 += work->device_diff; + thr->cgpu->last_device_valid_work = time(NULL); + mutex_unlock(&stats_lock); +} + +void inc_work_stats(struct thr_info *thr, struct pool *pool, int diff1) +{ + mutex_lock(&stats_lock); + total_diff1 += diff1; + thr->cgpu->diff1 += diff1; + if(pool) + { + pool->diff1 += diff1; + } + else + { + pool = current_pool(); + pool->diff1 += diff1; + } + thr->cgpu->last_device_valid_work = time(NULL); + mutex_unlock(&stats_lock); +} + + +/* To be used once the work has been tested to be meet diff1 and has had its + * nonce adjusted. Returns true if the work target is met. */ +bool submit_tested_work(struct thr_info *thr, struct work *work) +{ + struct work *work_out; + update_work_stats(thr, work); + + if (!fulltest(work->hash, work->target)) + { + applog(LOG_INFO, "%s %s %d: Share above target",__FUNCTION__, thr->cgpu->drv->name, + thr->cgpu->device_id); + return false; + } + work_out = copy_work(work); + submit_work_async(work_out); + return true; +} + +/* Rudimentary test to see if cgpu has returned the same nonce twice in a row which is + * always going to be a duplicate which should be reported as a hw error. */ +static bool new_nonce(struct thr_info *thr, uint32_t nonce) +{ + struct cgpu_info *cgpu = thr->cgpu; + + if (unlikely(cgpu->last_nonce == nonce)) + { + applog(LOG_INFO, "%s %d duplicate share detected as HW error", + cgpu->drv->name, cgpu->device_id); + return false; + } + cgpu->last_nonce = nonce; + return true; +} + +/* Returns true if nonce for work was a valid share and not a dupe of the very last + * nonce submitted by this device. */ +bool submit_nonce(struct thr_info *thr, struct work *work, uint32_t nonce) +{ + if (new_nonce(thr, nonce) && test_nonce(work, nonce)) + submit_tested_work(thr, work); + else + { + inc_hw_errors(thr); + return false; + } + + if (opt_benchfile && opt_benchfile_display) + benchfile_dspwork(work, nonce); + + return true; +} + +bool submit_nonce_1(struct thr_info *thr, struct work *work, uint32_t nonce, int * nofull) +{ + if(nofull) *nofull = 0; + if (test_nonce(work, nonce)) + { + update_work_stats(thr, work); + if (!fulltest(work->hash, work->target)) + { + if(nofull) *nofull = 1; + applog(LOG_INFO, "Share above target"); + return false; + } + } + else + { + inc_hw_errors(thr); + return false; + } + return true; +} + +void submit_nonce_2(struct work *work) +{ + struct work *work_out; + work_out = copy_work(work); + submit_work_async(work_out); +} + +bool submit_nonce_direct(struct thr_info *thr, struct work *work, uint32_t nonce) +{ + struct work *work_out; + uint32_t *work_nonce = (uint32_t *)(work->data + 64 + 12); + *work_nonce = htole32(nonce); + + work_out = copy_work(work); + submit_work_async(work_out); + return true; +} + +/* Allows drivers to submit work items where the driver has changed the ntime + * value by noffset. Must be only used with a work protocol that does not ntime + * roll itself intrinsically to generate work (eg stratum). We do not touch + * the original work struct, but the copy of it only. */ +bool submit_noffset_nonce(struct thr_info *thr, struct work *work_in, uint32_t nonce, + int noffset) +{ + struct work *work = make_work(); + bool ret = false; + + _copy_work(work, work_in, noffset); + if (!test_nonce(work, nonce)) + { + free_work(work); + inc_hw_errors(thr); + goto out; + } + update_work_stats(thr, work); + + if (opt_benchfile && opt_benchfile_display) + benchfile_dspwork(work, nonce); + + ret = true; + if (!fulltest(work->hash, work->target)) + { + free_work(work); + applog(LOG_INFO, "%s %d: Share above target", thr->cgpu->drv->name, + thr->cgpu->device_id); + goto out; + } + submit_work_async(work); + +out: + return ret; +} + +static inline bool abandon_work(struct work *work, struct timeval *wdiff, uint64_t hashes) +{ + if (wdiff->tv_sec > opt_scantime || hashes >= 0xfffffffe || + stale_work(work, false)) + return true; + return false; +} + +static void mt_disable(struct thr_info *mythr, const int thr_id, + struct device_drv *drv) +{ + applog(LOG_WARNING, "Thread %d being disabled", thr_id); + mythr->cgpu->rolling = 0; + applog(LOG_DEBUG, "Waiting on sem in miner thread"); + cgsem_wait(&mythr->sem); + applog(LOG_WARNING, "Thread %d being re-enabled", thr_id); + drv->thread_enable(mythr); +} + +/* The main hashing loop for devices that are slow enough to work on one work + * item at a time, without a queue, aborting work before the entire nonce + * range has been hashed if needed. */ +static void hash_sole_work(struct thr_info *mythr) +{ + const int thr_id = mythr->id; + struct cgpu_info *cgpu = mythr->cgpu; + struct device_drv *drv = cgpu->drv; + struct timeval getwork_start, tv_start, *tv_end, tv_workstart, tv_lastupdate; + struct cgminer_stats *dev_stats = &(cgpu->cgminer_stats); + struct cgminer_stats *pool_stats; + /* Try to cycle approximately 5 times before each log update */ + const long cycle = opt_log_interval / 5 ? : 1; + const bool primary = (!mythr->device_thread) || mythr->primary_thread; + struct timeval diff, sdiff, wdiff = {0, 0}; + uint32_t max_nonce = drv->can_limit_work(mythr); + int64_t hashes_done = 0; + + tv_end = &getwork_start; + cgtime(&getwork_start); + sdiff.tv_sec = sdiff.tv_usec = 0; + cgtime(&tv_lastupdate); + + while (likely(!cgpu->shutdown)) + { + struct work *work = get_work(mythr, thr_id); + int64_t hashes; + + mythr->work_restart = false; + cgpu->new_work = true; + + cgtime(&tv_workstart); + work->nonce = 0; + cgpu->max_hashes = 0; + if (!drv->prepare_work(mythr, work)) + { + applog(LOG_ERR, "work prepare failed, exiting " + "mining thread %d", thr_id); + break; + } + work->device_diff = MIN(drv->max_diff, work->work_difficulty); + work->device_diff = MAX(drv->min_diff, work->device_diff); + + do + { + cgtime(&tv_start); + + subtime(&tv_start, &getwork_start); + + addtime(&getwork_start, &dev_stats->getwork_wait); + if (time_more(&getwork_start, &dev_stats->getwork_wait_max)) + copy_time(&dev_stats->getwork_wait_max, &getwork_start); + if (time_less(&getwork_start, &dev_stats->getwork_wait_min)) + copy_time(&dev_stats->getwork_wait_min, &getwork_start); + dev_stats->getwork_calls++; + + pool_stats = &(work->pool->cgminer_stats); + + addtime(&getwork_start, &pool_stats->getwork_wait); + if (time_more(&getwork_start, &pool_stats->getwork_wait_max)) + copy_time(&pool_stats->getwork_wait_max, &getwork_start); + if (time_less(&getwork_start, &pool_stats->getwork_wait_min)) + copy_time(&pool_stats->getwork_wait_min, &getwork_start); + pool_stats->getwork_calls++; + + cgtime(&(work->tv_work_start)); + + /* Only allow the mining thread to be cancelled when + * it is not in the driver code. */ + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); + + thread_reportin(mythr); + hashes = drv->scanhash(mythr, work, work->nonce + max_nonce); + thread_reportout(mythr); + + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); + pthread_testcancel(); + + /* tv_end is == &getwork_start */ + cgtime(&getwork_start); + + if (unlikely(hashes == -1)) + { + applog(LOG_ERR, "%s %d failure, disabling!", drv->name, cgpu->device_id); + cgpu->deven = DEV_DISABLED; + dev_error(cgpu, REASON_THREAD_ZERO_HASH); + cgpu->shutdown = true; + break; + } + + hashes_done += hashes; + if (hashes > cgpu->max_hashes) + cgpu->max_hashes = hashes; + + timersub(tv_end, &tv_start, &diff); + sdiff.tv_sec += diff.tv_sec; + sdiff.tv_usec += diff.tv_usec; + if (sdiff.tv_usec > 1000000) + { + ++sdiff.tv_sec; + sdiff.tv_usec -= 1000000; + } + + timersub(tv_end, &tv_workstart, &wdiff); + + if (unlikely((long)sdiff.tv_sec < cycle)) + { + int mult; + + if (likely(max_nonce == 0xffffffff)) + continue; + + mult = 1000000 / ((sdiff.tv_usec + 0x400) / 0x400) + 0x10; + mult *= cycle; + if (max_nonce > (0xffffffff * 0x400) / mult) + max_nonce = 0xffffffff; + else + max_nonce = (max_nonce * mult) / 0x400; + } + else if (unlikely(sdiff.tv_sec > cycle)) + max_nonce = max_nonce * cycle / sdiff.tv_sec; + else if (unlikely(sdiff.tv_usec > 100000)) + max_nonce = max_nonce * 0x400 / (((cycle * 1000000) + sdiff.tv_usec) / (cycle * 1000000 / 0x400)); + + timersub(tv_end, &tv_lastupdate, &diff); + /* Update the hashmeter at most 5 times per second */ + if ((hashes_done && (diff.tv_sec > 0 || diff.tv_usec > 200000)) || + diff.tv_sec >= opt_log_interval) + { + hashmeter(thr_id, hashes_done); + hashes_done = 0; + copy_time(&tv_lastupdate, tv_end); + } + + if (unlikely(mythr->work_restart)) + { + /* Apart from device_thread 0, we stagger the + * starting of every next thread to try and get + * all devices busy before worrying about + * getting work for their extra threads */ + if (!primary) + { + struct timespec rgtp; + + rgtp.tv_sec = 0; + rgtp.tv_nsec = 250 * mythr->device_thread * 1000000; + nanosleep(&rgtp, NULL); + } + break; + } + + if (unlikely(mythr->pause || cgpu->deven != DEV_ENABLED)) + mt_disable(mythr, thr_id, drv); + + sdiff.tv_sec = sdiff.tv_usec = 0; + } + while (!abandon_work(work, &wdiff, cgpu->max_hashes)); + free_work(work); + } + cgpu->deven = DEV_DISABLED; +} + +/* Put a new unqueued work item in cgpu->unqueued_work under cgpu->qlock till + * the driver tells us it's full so that it may extract the work item using + * the get_queued() function which adds it to the hashtable on + * cgpu->queued_work. */ +static void fill_queue(struct thr_info *mythr, struct cgpu_info *cgpu, struct device_drv *drv, const int thr_id) +{ + do + { + bool need_work; + + /* Do this lockless just to know if we need more unqueued work. */ + need_work = (!cgpu->unqueued_work); + + /* get_work is a blocking function so do it outside of lock + * to prevent deadlocks with other locks. */ + if (need_work) + { + struct work *work = get_work(mythr, thr_id); + + wr_lock(&cgpu->qlock); + /* Check we haven't grabbed work somehow between + * checking and picking up the lock. */ + if (likely(!cgpu->unqueued_work)) + cgpu->unqueued_work = work; + else + need_work = false; + wr_unlock(&cgpu->qlock); + + if (unlikely(!need_work)) + discard_work(work); + } + /* The queue_full function should be used by the driver to + * actually place work items on the physical device if it + * does have a queue. */ + } + while (!drv->queue_full(cgpu)); +} + +/* Add a work item to a cgpu's queued hashlist */ +void __add_queued(struct cgpu_info *cgpu, struct work *work) +{ + cgpu->queued_count++; + HASH_ADD_INT(cgpu->queued_work, id, work); +} + +struct work *__get_queued(struct cgpu_info *cgpu) +{ + struct work *work = NULL; + + if (cgpu->unqueued_work) + { + work = cgpu->unqueued_work; + if (unlikely(stale_work(work, false))) + { + discard_work(work); + wake_gws(); + } + else + __add_queued(cgpu, work); + cgpu->unqueued_work = NULL; + } + + return work; +} + +/* This function is for retrieving one work item from the unqueued pointer and + * adding it to the hashtable of queued work. Code using this function must be + * able to handle NULL as a return which implies there is no work available. */ +struct work *get_queued(struct cgpu_info *cgpu) +{ + struct work *work; + + wr_lock(&cgpu->qlock); + work = __get_queued(cgpu); + wr_unlock(&cgpu->qlock); + + return work; +} + +void add_queued(struct cgpu_info *cgpu, struct work *work) +{ + wr_lock(&cgpu->qlock); + __add_queued(cgpu, work); + wr_unlock(&cgpu->qlock); +} + +/* Get fresh work and add it to cgpu's queued hashlist */ +struct work *get_queue_work(struct thr_info *thr, struct cgpu_info *cgpu, int thr_id) +{ + struct work *work = get_work(thr, thr_id); + + add_queued(cgpu, work); + return work; +} + +/* This function is for finding an already queued work item in the + * given que hashtable. Code using this function must be able + * to handle NULL as a return which implies there is no matching work. + * The calling function must lock access to the que if it is required. + * The common values for midstatelen, offset, datalen are 32, 64, 12 */ +struct work *__find_work_bymidstate(struct work *que, char *midstate, size_t midstatelen, char *data, int offset, size_t datalen) +{ + struct work *work, *tmp, *ret = NULL; + + HASH_ITER(hh, que, work, tmp) + { + if (memcmp(work->midstate, midstate, midstatelen) == 0 && + memcmp(work->data + offset, data, datalen) == 0) + { + ret = work; + break; + } + } + + return ret; +} + +/* This function is for finding an already queued work item in the + * device's queued_work hashtable. Code using this function must be able + * to handle NULL as a return which implies there is no matching work. + * The common values for midstatelen, offset, datalen are 32, 64, 12 */ +struct work *find_queued_work_bymidstate(struct cgpu_info *cgpu, char *midstate, size_t midstatelen, char *data, int offset, size_t datalen) +{ + struct work *ret; + + rd_lock(&cgpu->qlock); + ret = __find_work_bymidstate(cgpu->queued_work, midstate, midstatelen, data, offset, datalen); + rd_unlock(&cgpu->qlock); + + return ret; +} + +struct work *clone_queued_work_bymidstate(struct cgpu_info *cgpu, char *midstate, size_t midstatelen, char *data, int offset, size_t datalen) +{ + struct work *work, *ret = NULL; + + rd_lock(&cgpu->qlock); + work = __find_work_bymidstate(cgpu->queued_work, midstate, midstatelen, data, offset, datalen); + if (work) + ret = copy_work(work); + rd_unlock(&cgpu->qlock); + + return ret; +} + +/* This function is for finding an already queued work item in the + * given que hashtable. Code using this function must be able + * to handle NULL as a return which implies there is no matching work. + * The calling function must lock access to the que if it is required. */ +struct work *__find_work_byid(struct work *que, uint32_t id) +{ + struct work *work, *tmp, *ret = NULL; + + HASH_ITER(hh, que, work, tmp) + { + if (work->id == id) + { + ret = work; + break; + } + } + + return ret; +} + +struct work *find_queued_work_byid(struct cgpu_info *cgpu, uint32_t id) +{ + struct work *ret; + + rd_lock(&cgpu->qlock); + ret = __find_work_byid(cgpu->queued_work, id); + rd_unlock(&cgpu->qlock); + + return ret; +} + +struct work *clone_queued_work_byid(struct cgpu_info *cgpu, uint32_t id) +{ + struct work *work, *ret = NULL; + + rd_lock(&cgpu->qlock); + work = __find_work_byid(cgpu->queued_work, id); + if (work) + ret = copy_work(work); + rd_unlock(&cgpu->qlock); + + return ret; +} + +void __work_completed(struct cgpu_info *cgpu, struct work *work) +{ + cgpu->queued_count--; + HASH_DEL(cgpu->queued_work, work); +} + +/* This iterates over a queued hashlist finding work started more than secs + * seconds ago and discards the work as completed. The driver must set the + * work->tv_work_start value appropriately. Returns the number of items aged. */ +int age_queued_work(struct cgpu_info *cgpu, double secs) +{ + struct work *work, *tmp; + struct timeval tv_now; + int aged = 0; + + cgtime(&tv_now); + + wr_lock(&cgpu->qlock); + HASH_ITER(hh, cgpu->queued_work, work, tmp) + { + if (tdiff(&tv_now, &work->tv_work_start) > secs) + { + __work_completed(cgpu, work); + free_work(work); + aged++; + } + } + wr_unlock(&cgpu->qlock); + + return aged; +} + +/* This function should be used by queued device drivers when they're sure + * the work struct is no longer in use. */ +void work_completed(struct cgpu_info *cgpu, struct work *work) +{ + wr_lock(&cgpu->qlock); + __work_completed(cgpu, work); + wr_unlock(&cgpu->qlock); + + free_work(work); +} + +/* Combines find_queued_work_bymidstate and work_completed in one function + * withOUT destroying the work so the driver must free it. */ +struct work *take_queued_work_bymidstate(struct cgpu_info *cgpu, char *midstate, size_t midstatelen, char *data, int offset, size_t datalen) +{ + struct work *work; + + wr_lock(&cgpu->qlock); + work = __find_work_bymidstate(cgpu->queued_work, midstate, midstatelen, data, offset, datalen); + if (work) + __work_completed(cgpu, work); + wr_unlock(&cgpu->qlock); + + return work; +} + +void flush_queue(struct cgpu_info *cgpu) +{ + struct work *work = NULL; + + if (unlikely(!cgpu)) + return; + + /* Use only a trylock in case we get into a deadlock with a queueing + * function holding the read lock when we're called. */ + if (wr_trylock(&cgpu->qlock)) + return; + work = cgpu->unqueued_work; + cgpu->unqueued_work = NULL; + wr_unlock(&cgpu->qlock); + + if (work) + { + free_work(work); + applog(LOG_DEBUG, "Discarded queued work item"); + } +} + +/* This version of hash work is for devices that are fast enough to always + * perform a full nonce range and need a queue to maintain the device busy. + * Work creation and destruction is not done from within this function + * directly. */ +void hash_queued_work(struct thr_info *mythr) +{ + struct timeval tv_start = {0, 0}, tv_end; + struct cgpu_info *cgpu = mythr->cgpu; + struct device_drv *drv = cgpu->drv; + const int thr_id = mythr->id; + int64_t hashes_done = 0; + + while (likely(!cgpu->shutdown)) + { + struct timeval diff; + int64_t hashes; + + mythr->work_update = false; + + fill_queue(mythr, cgpu, drv, thr_id); + + hashes = drv->scanwork(mythr); + + /* Reset the bool here in case the driver looks for it + * synchronously in the scanwork loop. */ + mythr->work_restart = false; + + if (unlikely(hashes == -1 )) + { + applog(LOG_ERR, "%s %d failure, disabling!", drv->name, cgpu->device_id); + cgpu->deven = DEV_DISABLED; + dev_error(cgpu, REASON_THREAD_ZERO_HASH); + break; + } + + hashes_done += hashes; + cgtime(&tv_end); + timersub(&tv_end, &tv_start, &diff); + /* Update the hashmeter at most 5 times per second */ + if ((hashes_done && (diff.tv_sec > 0 || diff.tv_usec > 200000)) || + diff.tv_sec >= opt_log_interval) + { + hashmeter(thr_id, hashes_done); + hashes_done = 0; + copy_time(&tv_start, &tv_end); + } + + if (unlikely(mythr->pause || cgpu->deven != DEV_ENABLED)) + mt_disable(mythr, thr_id, drv); + + if (mythr->work_update) + drv->update_work(cgpu); + } + cgpu->deven = DEV_DISABLED; +} + +/* This version of hash_work is for devices drivers that want to do their own + * work management entirely, usually by using get_work(). Note that get_work + * is a blocking function and will wait indefinitely if no work is available + * so this must be taken into consideration in the driver. */ +void hash_driver_work(struct thr_info *mythr) +{ + struct timeval tv_start = {0, 0}, tv_end; + struct cgpu_info *cgpu = mythr->cgpu; + struct device_drv *drv = cgpu->drv; + const int thr_id = mythr->id; + int64_t hashes_done = 0; + + while (likely(!cgpu->shutdown)) + { + struct timeval diff; + int64_t hashes; + + mythr->work_update = false; + + hashes = drv->scanwork(mythr); + + /* Reset the bool here in case the driver looks for it + * synchronously in the scanwork loop. */ + mythr->work_restart = false; + + if (unlikely(hashes == -1 )) + { + applog(LOG_ERR, "%s %d failure, disabling!", drv->name, cgpu->device_id); + cgpu->deven = DEV_DISABLED; + dev_error(cgpu, REASON_THREAD_ZERO_HASH); + break; + } + + hashes_done += hashes; + cgtime(&tv_end); + timersub(&tv_end, &tv_start, &diff); + /* Update the hashmeter at most 5 times per second */ + if ((hashes_done && (diff.tv_sec > 0 || diff.tv_usec > 200000)) || + diff.tv_sec >= opt_log_interval) + { + hashmeter(thr_id, hashes_done); + hashes_done = 0; + copy_time(&tv_start, &tv_end); + } + + if (unlikely(mythr->pause || cgpu->deven != DEV_ENABLED)) + mt_disable(mythr, thr_id, drv); + + if (mythr->work_update) + { + mutex_lock(&update_job_lock); + drv->update_work(cgpu); + mutex_unlock(&update_job_lock); + } + } + cgpu->deven = DEV_DISABLED; +} + +void *miner_thread(void *userdata) +{ + struct thr_info *mythr = userdata; + const int thr_id = mythr->id; + struct cgpu_info *cgpu = mythr->cgpu; + struct device_drv *drv = cgpu->drv; + char threadname[16]; + + snprintf(threadname, sizeof(threadname), "%d/Miner", thr_id); + RenameThread(threadname); + + thread_reportout(mythr); + if (!drv->thread_init(mythr)) + { + dev_error(cgpu, REASON_THREAD_FAIL_INIT); + goto out; + } + + applog(LOG_DEBUG, "Waiting on sem in miner thread"); + cgsem_wait(&mythr->sem); + + cgpu->last_device_valid_work = time(NULL); + drv->hash_work(mythr); + drv->thread_shutdown(mythr); +out: + return NULL; +} + +enum +{ + STAT_SLEEP_INTERVAL = 1, + STAT_CTR_INTERVAL = 10000000, + FAILURE_INTERVAL = 30, +}; + +#ifdef HAVE_LIBCURL +/* Stage another work item from the work returned in a longpoll */ +static void convert_to_work(json_t *val, int rolltime, struct pool *pool, struct timeval *tv_lp, struct timeval *tv_lp_reply) +{ + struct work *work; + bool rc; + + work = make_work(); + + rc = work_decode(pool, work, val); + if (unlikely(!rc)) + { + applog(LOG_ERR, "Could not convert longpoll data to work"); + free_work(work); + return; + } + total_getworks++; + pool->getwork_requested++; + work->pool = pool; + work->rolltime = rolltime; + copy_time(&work->tv_getwork, tv_lp); + copy_time(&work->tv_getwork_reply, tv_lp_reply); + calc_diff(work, 0); + + if (pool->enabled == POOL_REJECTING) + work->mandatory = true; + + if (pool->has_gbt) + gen_gbt_work(pool, work); + work->longpoll = true; + work->getwork_mode = GETWORK_MODE_LP; + + /* We'll be checking this work item twice, but we already know it's + * from a new block so explicitly force the new block detection now + * rather than waiting for it to hit the stage thread. This also + * allows testwork to know whether LP discovered the block or not. */ + test_work_current(work); + + /* Don't use backup LPs as work if we have failover-only enabled. Use + * the longpoll work from a pool that has been rejecting shares as a + * way to detect when the pool has recovered. + */ + if (pool != current_pool() && opt_fail_only && pool->enabled != POOL_REJECTING) + { + free_work(work); + return; + } + + work = clone_work(work); + + applog(LOG_DEBUG, "Pushing converted work to stage thread"); + + stage_work(work); + applog(LOG_DEBUG, "Converted longpoll data to work"); +} + +/* If we want longpoll, enable it for the chosen default pool, or, if + * the pool does not support longpoll, find the first one that does + * and use its longpoll support */ +static struct pool *select_longpoll_pool(struct pool *cp) +{ + int i; + + if (cp->hdr_path || cp->has_gbt || cp->gbt_solo) + return cp; + for (i = 0; i < total_pools; i++) + { + struct pool *pool = pools[i]; + + if (pool->has_stratum || pool->hdr_path) + return pool; + } + return NULL; +} +#endif /* HAVE_LIBCURL */ + +/* This will make the longpoll thread wait till it's the current pool, or it + * has been flagged as rejecting, before attempting to open any connections. + */ +static void wait_lpcurrent(struct pool *pool) +{ + while (!cnx_needed(pool) && (pool->enabled == POOL_DISABLED || + (pool != current_pool() && pool_strategy != POOL_LOADBALANCE && + pool_strategy != POOL_BALANCE))) + { + mutex_lock(&lp_lock); + pthread_cond_wait(&lp_cond, &lp_lock); + mutex_unlock(&lp_lock); + } +} + +#ifdef HAVE_LIBCURL +static void *longpoll_thread(void *userdata) +{ + struct pool *cp = (struct pool *)userdata; + /* This *pool is the source of the actual longpoll, not the pool we've + * tied it to */ + struct timeval start, reply, end; + struct pool *pool = NULL; + char threadname[16]; + CURL *curl = NULL; + int failures = 0; + char lpreq[1024]; + char *lp_url; + int rolltime; + + snprintf(threadname, sizeof(threadname), "%d/Longpoll", cp->pool_no); + RenameThread(threadname); + +retry_pool: + pool = select_longpoll_pool(cp); + if (!pool) + { + applog(LOG_WARNING, "No suitable long-poll found for %s", cp->rpc_url); + while (!pool) + { + cgsleep_ms(60000); + pool = select_longpoll_pool(cp); + } + } + + if (pool->has_stratum) + { + applog(LOG_WARNING, "Block change for %s detection via %s stratum", + cp->rpc_url, pool->rpc_url); + goto out; + } + + if (pool->gbt_solo) + { + applog(LOG_WARNING, "Block change for %s detection via getblockcount polling", + cp->rpc_url); + while (42) + { + json_t *val, *res_val = NULL; + + if (unlikely(pool->removed)) + return NULL; + + cgtime(&start); + wait_lpcurrent(cp); + sprintf(lpreq, "{\"id\": 0, \"method\": \"getblockcount\"}\n"); + + /* We will be making another call immediately after this + * one to get the height so allow this curl to be reused.*/ + get_gbt_curl(pool, 500); + curl_easy_setopt(pool->gbt_curl, CURLOPT_FORBID_REUSE, 0); + val = json_rpc_call(pool->gbt_curl, pool->rpc_url, pool->rpc_userpass, lpreq, true, + false, &rolltime, pool, false); + release_gbt_curl(pool); + + if (likely(val)) + res_val = json_object_get(val, "result"); + if (likely(res_val)) + { + int height = json_integer_value(res_val); + const char *prev_hash; + + failures = 0; + json_decref(val); + if (height >= cp->height) + { + applog(LOG_WARNING, "Block height change to %d detected on pool %d", + height, cp->pool_no); + update_gbt_solo(pool); + continue; + } + + sprintf(lpreq, "{\"id\": 0, \"method\": \"getblockhash\", \"params\": [%d]}\n", height); + get_gbt_curl(pool, 500); + curl_easy_setopt(pool->gbt_curl, CURLOPT_FORBID_REUSE, 1); + val = json_rpc_call(pool->gbt_curl, pool->rpc_url, pool->rpc_userpass, + lpreq, true, false, &rolltime, pool, false); + release_gbt_curl(pool); + + if (val) + { + /* Do a comparison on a short stretch of + * the hash to make sure it hasn't changed + * due to mining on an orphan branch. */ + prev_hash = json_string_value(json_object_get(val, "result")); + if (unlikely(prev_hash && strncasecmp(prev_hash + 56, pool->prev_hash, 8))) + { + applog(LOG_WARNING, "Mining on orphan branch detected, switching!"); + update_gbt_solo(pool); + } + json_decref(val); + } + + cgsleep_ms(500); + } + else + { + if (val) + json_decref(val); + cgtime(&end); + if (end.tv_sec - start.tv_sec > 30) + continue; + if (failures == 1) + applog(LOG_WARNING, "longpoll failed for %s, retrying every 30s", lp_url); + cgsleep_ms(30000); + } + } + } + + curl = curl_easy_init(); + if (unlikely(!curl)) + quit (1, "Longpoll CURL initialisation failed"); + + /* Any longpoll from any pool is enough for this to be true */ + have_longpoll = true; + + wait_lpcurrent(cp); + + if (pool->has_gbt) + { + lp_url = pool->rpc_url; + applog(LOG_WARNING, "GBT longpoll ID activated for %s", lp_url); + } + else + { + strcpy(lpreq, getwork_req); + + lp_url = pool->lp_url; + if (cp == pool) + applog(LOG_WARNING, "Long-polling activated for %s", lp_url); + else + applog(LOG_WARNING, "Long-polling activated for %s via %s", cp->rpc_url, lp_url); + } + + while (42) + { + json_t *val, *soval; + + wait_lpcurrent(cp); + + cgtime(&start); + + /* Update the longpollid every time, but do it under lock to + * avoid races */ + if (pool->has_gbt) + { + cg_rlock(&pool->gbt_lock); + snprintf(lpreq, sizeof(lpreq), + "{\"id\": 0, \"method\": \"getblocktemplate\", \"params\": " + "[{\"capabilities\": [\"coinbasetxn\", \"workid\", \"coinbase/append\"], " + "\"longpollid\": \"%s\"}]}\n", pool->longpollid); + cg_runlock(&pool->gbt_lock); + } + + /* Longpoll connections can be persistent for a very long time + * and any number of issues could have come up in the meantime + * so always establish a fresh connection instead of relying on + * a persistent one. */ + curl_easy_setopt(curl, CURLOPT_FRESH_CONNECT, 1); + val = json_rpc_call(curl, lp_url, pool->rpc_userpass, + lpreq, false, true, &rolltime, pool, false); + + cgtime(&reply); + + if (likely(val)) + { + soval = json_object_get(json_object_get(val, "result"), "submitold"); + if (soval) + pool->submit_old = json_is_true(soval); + else + pool->submit_old = false; + convert_to_work(val, rolltime, pool, &start, &reply); + failures = 0; + json_decref(val); + } + else + { + /* Some pools regularly drop the longpoll request so + * only see this as longpoll failure if it happens + * immediately and just restart it the rest of the + * time. */ + cgtime(&end); + if (end.tv_sec - start.tv_sec > 30) + continue; + if (failures == 1) + applog(LOG_WARNING, "longpoll failed for %s, retrying every 30s", lp_url); + cgsleep_ms(30000); + } + + if (pool != cp) + { + pool = select_longpoll_pool(cp); + if (pool->has_stratum) + { + applog(LOG_WARNING, "Block change for %s detection via %s stratum", + cp->rpc_url, pool->rpc_url); + break; + } + if (unlikely(!pool)) + goto retry_pool; + } + + if (unlikely(pool->removed)) + break; + } + +out: + curl_easy_cleanup(curl); + + return NULL; +} +#else /* HAVE_LIBCURL */ +static void *longpoll_thread(void __maybe_unused *userdata) +{ + pthread_detach(pthread_self()); + return NULL; +} +#endif /* HAVE_LIBCURL */ + +void reinit_device(struct cgpu_info *cgpu) +{ + if (cgpu->deven == DEV_DISABLED) + return; + +#ifdef USE_USBUTILS + /* Attempt a usb device reset if the device has gone sick */ + if (cgpu->usbdev && cgpu->usbdev->handle) + libusb_reset_device(cgpu->usbdev->handle); +#endif + cgpu->drv->reinit_device(cgpu); +} + +static struct timeval rotate_tv; + +/* We reap curls if they are unused for over a minute */ +static void reap_curl(struct pool *pool) +{ + struct curl_ent *ent, *iter; + struct timeval now; + int reaped = 0; + + cgtime(&now); + + mutex_lock(&pool->pool_lock); + list_for_each_entry_safe(ent, iter, &pool->curlring, node) + { + if (pool->curls < 2) + break; + if (now.tv_sec - ent->tv.tv_sec > 300) + { + reaped++; + pool->curls--; + list_del(&ent->node); + curl_easy_cleanup(ent->curl); + free(ent); + } + } + mutex_unlock(&pool->pool_lock); + + if (reaped) + applog(LOG_DEBUG, "Reaped %d curl%s from pool %d", reaped, reaped > 1 ? "s" : "", pool->pool_no); +} + +/* Prune old shares we haven't had a response about for over 2 minutes in case + * the pool never plans to respond and we're just leaking memory. If we get a + * response beyond that time they will be seen as untracked shares. */ +static void prune_stratum_shares(struct pool *pool) +{ + struct stratum_share *sshare, *tmpshare; + time_t current_time = time(NULL); + int cleared = 0; + + mutex_lock(&sshare_lock); + HASH_ITER(hh, stratum_shares, sshare, tmpshare) + { + if (sshare->work->pool == pool && current_time > sshare->sshare_time + 120) + { + HASH_DEL(stratum_shares, sshare); + free_work(sshare->work); + free(sshare); + cleared++; + } + } + mutex_unlock(&sshare_lock); + + if (cleared) + { + applog(LOG_WARNING, "Lost %d shares due to no stratum share response from pool %d", + cleared, pool->pool_no); + pool->stale_shares += cleared; + total_stale += cleared; + } +} + +static void *watchpool_thread(void __maybe_unused *userdata) +{ + int intervals = 0; + + pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); + + RenameThread("Watchpool"); + + set_lowprio(); + + while (42) + { + struct timeval now; + int i; + + if (++intervals > 20) + intervals = 0; + cgtime(&now); + + for (i = 0; i < total_pools; i++) + { + struct pool *pool = pools[i]; + + if (!opt_benchmark && !opt_benchfile) + { + reap_curl(pool); + prune_stratum_shares(pool); + } + + /* Get a rolling utility per pool over 10 mins */ + if (intervals > 19) + { + double shares = pool->diff1 - pool->last_shares; + + pool->last_shares = pool->diff1; + pool->utility = (pool->utility + shares * 0.63) / 1.63; + pool->shares = pool->utility; + } + + if (pool->enabled == POOL_DISABLED) + continue; + + /* Don't start testing a pool if its test thread + * from startup is still doing its first attempt. */ + if (unlikely(pool->testing)) + continue; + + /* Test pool is idle once every minute */ + if (pool->idle && now.tv_sec - pool->tv_idle.tv_sec > 30) + { + if (pool_active(pool, true) && pool_tclear(pool, &pool->idle)) + pool_resus(pool); + else + cgtime(&pool->tv_idle); + } + + /* Only switch pools if the failback pool has been + * alive for more than 5 minutes to prevent + * intermittently failing pools from being used. */ + if (!pool->idle && pool_strategy == POOL_FAILOVER && pool->prio < cp_prio() && + now.tv_sec - pool->tv_idle.tv_sec > 300) + { + applog(LOG_WARNING, "Pool %d %s stable for 5 mins", + pool->pool_no, pool->rpc_url); + switch_pools(NULL); + } + } + + if (current_pool()->idle) + switch_pools(NULL); + + if (pool_strategy == POOL_ROTATE && now.tv_sec - rotate_tv.tv_sec > 60 * opt_rotate_period) + { + cgtime(&rotate_tv); + switch_pools(NULL); + } + + cgsleep_ms(30000); + + } + return NULL; +} + +/* Makes sure the hashmeter keeps going even if mining threads stall, updates + * the screen at regular intervals, and restarts threads if they appear to have + * died. */ +#define WATCHDOG_INTERVAL 2 +#define WATCHDOG_SICK_TIME 120 +#define WATCHDOG_DEAD_TIME 600 +#define WATCHDOG_SICK_COUNT (WATCHDOG_SICK_TIME/WATCHDOG_INTERVAL) +#define WATCHDOG_DEAD_COUNT (WATCHDOG_DEAD_TIME/WATCHDOG_INTERVAL) + +static void *watchdog_thread(void __maybe_unused *userdata) +{ + const unsigned int interval = WATCHDOG_INTERVAL; + struct timeval zero_tv; + + pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); + + RenameThread("Watchdog"); + + set_lowprio(); + memset(&zero_tv, 0, sizeof(struct timeval)); + cgtime(&rotate_tv); + + while (1) + { + int i; + struct timeval now; + + sleep(interval); + + discard_stale(); + + hashmeter(-1, 0); + +#ifdef HAVE_CURSES + if (curses_active_locked()) + { + struct cgpu_info *cgpu; + int count; + + change_logwinsize(); + curses_print_status(); + count = 0; + for (i = 0; i < total_devices; i++) + { + cgpu = get_devices(i); +#ifndef USE_USBUTILS + if (cgpu) +#else + if (cgpu && !cgpu->usbinfo.nodev) +#endif + curses_print_devstatus(cgpu, i, count++); + } +#ifdef USE_USBUTILS + for (i = 0; i < total_devices; i++) + { + cgpu = get_devices(i); + if (cgpu && cgpu->usbinfo.nodev) + curses_print_devstatus(cgpu, i, count++); + } +#endif + touchwin(statuswin); + wrefresh(statuswin); + touchwin(logwin); + wrefresh(logwin); + unlock_curses(); + } +#endif + + cgtime(&now); + + if (!sched_paused && !should_run()) + { + applog(LOG_WARNING, "Pausing execution as per stop time %02d:%02d scheduled", + schedstop.tm.tm_hour, schedstop.tm.tm_min); + if (!schedstart.enable) + { + quit(0, "Terminating execution as planned"); + break; + } + + applog(LOG_WARNING, "Will restart execution as scheduled at %02d:%02d", + schedstart.tm.tm_hour, schedstart.tm.tm_min); + sched_paused = true; + + rd_lock(&mining_thr_lock); + for (i = 0; i < mining_threads; i++) + mining_thr[i]->pause = true; + rd_unlock(&mining_thr_lock); + } + else if (sched_paused && should_run()) + { + applog(LOG_WARNING, "Restarting execution as per start time %02d:%02d scheduled", + schedstart.tm.tm_hour, schedstart.tm.tm_min); + if (schedstop.enable) + applog(LOG_WARNING, "Will pause execution as scheduled at %02d:%02d", + schedstop.tm.tm_hour, schedstop.tm.tm_min); + sched_paused = false; + + for (i = 0; i < mining_threads; i++) + { + struct thr_info *thr; + + thr = get_thread(i); + + /* Don't touch disabled devices */ + if (thr->cgpu->deven == DEV_DISABLED) + continue; + thr->pause = false; + applog(LOG_DEBUG, "Pushing sem post to thread %d", thr->id); + cgsem_post(&thr->sem); + } + } + + for (i = 0; i < total_devices; ++i) + { + struct cgpu_info *cgpu = get_devices(i); + struct thr_info *thr = cgpu->thr[0]; + enum dev_enable *denable; + char dev_str[8]; + + if (!thr) + continue; + + cgpu->drv->get_stats(cgpu); + + denable = &cgpu->deven; + snprintf(dev_str, sizeof(dev_str), "%s %d", cgpu->drv->name, cgpu->device_id); + + /* Thread is waiting on getwork or disabled */ + if (thr->getwork || *denable == DEV_DISABLED) + continue; + + if (cgpu->status != LIFE_WELL && (now.tv_sec - thr->last.tv_sec < WATCHDOG_SICK_TIME)) + { + if (cgpu->status != LIFE_INIT) + applog(LOG_ERR, "%s: Recovered, declaring WELL!", dev_str); + cgpu->status = LIFE_WELL; + cgpu->device_last_well = time(NULL); + } + else if (cgpu->status == LIFE_WELL && (now.tv_sec - thr->last.tv_sec > WATCHDOG_SICK_TIME)) + { + cgpu->rolling = 0; + cgpu->status = LIFE_SICK; + applog(LOG_ERR, "%s: Idle for more than 60 seconds, declaring SICK!", dev_str); + cgtime(&thr->sick); + + dev_error(cgpu, REASON_DEV_SICK_IDLE_60); + if (opt_restart) + { + applog(LOG_ERR, "%s: Attempting to restart", dev_str); + reinit_device(cgpu); + } + } + else if (cgpu->status == LIFE_SICK && (now.tv_sec - thr->last.tv_sec > WATCHDOG_DEAD_TIME)) + { + cgpu->status = LIFE_DEAD; + applog(LOG_ERR, "%s: Not responded for more than 10 minutes, declaring DEAD!", dev_str); + cgtime(&thr->sick); + + dev_error(cgpu, REASON_DEV_DEAD_IDLE_600); + } + else if (now.tv_sec - thr->sick.tv_sec > 60 && + (cgpu->status == LIFE_SICK || cgpu->status == LIFE_DEAD)) + { + /* Attempt to restart a GPU that's sick or dead once every minute */ + cgtime(&thr->sick); + if (opt_restart) + reinit_device(cgpu); + } + } + } + + return NULL; +} + +static void log_print_status(struct cgpu_info *cgpu) +{ + char logline[255]; + + get_statline(logline, sizeof(logline), cgpu); + applog(LOG_WARNING, "%s", logline); +} + +static void noop_get_statline(char __maybe_unused *buf, size_t __maybe_unused bufsiz, struct cgpu_info __maybe_unused *cgpu); +void blank_get_statline_before(char *buf, size_t bufsiz, struct cgpu_info __maybe_unused *cgpu); + +void print_summary(void) +{ + struct timeval diff; + int hours, mins, secs, i; + double utility, displayed_hashes, work_util; + + timersub(&total_tv_end, &total_tv_start, &diff); + hours = diff.tv_sec / 3600; + mins = (diff.tv_sec % 3600) / 60; + secs = diff.tv_sec % 60; + + utility = total_accepted / total_secs * 60; + work_util = total_diff1 / total_secs * 60; + + applog(LOG_WARNING, "\nSummary of runtime statistics:\n"); + applog(LOG_WARNING, "Started at %s", datestamp); + if (total_pools == 1) + applog(LOG_WARNING, "Pool: %s", pools[0]->rpc_url); + applog(LOG_WARNING, "Runtime: %d hrs : %d mins : %d secs", hours, mins, secs); + displayed_hashes = total_mhashes_done / total_secs; + + applog(LOG_WARNING, "Average hashrate: %.1f Mhash/s", displayed_hashes); + applog(LOG_WARNING, "Solved blocks: %d", found_blocks); + applog(LOG_WARNING, "Best share difficulty: %s", best_share); + applog(LOG_WARNING, "Share submissions: %"PRId64, total_accepted + total_rejected); + applog(LOG_WARNING, "Accepted shares: %"PRId64, total_accepted); + applog(LOG_WARNING, "Rejected shares: %"PRId64, total_rejected); + applog(LOG_WARNING, "Accepted difficulty shares: %1.f", total_diff_accepted); + applog(LOG_WARNING, "Rejected difficulty shares: %1.f", total_diff_rejected); + if (total_accepted || total_rejected) + applog(LOG_WARNING, "Reject ratio: %.1f%%", (double)(total_rejected * 100) / (double)(total_accepted + total_rejected)); + applog(LOG_WARNING, "Hardware errors: %d", hw_errors); + applog(LOG_WARNING, "Utility (accepted shares / min): %.2f/min", utility); + applog(LOG_WARNING, "Work Utility (diff1 shares solved / min): %.2f/min\n", work_util); + + applog(LOG_WARNING, "Stale submissions discarded due to new blocks: %"PRId64, total_stale); + applog(LOG_WARNING, "Unable to get work from server occasions: %d", total_go); + applog(LOG_WARNING, "Work items generated locally: %d", local_work); + applog(LOG_WARNING, "Submitting work remotely delay occasions: %d", total_ro); + applog(LOG_WARNING, "New blocks detected on network: %d\n", new_blocks); + + if (total_pools > 1) + { + for (i = 0; i < total_pools; i++) + { + struct pool *pool = pools[i]; + + applog(LOG_WARNING, "Pool: %s", pool->rpc_url); + if (pool->solved) + applog(LOG_WARNING, "SOLVED %d BLOCK%s!", pool->solved, pool->solved > 1 ? "S" : ""); + applog(LOG_WARNING, " Share submissions: %"PRId64, pool->accepted + pool->rejected); + applog(LOG_WARNING, " Accepted shares: %"PRId64, pool->accepted); + applog(LOG_WARNING, " Rejected shares: %"PRId64, pool->rejected); + applog(LOG_WARNING, " Accepted difficulty shares: %1.f", pool->diff_accepted); + applog(LOG_WARNING, " Rejected difficulty shares: %1.f", pool->diff_rejected); + if (pool->accepted || pool->rejected) + applog(LOG_WARNING, " Reject ratio: %.1f%%", (double)(pool->rejected * 100) / (double)(pool->accepted + pool->rejected)); + + applog(LOG_WARNING, " Items worked on: %d", pool->works); + applog(LOG_WARNING, " Stale submissions discarded due to new blocks: %d", pool->stale_shares); + applog(LOG_WARNING, " Unable to get work from server occasions: %d", pool->getfail_occasions); + applog(LOG_WARNING, " Submitting work remotely delay occasions: %d\n", pool->remotefail_occasions); + } + } + + applog(LOG_WARNING, "Summary of per device statistics:\n"); + for (i = 0; i < total_devices; ++i) + { + struct cgpu_info *cgpu = get_devices(i); + + cgpu->drv->get_statline_before = &blank_get_statline_before; + cgpu->drv->get_statline = &noop_get_statline; + log_print_status(cgpu); + } + + if (opt_shares) + { + applog(LOG_WARNING, "Mined %.0f accepted shares of %d requested\n", total_diff_accepted, opt_shares); + if (opt_shares > total_diff_accepted) + applog(LOG_WARNING, "WARNING - Mined only %.0f shares of %d requested.", total_diff_accepted, opt_shares); + } + applog(LOG_WARNING, " "); + + fflush(stderr); + fflush(stdout); +} + +static void clean_up(bool restarting) +{ +#ifdef USE_USBUTILS + usb_polling = false; + pthread_join(usb_poll_thread, NULL); + libusb_exit(NULL); +#endif + + cgtime(&total_tv_end); +#ifdef WIN32 + timeEndPeriod(1); +#endif +#ifdef HAVE_CURSES + disable_curses(); +#endif + if (!restarting && !opt_realquiet && successful_connect) + print_summary(); + + curl_global_cleanup(); +} + +/* Should all else fail and we're unable to clean up threads due to locking + * issues etc, just silently exit. */ +static void *killall_thread(void __maybe_unused *arg) +{ + pthread_detach(pthread_self()); + pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); + sleep(5); + exit(1); + return NULL; +} + +void __quit(int status, bool clean) +{ + pthread_t killall_t; + + if (unlikely(pthread_create(&killall_t, NULL, killall_thread, NULL))) + exit(1); + + if (clean) + clean_up(false); +#ifdef HAVE_CURSES + else + disable_curses(); +#endif + +#if defined(unix) || defined(__APPLE__) + if (forkpid > 0) + { + kill(forkpid, SIGTERM); + forkpid = 0; + } +#endif + pthread_cancel(killall_t); + + exit(status); +} + +void _quit(int status) +{ + __quit(status, true); +} + +#ifdef HAVE_CURSES +char *curses_input(const char *query) +{ + char *input; + + echo(); + input = malloc(255); + if (!input) + quit(1, "Failed to malloc input"); + leaveok(logwin, false); + wlogprint("%s:\n", query); + wgetnstr(logwin, input, 255); + if (!strlen(input)) + strcpy(input, "-1"); + leaveok(logwin, true); + noecho(); + return input; +} +#endif + +static bool pools_active = false; + +static void *test_pool_thread(void *arg) +{ + struct pool *pool = (struct pool *)arg; + + if (!pool->blocking) + pthread_detach(pthread_self()); +retry: + if (pool_active(pool, false)) + { + pool_tset(pool, &pool->lagging); + pool_tclear(pool, &pool->idle); + bool first_pool = false; + + cg_wlock(&control_lock); + if (!pools_active) + { + currentpool = pool; + if (pool->pool_no != 0) + first_pool = true; + pools_active = true; + } + cg_wunlock(&control_lock); + + if (unlikely(first_pool)) + applog(LOG_NOTICE, "Switching to pool %d %s - first alive pool", pool->pool_no, pool->rpc_url); + + pool_resus(pool); + switch_pools(NULL); + } + else + { + pool_died(pool); + sleep(5); + goto retry; + } + + pool->testing = false; + + return NULL; +} + +/* Always returns true that the pool details were added unless we are not + * live, implying this is the only pool being added, so if no pools are + * active it returns false. */ +bool add_pool_details(struct pool *pool, bool live, char *url, char *user, char *pass) +{ + size_t siz; + + url = get_proxy(url, pool); + + pool->rpc_url = url; + pool->rpc_user = user; + pool->rpc_pass = pass; + siz = strlen(pool->rpc_user) + strlen(pool->rpc_pass) + 2; + pool->rpc_userpass = malloc(siz); + if (!pool->rpc_userpass) + quit(1, "Failed to malloc userpass"); + snprintf(pool->rpc_userpass, siz, "%s:%s", pool->rpc_user, pool->rpc_pass); + + pool->testing = true; + pool->idle = true; + pool->blocking = !live; + enable_pool(pool); + + pthread_create(&pool->test_thread, NULL, test_pool_thread, (void *)pool); + if (!live) + { + pthread_join(pool->test_thread, NULL); + return pools_active; + } + return true; +} + +#ifdef HAVE_CURSES +static bool input_pool(bool live) +{ + char *url = NULL, *user = NULL, *pass = NULL; + struct pool *pool; + bool ret = false; + + immedok(logwin, true); + wlogprint("Input server details.\n"); + + url = curses_input("URL"); + if (!strcmp(url, "-1")) + goto out; + + user = curses_input("Username"); + if (!strcmp(user, "-1")) + goto out; + + pass = curses_input("Password"); + if (!strcmp(pass, "-1")) + { + free(pass); + pass = strdup(""); + } + + pool = add_pool(); + + if (!detect_stratum(pool, url) && strncmp(url, "http://", 7) && + strncmp(url, "https://", 8)) + { + char *httpinput; + + httpinput = malloc(256); + if (!httpinput) + quit(1, "Failed to malloc httpinput"); + strcpy(httpinput, "http://"); + strncat(httpinput, url, 248); + free(url); + url = httpinput; + } + + ret = add_pool_details(pool, live, url, user, pass); +out: + immedok(logwin, false); + + if (!ret) + { + free(url); + free(user); + free(pass); + } + return ret; +} +#endif + +#if defined(unix) || defined(__APPLE__) +static void fork_monitor() +{ + // Make a pipe: [readFD, writeFD] + int pfd[2]; + int r = pipe(pfd); + + if (r < 0) + { + perror("pipe - failed to create pipe for --monitor"); + exit(1); + } + + // Make stderr write end of pipe + fflush(stderr); + r = dup2(pfd[1], 2); + if (r < 0) + { + perror("dup2 - failed to alias stderr to write end of pipe for --monitor"); + exit(1); + } + r = close(pfd[1]); + if (r < 0) + { + perror("close - failed to close write end of pipe for --monitor"); + exit(1); + } + + // Don't allow a dying monitor to kill the main process + sighandler_t sr0 = signal(SIGPIPE, SIG_IGN); + sighandler_t sr1 = signal(SIGPIPE, SIG_IGN); + if (SIG_ERR == sr0 || SIG_ERR == sr1) + { + perror("signal - failed to edit signal mask for --monitor"); + exit(1); + } + + // Fork a child process + forkpid = fork(); + if (forkpid < 0) + { + perror("fork - failed to fork child process for --monitor"); + exit(1); + } + + // Child: launch monitor command + if (0 == forkpid) + { + // Make stdin read end of pipe + r = dup2(pfd[0], 0); + if (r < 0) + { + perror("dup2 - in child, failed to alias read end of pipe to stdin for --monitor"); + exit(1); + } + close(pfd[0]); + if (r < 0) + { + perror("close - in child, failed to close read end of pipe for --monitor"); + exit(1); + } + + // Launch user specified command + execl("/bin/bash", "/bin/bash", "-c", opt_stderr_cmd, (char*)NULL); + perror("execl - in child failed to exec user specified command for --monitor"); + exit(1); + } + + // Parent: clean up unused fds and bail + r = close(pfd[0]); + if (r < 0) + { + perror("close - failed to close read end of pipe for --monitor"); + exit(1); + } +} +#endif // defined(unix) + +#ifdef HAVE_CURSES +static void enable_curses_windows(void) +{ + int x,y; + + getmaxyx(mainwin, y, x); + statuswin = newwin(logstart, x, 0, 0); + leaveok(statuswin, true); + logwin = newwin(y - logcursor, 0, logcursor, 0); + idlok(logwin, true); + scrollok(logwin, true); + leaveok(logwin, true); + cbreak(); + noecho(); +} +void enable_curses(void) +{ + lock_curses(); + if (curses_active) + { + unlock_curses(); + return; + } + + mainwin = initscr(); + enable_curses_windows(); + curses_active = true; + statusy = logstart; + unlock_curses(); +} +#endif + +static int cgminer_id_count = 0; + +/* Various noop functions for drivers that don't support or need their + * variants. */ +static void noop_reinit_device(struct cgpu_info __maybe_unused *cgpu) +{ +} + +void blank_get_statline_before(char __maybe_unused *buf,size_t __maybe_unused bufsiz, struct cgpu_info __maybe_unused *cgpu) +{ +} + +static void noop_get_statline(char __maybe_unused *buf, size_t __maybe_unused bufsiz, struct cgpu_info __maybe_unused *cgpu) +{ +} + +static bool noop_get_stats(struct cgpu_info __maybe_unused *cgpu) +{ + return true; +} + +static bool noop_thread_prepare(struct thr_info __maybe_unused *thr) +{ + return true; +} + +static uint64_t noop_can_limit_work(struct thr_info __maybe_unused *thr) +{ + return 0xffffffff; +} + +static bool noop_thread_init(struct thr_info __maybe_unused *thr) +{ + return true; +} + +static bool noop_prepare_work(struct thr_info __maybe_unused *thr, struct work __maybe_unused *work) +{ + return true; +} + +static void noop_hw_error(struct thr_info __maybe_unused *thr) +{ +} + +static void noop_thread_shutdown(struct thr_info __maybe_unused *thr) +{ +} + +static void noop_thread_enable(struct thr_info __maybe_unused *thr) +{ +} + +static void noop_detect(bool __maybe_unused hotplug) +{ +} + +static struct api_data *noop_get_api_stats(struct cgpu_info __maybe_unused *cgpu) +{ + return NULL; +} + +static void noop_hash_work(struct thr_info __maybe_unused *thr) +{ +} + +#define noop_flush_work noop_reinit_device +#define noop_update_work noop_reinit_device +#define noop_queue_full noop_get_stats +#define noop_zero_stats noop_reinit_device +#define noop_identify_device noop_reinit_device + +/* Fill missing driver drv functions with noops */ +void fill_device_drv(struct device_drv *drv) +{ + if (!drv->drv_detect) + drv->drv_detect = &noop_detect; + if (!drv->reinit_device) + drv->reinit_device = &noop_reinit_device; + if (!drv->get_statline_before) + drv->get_statline_before = &blank_get_statline_before; + if (!drv->get_statline) + drv->get_statline = &noop_get_statline; + if (!drv->get_stats) + drv->get_stats = &noop_get_stats; + if (!drv->thread_prepare) + drv->thread_prepare = &noop_thread_prepare; + if (!drv->can_limit_work) + drv->can_limit_work = &noop_can_limit_work; + if (!drv->thread_init) + drv->thread_init = &noop_thread_init; + if (!drv->prepare_work) + drv->prepare_work = &noop_prepare_work; + if (!drv->hw_error) + drv->hw_error = &noop_hw_error; + if (!drv->thread_shutdown) + drv->thread_shutdown = &noop_thread_shutdown; + if (!drv->thread_enable) + drv->thread_enable = &noop_thread_enable; + if (!drv->hash_work) + drv->hash_work = &hash_sole_work; + if (!drv->flush_work) + drv->flush_work = &noop_flush_work; + if (!drv->update_work) + drv->update_work = &noop_update_work; + if (!drv->queue_full) + drv->queue_full = &noop_queue_full; + if (!drv->zero_stats) + drv->zero_stats = &noop_zero_stats; + /* If drivers support internal diff they should set a max_diff or + * we will assume they don't and set max to 1. */ + if (!drv->max_diff) + drv->max_diff = 1; +} + +void null_device_drv(struct device_drv *drv) +{ + drv->drv_detect = &noop_detect; + drv->reinit_device = &noop_reinit_device; + drv->get_statline_before = &blank_get_statline_before; + drv->get_statline = &noop_get_statline; + drv->get_api_stats = &noop_get_api_stats; + drv->get_stats = &noop_get_stats; + drv->identify_device = &noop_identify_device; + drv->set_device = NULL; + + drv->thread_prepare = &noop_thread_prepare; + drv->can_limit_work = &noop_can_limit_work; + drv->thread_init = &noop_thread_init; + drv->prepare_work = &noop_prepare_work; + + /* This should make the miner thread just exit */ + drv->hash_work = &noop_hash_work; + + drv->hw_error = &noop_hw_error; + drv->thread_shutdown = &noop_thread_shutdown; + drv->thread_enable = &noop_thread_enable; + + drv->zero_stats = &noop_zero_stats; + + drv->hash_work = &noop_hash_work; + + drv->queue_full = &noop_queue_full; + drv->flush_work = &noop_flush_work; + drv->update_work = &noop_update_work; + + drv->zero_stats = &noop_zero_stats; + drv->max_diff = 1; + drv->min_diff = 1; +} + +void enable_device(struct cgpu_info *cgpu) +{ + cgpu->deven = DEV_ENABLED; + + wr_lock(&devices_lock); + devices[cgpu->cgminer_id = cgminer_id_count++] = cgpu; + wr_unlock(&devices_lock); + + if (hotplug_mode) + new_threads += cgpu->threads; + else + mining_threads += cgpu->threads; + + rwlock_init(&cgpu->qlock); + cgpu->queued_work = NULL; +} + +struct _cgpu_devid_counter +{ + char name[4]; + int lastid; + UT_hash_handle hh; +}; + +static void adjust_mostdevs(void) +{ + if (total_devices - zombie_devs > most_devices) + most_devices = total_devices - zombie_devs; +} + +#ifdef USE_ICARUS +bool icarus_get_device_id(struct cgpu_info *cgpu) +{ + static struct _cgpu_devid_counter *devids = NULL; + struct _cgpu_devid_counter *d; + + HASH_FIND_STR(devids, cgpu->drv->name, d); + if (d) + return (d->lastid + 1); + else + return 0; +} +#endif + +bool add_cgpu(struct cgpu_info *cgpu) +{ + static struct _cgpu_devid_counter *devids = NULL; + struct _cgpu_devid_counter *d; + + HASH_FIND_STR(devids, cgpu->drv->name, d); + if (d) + cgpu->device_id = ++d->lastid; + else + { + d = malloc(sizeof(*d)); + memcpy(d->name, cgpu->drv->name, sizeof(d->name)); + cgpu->device_id = d->lastid = 0; + HASH_ADD_STR(devids, name, d); + } + + wr_lock(&devices_lock); + devices = realloc(devices, sizeof(struct cgpu_info *) * (total_devices + new_devices + 2)); + wr_unlock(&devices_lock); + + mutex_lock(&stats_lock); + cgpu->last_device_valid_work = time(NULL); + mutex_unlock(&stats_lock); + + if (hotplug_mode) + devices[total_devices + new_devices++] = cgpu; + else + devices[total_devices++] = cgpu; + + adjust_mostdevs(); +#ifdef USE_USBUTILS + if (cgpu->usbdev && !cgpu->unique_id && cgpu->usbdev->serial_string && + strlen(cgpu->usbdev->serial_string) > 4) + cgpu->unique_id = str_text(cgpu->usbdev->serial_string); +#endif + return true; +} + +struct device_drv *copy_drv(struct device_drv *drv) +{ + struct device_drv *copy; + + if (unlikely(!(copy = malloc(sizeof(*copy))))) + { + quit(1, "Failed to allocate device_drv copy of %s (%s)", + drv->name, drv->copy ? "copy" : "original"); + } + memcpy(copy, drv, sizeof(*copy)); + copy->copy = true; + return copy; +} + +#ifdef USE_USBUTILS +static void hotplug_process(void) +{ + struct thr_info *thr; + int i, j; + + for (i = 0; i < new_devices; i++) + { + struct cgpu_info *cgpu; + int dev_no = total_devices + i; + + cgpu = devices[dev_no]; + enable_device(cgpu); + cgpu->cgminer_stats.getwork_wait_min.tv_sec = MIN_SEC_UNSET; + cgpu->rolling = cgpu->total_mhashes = 0; + } + + wr_lock(&mining_thr_lock); + mining_thr = realloc(mining_thr, sizeof(thr) * (mining_threads + new_threads + 1)); + + if (!mining_thr) + quit(1, "Failed to hotplug realloc mining_thr"); + for (i = 0; i < new_threads; i++) + { + mining_thr[mining_threads + i] = calloc(1, sizeof(*thr)); + if (!mining_thr[mining_threads + i]) + quit(1, "Failed to hotplug calloc mining_thr[%d]", i); + } + + // Start threads + for (i = 0; i < new_devices; ++i) + { + struct cgpu_info *cgpu = devices[total_devices]; + cgpu->thr = malloc(sizeof(*cgpu->thr) * (cgpu->threads+1)); + cgpu->thr[cgpu->threads] = NULL; + cgpu->status = LIFE_INIT; + cgtime(&(cgpu->dev_start_tv)); + + for (j = 0; j < cgpu->threads; ++j) + { + thr = __get_thread(mining_threads); + thr->id = mining_threads; + thr->cgpu = cgpu; + thr->device_thread = j; + + if (!cgpu->drv->thread_prepare(thr)) + { + null_device_drv(cgpu->drv); + cgpu->deven = DEV_DISABLED; + continue; + } + + if (unlikely(thr_info_create(thr, NULL, miner_thread, thr))) + quit(1, "hotplug thread %d create failed", thr->id); + + cgpu->thr[j] = thr; + + /* Enable threads for devices set not to mine but disable + * their queue in case we wish to enable them later */ + if (cgpu->deven != DEV_DISABLED) + { + applog(LOG_DEBUG, "Pushing sem post to thread %d", thr->id); + cgsem_post(&thr->sem); + } + + mining_threads++; + } + total_devices++; + applog(LOG_WARNING, "Hotplug: %s added %s %i", cgpu->drv->dname, cgpu->drv->name, cgpu->device_id); + } + wr_unlock(&mining_thr_lock); + + adjust_mostdevs(); +#ifdef HAVE_CURSES + switch_logsize(true); +#endif +} + +#define DRIVER_DRV_DETECT_HOTPLUG(X) X##_drv.drv_detect(true); + +static void *hotplug_thread(void __maybe_unused *userdata) +{ + pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); + + RenameThread("Hotplug"); + + hotplug_mode = true; + + cgsleep_ms(5000); + + while (0x2a) + { +// Version 0.1 just add the devices on - worry about using nodev later + + if (hotplug_time == 0) + cgsleep_ms(5000); + else + { + new_devices = 0; + new_threads = 0; + + /* Use the DRIVER_PARSE_COMMANDS macro to detect all + * devices */ + DRIVER_PARSE_COMMANDS(DRIVER_DRV_DETECT_HOTPLUG) + + if (new_devices) + hotplug_process(); + + // hotplug_time >0 && <=9999 + cgsleep_ms(hotplug_time * 1000); + } + } + + return NULL; +} +#endif + +static void probe_pools(void) +{ + int i; + + for (i = 0; i < total_pools; i++) + { + struct pool *pool = pools[i]; + + pool->testing = true; + pthread_create(&pool->test_thread, NULL, test_pool_thread, (void *)pool); + } +} + +#define DRIVER_FILL_DEVICE_DRV(X) fill_device_drv(&X##_drv); +#define DRIVER_DRV_DETECT_ALL(X) X##_drv.drv_detect(false); + +#ifdef USE_USBUTILS +static void *libusb_poll_thread(void __maybe_unused *arg) +{ + struct timeval tv_end = {1, 0}; + + RenameThread("USBPoll"); + + while (usb_polling) + libusb_handle_events_timeout_completed(NULL, &tv_end, NULL); + + /* Cancel any cancellable usb transfers */ + cancel_usb_transfers(); + + /* Keep event handling going until there are no async transfers in + * flight. */ + do + { + libusb_handle_events_timeout_completed(NULL, &tv_end, NULL); + } + while (async_usb_transfers()); + + return NULL; +} + +static void initialise_usb(void) +{ + int err = libusb_init(NULL); + + if (err) + { + fprintf(stderr, "libusb_init() failed err %d", err); + fflush(stderr); + quit(1, "libusb_init() failed"); + } + initialise_usblocks(); + usb_polling = true; + pthread_create(&usb_poll_thread, NULL, libusb_poll_thread, NULL); +} +#else +#define initialise_usb() {} +#endif + +int main(int argc, char *argv[]) +{ + struct sigaction handler; + struct work *work = NULL; + bool pool_msg = false; + struct thr_info *thr; + struct block *block; + int i, j, slept = 0; + unsigned int k; + char *s; + + g_logfile_enable = false; + strcpy(g_logfile_path, "bmminer.log"); + strcpy(g_logfile_openflag, "a+"); + /* This dangerous functions tramples random dynamically allocated + * variables so do it before anything at all */ + if (unlikely(curl_global_init(CURL_GLOBAL_ALL))) + early_quit(1, "Failed to curl_global_init"); + +# ifdef __linux + /* If we're on a small lowspec platform with only one CPU, we should + * yield after dropping a lock to allow a thread waiting for it to be + * able to get CPU time to grab the lock. */ + if (sysconf(_SC_NPROCESSORS_ONLN) == 1) + selective_yield = &sched_yield; +#endif + +#if LOCK_TRACKING + // Must be first + if (unlikely(pthread_mutex_init(&lockstat_lock, NULL))) + quithere(1, "Failed to pthread_mutex_init lockstat_lock errno=%d", errno); +#endif + + initial_args = malloc(sizeof(char *) * (argc + 1)); + for (i = 0; i < argc; i++) + initial_args[i] = strdup(argv[i]); + initial_args[argc] = NULL; + + mutex_init(&hash_lock); + mutex_init(&update_job_lock); + mutex_init(&console_lock); + cglock_init(&control_lock); + mutex_init(&stats_lock); + mutex_init(&sharelog_lock); + cglock_init(&ch_lock); + mutex_init(&sshare_lock); + rwlock_init(&blk_lock); + rwlock_init(&netacc_lock); + rwlock_init(&mining_thr_lock); + rwlock_init(&devices_lock); + + mutex_init(&lp_lock); + if (unlikely(pthread_cond_init(&lp_cond, NULL))) + early_quit(1, "Failed to pthread_cond_init lp_cond"); + + mutex_init(&restart_lock); + if (unlikely(pthread_cond_init(&restart_cond, NULL))) + early_quit(1, "Failed to pthread_cond_init restart_cond"); + + if (unlikely(pthread_cond_init(&gws_cond, NULL))) + early_quit(1, "Failed to pthread_cond_init gws_cond"); + + /* Create a unique get work queue */ + getq = tq_new(); + if (!getq) + early_quit(1, "Failed to create getq"); + /* We use the getq mutex as the staged lock */ + stgd_lock = &getq->mutex; + + initialise_usb(); + + snprintf(packagename, sizeof(packagename), "%s %s", PACKAGE, VERSION); + + handler.sa_handler = &sighandler; + handler.sa_flags = 0; + sigemptyset(&handler.sa_mask); + sigaction(SIGTERM, &handler, &termhandler); + sigaction(SIGINT, &handler, &inthandler); +#ifndef WIN32 + signal(SIGPIPE, SIG_IGN); +#else + timeBeginPeriod(1); +#endif + opt_kernel_path = alloca(PATH_MAX); + strcpy(opt_kernel_path, CGMINER_PREFIX); + cgminer_path = alloca(PATH_MAX); + s = strdup(argv[0]); + strcpy(cgminer_path, dirname(s)); + free(s); + strcat(cgminer_path, "/"); + + devcursor = 8; + logstart = devcursor + 1; + logcursor = logstart + 1; + + block = calloc(sizeof(struct block), 1); + if (unlikely(!block)) + quit (1, "main OOM"); + for (i = 0; i < 36; i++) + strcat(block->hash, "0"); + HASH_ADD_STR(blocks, hash, block); + strcpy(current_hash, block->hash); + + INIT_LIST_HEAD(&scan_devices); + + /* parse command line */ + opt_register_table(opt_config_table, + "Options for both config file and command line"); + opt_register_table(opt_cmdline_table, + "Options for command line only"); + + opt_parse(&argc, argv, applog_and_exit); + if (argc != 1) + early_quit(1, "Unexpected extra commandline arguments"); + + if (!config_loaded) + load_default_config(); + + if (opt_benchmark || opt_benchfile) + { + struct pool *pool; + + pool = add_pool(); + pool->rpc_url = malloc(255); + if (opt_benchfile) + strcpy(pool->rpc_url, "Benchfile"); + else + strcpy(pool->rpc_url, "Benchmark"); + pool->rpc_user = pool->rpc_url; + pool->rpc_pass = pool->rpc_url; + pool->rpc_userpass = pool->rpc_url; + pool->sockaddr_url = pool->rpc_url; + strncpy(pool->diff, "?", sizeof(pool->diff)-1); + pool->diff[sizeof(pool->diff)-1] = '\0'; + enable_pool(pool); + pool->idle = false; + successful_connect = true; + + for (i = 0; i < 16; i++) + { + hex2bin(&bench_hidiff_bins[i][0], &bench_hidiffs[i][0], 160); + hex2bin(&bench_lodiff_bins[i][0], &bench_lodiffs[i][0], 160); + } + set_target(bench_target, 32); + } + + if(opt_version_path) + { + FILE * fpversion = fopen(opt_version_path, "rb"); + char tmp[256] = {0}; + int len = 0; + char * start = 0; + if(fpversion == NULL) + { + applog(LOG_ERR, "Open miner version file %s error", opt_version_path); + } + else + { + len = fread(tmp, 1, 256, fpversion); + if(len <= 0) + { + applog(LOG_ERR, "Read miner version file %s error %d", opt_version_path, len); + } + else + { + start = strstr(tmp, "\n"); + if(start == NULL) + { + strcpy(g_miner_compiletime, tmp); + } + else + { + memcpy(g_miner_compiletime, tmp, start-tmp); + strcpy(g_miner_type, start+1); + } + if(g_miner_compiletime[strlen(g_miner_compiletime)-1] == '\n') + g_miner_compiletime[strlen(g_miner_compiletime)-1] = 0; + if(g_miner_compiletime[strlen(g_miner_compiletime)-1] == '\r') + g_miner_compiletime[strlen(g_miner_compiletime)-1] = 0; + if(g_miner_type[strlen(g_miner_type)-1] == '\n') + g_miner_type[strlen(g_miner_type)-1] = 0; + if(g_miner_type[strlen(g_miner_type)-1] == '\r') + g_miner_type[strlen(g_miner_type)-1] = 0; + } + } + applog(LOG_ERR, "Miner compile time: %s type: %s", g_miner_compiletime, g_miner_type); + } + + if(opt_logfile_path) + { + g_logfile_enable = true; + strcpy(g_logfile_path, opt_logfile_path); + if(opt_logfile_openflag) + { + strcpy(g_logfile_openflag, opt_logfile_openflag); + } + applog(LOG_ERR, "Log file path: %s Open flag: %s", g_logfile_path, g_logfile_openflag); + } + + if(opt_logwork_path) + { + char szfilepath[256] = {0}; + if(opt_logwork_asicnum) + { + if(strlen(opt_logwork_asicnum) <= 0) + { + quit(1, "Log work asic num empty"); + } + g_logwork_asicnum = atoi(opt_logwork_asicnum); + if(g_logwork_asicnum != 1 && g_logwork_asicnum != 32 && g_logwork_asicnum != 64) + { + quit(1, "Log work asic num must be 1, 32, 64"); + } + applog(LOG_ERR, "Log work path: %s Asic num: %s", opt_logwork_path, opt_logwork_asicnum); + } + else + { + applog(LOG_ERR, "Log work path: %s", opt_logwork_path); + } + + sprintf(szfilepath, "%s.txt", opt_logwork_path); + g_logwork_file = fopen(szfilepath, "a+"); + applog(LOG_ERR, "Log work open file %s", szfilepath); + + if(g_logwork_asicnum == 1) + { + sprintf(szfilepath, "%s%02d.txt", opt_logwork_path, g_logwork_asicnum); + g_logwork_files[0] = fopen(szfilepath, "a+"); + applog(LOG_ERR, "Log work open asic %d file %s", g_logwork_asicnum, szfilepath); + } + else if(g_logwork_asicnum == 32 || g_logwork_asicnum == 64) + { + for(i = 0; i <= g_logwork_asicnum; i++) + { + sprintf(szfilepath, "%s%02d_%02d.txt", opt_logwork_path, g_logwork_asicnum, i); + g_logwork_files[i] = fopen(szfilepath, "a+"); + applog(LOG_ERR, "Log work open asic %d file %s", g_logwork_asicnum, szfilepath); + } + } + + if(opt_logwork_diff) + { + for(i = 0; i <= 64; i++) + { + sprintf(szfilepath, "%s_diff_%02d.txt", opt_logwork_path, i); + g_logwork_diffs[i] = fopen(szfilepath, "a+"); + applog(LOG_ERR, "Log work open diff file %s", szfilepath); + } + } + } + +#ifdef HAVE_CURSES + if (opt_realquiet || opt_display_devs) + use_curses = false; + + if (use_curses) + enable_curses(); +#endif + + applog(LOG_WARNING, "Started %s", packagename); + if (cnfbuf) + { + applog(LOG_NOTICE, "Loaded configuration file %s", cnfbuf); + switch (fileconf_load) + { + case 0: + applog(LOG_WARNING, "Fatal JSON error in configuration file."); + applog(LOG_WARNING, "Configuration file could not be used."); + break; + case -1: + applog(LOG_WARNING, "Error in configuration file, partially loaded."); + if (use_curses) + applog(LOG_WARNING, "Start bmminer with -T to see what failed to load."); + break; + default: + break; + } + free(cnfbuf); + cnfbuf = NULL; + } + + strcat(opt_kernel_path, "/"); + + if (want_per_device_stats) + opt_log_output = true; + + if (opt_scantime < 0) + opt_scantime = 60; + + total_control_threads = 8; + control_thr = calloc(total_control_threads, sizeof(*thr)); + if (!control_thr) + early_quit(1, "Failed to calloc control_thr"); + + gwsched_thr_id = 0; + +#ifdef USE_USBUTILS + usb_initialise(); + + // before device detection + cgsem_init(&usb_resource_sem); + usbres_thr_id = 1; + thr = &control_thr[usbres_thr_id]; + if (thr_info_create(thr, NULL, usb_resource_thread, thr)) + early_quit(1, "usb resource thread create failed"); + pthread_detach(thr->pth); +#endif + + /* Use the DRIVER_PARSE_COMMANDS macro to fill all the device_drvs */ + DRIVER_PARSE_COMMANDS(DRIVER_FILL_DEVICE_DRV) + + /* Use the DRIVER_PARSE_COMMANDS macro to detect all devices */ + DRIVER_PARSE_COMMANDS(DRIVER_DRV_DETECT_ALL) + + if (opt_display_devs) + { + applog(LOG_ERR, "Devices detected:"); + for (i = 0; i < total_devices; ++i) + { + struct cgpu_info *cgpu = devices[i]; + if (cgpu->name) + applog(LOG_ERR, " %2d. %s %d: %s (driver: %s)", i, cgpu->drv->name, cgpu->device_id, cgpu->name, cgpu->drv->dname); + else + applog(LOG_ERR, " %2d. %s %d (driver: %s)", i, cgpu->drv->name, cgpu->device_id, cgpu->drv->dname); + } + early_quit(0, "%d devices listed", total_devices); + } + + mining_threads = 0; + for (i = 0; i < total_devices; ++i) + enable_device(devices[i]); + +#ifdef USE_USBUTILS + if (!total_devices) + { + applog(LOG_WARNING, "No devices detected!"); + applog(LOG_WARNING, "Waiting for USB hotplug devices or press q to quit"); + } +#else + if (!total_devices) + early_quit(1, "All devices disabled, cannot mine!"); +#endif + + most_devices = total_devices; + + load_temp_cutoffs(); + + for (i = 0; i < total_devices; ++i) + devices[i]->cgminer_stats.getwork_wait_min.tv_sec = MIN_SEC_UNSET; + + if (!opt_compact) + { + logstart += most_devices; + logcursor = logstart + 1; +#ifdef HAVE_CURSES + check_winsizes(); +#endif + } + + if (!total_pools) + { + applog(LOG_WARNING, "Need to specify at least one pool server."); +#ifdef HAVE_CURSES + if (!use_curses || !input_pool(false)) +#endif + early_quit(1, "Pool setup failed"); + } + + for (i = 0; i < total_pools; i++) + { + struct pool *pool = pools[i]; + size_t siz; + + pool->cgminer_stats.getwork_wait_min.tv_sec = MIN_SEC_UNSET; + pool->cgminer_pool_stats.getwork_wait_min.tv_sec = MIN_SEC_UNSET; + + if (!pool->rpc_userpass) + { + if (!pool->rpc_pass) + pool->rpc_pass = strdup(""); + if (!pool->rpc_user) + early_quit(1, "No login credentials supplied for pool %u %s", i, pool->rpc_url); + siz = strlen(pool->rpc_user) + strlen(pool->rpc_pass) + 2; + pool->rpc_userpass = malloc(siz); + if (!pool->rpc_userpass) + early_quit(1, "Failed to malloc userpass"); + snprintf(pool->rpc_userpass, siz, "%s:%s", pool->rpc_user, pool->rpc_pass); + } + } + /* Set the currentpool to pool 0 */ + currentpool = pools[0]; + +#ifdef HAVE_SYSLOG_H + if (use_syslog) + openlog(PACKAGE, LOG_PID, LOG_USER); +#endif + +#if defined(unix) || defined(__APPLE__) + if (opt_stderr_cmd) + fork_monitor(); +#endif // defined(unix) + + mining_thr = calloc(mining_threads, sizeof(thr)); + if (!mining_thr) + early_quit(1, "Failed to calloc mining_thr"); + for (i = 0; i < mining_threads; i++) + { + mining_thr[i] = calloc(1, sizeof(*thr)); + if (!mining_thr[i]) + early_quit(1, "Failed to calloc mining_thr[%d]", i); + } + + // Start threads + k = 0; + for (i = 0; i < total_devices; ++i) + { + struct cgpu_info *cgpu = devices[i]; + cgpu->thr = malloc(sizeof(*cgpu->thr) * (cgpu->threads+1)); + cgpu->thr[cgpu->threads] = NULL; + cgpu->status = LIFE_INIT; + + for (j = 0; j < cgpu->threads; ++j, ++k) + { + thr = get_thread(k); + thr->id = k; + thr->cgpu = cgpu; + thr->device_thread = j; + + if (!cgpu->drv->thread_prepare(thr)) + continue; + + if (unlikely(thr_info_create(thr, NULL, miner_thread, thr))) + early_quit(1, "thread %d create failed", thr->id); + + cgpu->thr[j] = thr; + + /* Enable threads for devices set not to mine but disable + * their queue in case we wish to enable them later */ + if (cgpu->deven != DEV_DISABLED) + { + applog(LOG_DEBUG, "Pushing sem post to thread %d", thr->id); + cgsem_post(&thr->sem); + } + } + } + + if (opt_benchmark || opt_benchfile) + goto begin_bench; + + for (i = 0; i < total_pools; i++) + { + struct pool *pool = pools[i]; + + enable_pool(pool); + pool->idle = true; + } + + /* Look for at least one active pool before starting */ + applog(LOG_NOTICE, "Probing for an alive pool"); + probe_pools(); + do + { + sleep(1); + slept++; + } + while (!pools_active && slept < 60); + + while (!pools_active) + { + if (!pool_msg) + { + applog(LOG_ERR, "No servers were found that could be used to get work from."); + applog(LOG_ERR, "Please check the details from the list below of the servers you have input"); + applog(LOG_ERR, "Most likely you have input the wrong URL, forgotten to add a port, or have not set up workers"); + for (i = 0; i < total_pools; i++) + { + struct pool *pool = pools[i]; + + applog(LOG_WARNING, "Pool: %d URL: %s User: %s Password: %s", + i, pool->rpc_url, pool->rpc_user, pool->rpc_pass); + } + pool_msg = true; + if (use_curses) + applog(LOG_ERR, "Press any key to exit, or bmminer will wait indefinitely for an alive pool."); + } + if (!use_curses) + early_quit(0, "No servers could be used! Exiting."); +#ifdef HAVE_CURSES + touchwin(logwin); + wrefresh(logwin); + halfdelay(10); + if (getch() != ERR) + early_quit(0, "No servers could be used! Exiting."); + cbreak(); +#endif + }; + +begin_bench: + total_mhashes_done = 0; + for(i = 0; i < CG_LOCAL_MHASHES_MAX_NUM; i++) + { + g_local_mhashes_dones[i] = 0; + } + g_local_mhashes_index = 0; + for (i = 0; i < total_devices; i++) + { + struct cgpu_info *cgpu = devices[i]; + + cgpu->rolling = cgpu->total_mhashes = 0; + } + + cgtime(&total_tv_start); + cgtime(&total_tv_end); + cgtime(&tv_hashmeter); + get_datestamp(datestamp, sizeof(datestamp), &total_tv_start); + + watchpool_thr_id = 2; + thr = &control_thr[watchpool_thr_id]; + /* start watchpool thread */ + if (thr_info_create(thr, NULL, watchpool_thread, NULL)) + early_quit(1, "watchpool thread create failed"); + pthread_detach(thr->pth); + + watchdog_thr_id = 3; + thr = &control_thr[watchdog_thr_id]; + /* start watchdog thread */ + if (thr_info_create(thr, NULL, watchdog_thread, NULL)) + early_quit(1, "watchdog thread create failed"); + pthread_detach(thr->pth); + + /* Create API socket thread */ + api_thr_id = 5; + thr = &control_thr[api_thr_id]; + if (thr_info_create(thr, NULL, api_thread, thr)) + early_quit(1, "API thread create failed"); + +#ifdef USE_USBUTILS + hotplug_thr_id = 6; + thr = &control_thr[hotplug_thr_id]; + if (thr_info_create(thr, NULL, hotplug_thread, thr)) + early_quit(1, "hotplug thread create failed"); + pthread_detach(thr->pth); +#endif + +#ifdef HAVE_CURSES + /* Create curses input thread for keyboard input. Create this last so + * that we know all threads are created since this can call kill_work + * to try and shut down all previous threads. */ + input_thr_id = 7; + thr = &control_thr[input_thr_id]; + if (thr_info_create(thr, NULL, input_thread, thr)) + early_quit(1, "input thread create failed"); + pthread_detach(thr->pth); +#endif + + /* Just to be sure */ + if (total_control_threads != 8) + early_quit(1, "incorrect total_control_threads (%d) should be 8", total_control_threads); + + set_highprio(); + + /* Once everything is set up, main() becomes the getwork scheduler */ + while (42) + { + int ts, max_staged = max_queue; + struct pool *pool, *cp; + bool lagging = false; + + if (opt_work_update) + signal_work_update(); + opt_work_update = false; + cp = current_pool(); + + /* If the primary pool is a getwork pool and cannot roll work, + * try to stage one extra work per mining thread */ + if (!pool_localgen(cp) && !staged_rollable) + max_staged += mining_threads; + + mutex_lock(stgd_lock); + ts = __total_staged(); + + if (!pool_localgen(cp) && !ts && !opt_fail_only) + lagging = true; + + /* Wait until hash_pop tells us we need to create more work */ + if (ts > max_staged) + { + if (work_emptied && max_queue < opt_queue) + { + max_queue++; + work_emptied = false; + } + work_filled = true; + pthread_cond_wait(&gws_cond, stgd_lock); + ts = __total_staged(); + } + mutex_unlock(stgd_lock); + + if (ts > max_staged) + { + /* Keeps slowly generating work even if it's not being + * used to keep last_getwork incrementing and to see + * if pools are still alive. */ + if (work_emptied && max_queue < opt_queue) + { + max_queue++; + work_emptied = false; + } + work_filled = true; + work = hash_pop(false); + if (work) + discard_work(work); + continue; + } + + if (work) + discard_work(work); + work = make_work(); + + if (lagging && !pool_tset(cp, &cp->lagging)) + { + applog(LOG_WARNING, "Pool %d not providing work fast enough", cp->pool_no); + cp->getfail_occasions++; + total_go++; + if (!pool_localgen(cp) && max_queue < opt_queue) + applog(LOG_INFO, "Increasing queue to %d", ++max_queue); + } + pool = select_pool(lagging); + retry: + if (pool->has_stratum) + { + while (!pool->stratum_active || !pool->stratum_notify) + { + struct pool *altpool = select_pool(true); + + cgsleep_ms(5000); + if (altpool != pool) + { + pool = altpool; + goto retry; + } + } + gen_stratum_work(pool, work); + applog(LOG_DEBUG, "Generated stratum work"); + stage_work(work); + continue; + } + + if (opt_benchfile) + { + get_benchfile_work(work); + applog(LOG_DEBUG, "Generated benchfile work"); + stage_work(work); + continue; + } + else if (opt_benchmark) + { + get_benchmark_work(work); + applog(LOG_DEBUG, "Generated benchmark work"); + stage_work(work); + continue; + } + +#ifdef HAVE_LIBCURL + struct curl_ent *ce; + + if (pool->gbt_solo) + { + while (pool->idle) + { + struct pool *altpool = select_pool(true); + + cgsleep_ms(5000); + if (altpool != pool) + { + pool = altpool; + goto retry; + } + } + gen_solo_work(pool, work); + applog(LOG_DEBUG, "Generated GBT SOLO work"); + stage_work(work); + continue; + } + + if (pool->has_gbt) + { + while (pool->idle) + { + struct pool *altpool = select_pool(true); + + cgsleep_ms(5000); + if (altpool != pool) + { + pool = altpool; + goto retry; + } + } + gen_gbt_work(pool, work); + applog(LOG_DEBUG, "Generated GBT work"); + stage_work(work); + continue; + } + + if (clone_available()) + { + applog(LOG_DEBUG, "Cloned getwork work"); + free_work(work); + continue; + } + + work->pool = pool; + ce = pop_curl_entry(pool); + /* obtain new work from bitcoin via JSON-RPC */ + if (!get_upstream_work(work, ce->curl)) + { + applog(LOG_DEBUG, "Pool %d json_rpc_call failed on get work, retrying in 5s", pool->pool_no); + /* Make sure the pool just hasn't stopped serving + * requests but is up as we'll keep hammering it */ + if (++pool->seq_getfails > mining_threads + opt_queue) + pool_died(pool); + cgsleep_ms(5000); + push_curl_entry(ce, pool); + pool = select_pool(!opt_fail_only); + free_work(work); + goto retry; + } + if (ts >= max_staged) + pool_tclear(pool, &pool->lagging); + if (pool_tclear(pool, &pool->idle)) + pool_resus(pool); + + applog(LOG_DEBUG, "Generated getwork work"); + stage_work(work); + push_curl_entry(ce, pool); +#endif + } + + return 0; +} diff --git a/compat.h b/compat.h new file mode 100644 index 0000000..ed18baa --- /dev/null +++ b/compat.h @@ -0,0 +1,88 @@ +#ifndef __COMPAT_H__ +#define __COMPAT_H__ + +#ifdef WIN32 +#include "config.h" +#include +#include +#include +#include + +#include "miner.h" // for timersub +#include "util.h" + +#include + +#ifndef HAVE_LIBWINPTHREAD +static inline int nanosleep(const struct timespec *req, struct timespec *rem) +{ + struct timeval tstart; + DWORD msecs; + + cgtime(&tstart); + msecs = (req->tv_sec * 1000) + ((999999 + req->tv_nsec) / 1000000); + + if (SleepEx(msecs, true) == WAIT_IO_COMPLETION) { + if (rem) { + struct timeval tdone, tnow, tleft; + tdone.tv_sec = tstart.tv_sec + req->tv_sec; + tdone.tv_usec = tstart.tv_usec + ((999 + req->tv_nsec) / 1000); + if (tdone.tv_usec > 1000000) { + tdone.tv_usec -= 1000000; + ++tdone.tv_sec; + } + + cgtime(&tnow); + if (timercmp(&tnow, &tdone, >)) + return 0; + timersub(&tdone, &tnow, &tleft); + + rem->tv_sec = tleft.tv_sec; + rem->tv_nsec = tleft.tv_usec * 1000; + } + errno = EINTR; + return -1; + } + return 0; +} +#endif + +static inline int sleep(unsigned int secs) +{ + struct timespec req, rem; + req.tv_sec = secs; + req.tv_nsec = 0; + if (!nanosleep(&req, &rem)) + return 0; + return rem.tv_sec + (rem.tv_nsec ? 1 : 0); +} + +enum { + PRIO_PROCESS = 0, +}; + +static inline int setpriority(__maybe_unused int which, __maybe_unused int who, __maybe_unused int prio) +{ + /* FIXME - actually do something */ + return 0; +} + +typedef unsigned long int ulong; +typedef unsigned short int ushort; +typedef unsigned int uint; + +#ifndef __SUSECONDS_T_TYPE +typedef long suseconds_t; +#endif + +#ifdef HAVE_LIBWINPTHREAD +#define PTH(thr) ((thr)->pth) +#else +#define PTH(thr) ((thr)->pth.p) +#endif + +#else +#define PTH(thr) ((thr)->pth) +#endif /* WIN32 */ + +#endif /* __COMPAT_H__ */ diff --git a/compat/.gitignore b/compat/.gitignore new file mode 100644 index 0000000..8e687f8 --- /dev/null +++ b/compat/.gitignore @@ -0,0 +1,3 @@ +libusb-1.0/libusb/libusb-1.0.la +libusb-1.0/libusb/*.lo +libusb-1.0/libusb/os/*.lo diff --git a/compat/Makefile.am b/compat/Makefile.am new file mode 100644 index 0000000..5a32214 --- /dev/null +++ b/compat/Makefile.am @@ -0,0 +1,8 @@ + +SUBDIRS = jansson-2.6 + +if WANT_USBUTILS +if WANT_STATIC_LIBUSB +SUBDIRS += libusb-1.0 +endif +endif diff --git a/compat/jansson-2.6/CHANGES b/compat/jansson-2.6/CHANGES new file mode 100644 index 0000000..99d1647 --- /dev/null +++ b/compat/jansson-2.6/CHANGES @@ -0,0 +1,583 @@ +Version 2.6 +=========== + +Released 2014-02-11 + +* Security: + + - CVE-2013-6401: The hash function used by the hashtable + implementation has been changed, and is automatically seeded with + random data when the first JSON object is created. This prevents + an attacker from causing large JSON objects with specially crafted + keys perform poorly. + +* New features: + + - `json_object_seed()`: Set the seed value of the hash function. + +* Bug fixes: + + - Include CMake specific files in the release tarball. + +* Documentation: + + - Fix tutorial source to send a User-Agent header, which is now + required by the GitHub API. + + - Set all memory to zero in secure_free() example. + + +Version 2.5 +=========== + +Released 2013-09-19 + +* New features: + + - `json_pack()` and friends: Add format specifiers ``s#``, ``+`` and + ``+#``. + + - Add ``JSON_DECODE_INT_AS_REAL`` decoding flag to treat all numbers + as real in the decoder (#123). + + - Add `json_array_foreach()`, paralleling `json_object_foreach()` + (#118). + +* Bug fixes: + + - `json_dumps()` and friends: Don't crash if json is *NULL* and + ``JSON_ENCODE_ANY`` is set. + + - Fix a theoretical integer overflow in `jsonp_strdup()`. + + - Fix `l_isxdigit()` macro (#97). + + - Fix an off-by-one error in `json_array_remove()`. + +* Build: + + - Support CMake in addition to GNU Autotools (#106, #107, #112, + #115, #120, #127). + + - Support building for Android (#109). + + - Don't use ``-Werror`` by default. + + - Support building and testing with VPATH (#93). + + - Fix compilation when ``NDEBUG`` is defined (#128) + +* Tests: + + - Fix a refleak in ``test/bin/json_process.c``. + +* Documentation: + + - Clarify the return value of `json_load_callback_t`. + + - Document how to circumvent problems with separate heaps on Windows. + + - Fix memory leaks and warnings in ``github_commits.c``. + + - Use `json_decref()` properly in tutorial. + +* Other: + + - Make it possible to forward declare ``struct json_t``. + + +Version 2.4 +=========== + +Released 2012-09-23 + +* New features: + + - Add `json_boolean()` macro that returns the JSON true or false + value based on its argument (#86). + + - Add `json_load_callback()` that calls a callback function + repeatedly to read the JSON input (#57). + + - Add JSON_ESCAPE_SLASH encoding flag to escape all occurences of + ``/`` with ``\/``. + +* Bug fixes: + + - Check for and reject NaN and Inf values for reals. Encoding these + values resulted in invalid JSON. + + - Fix `json_real_set()` to return -1 on error. + +* Build: + + - Jansson now builds on Windows with Visual Studio 2010, and + includes solution and project files in ``win32/vs2010/`` + directory. + + - Fix build warnings (#77, #78). + + - Add ``-no-undefined`` to LDFLAGS (#90). + +* Tests: + + - Fix the symbol exports test on Linux/PPC64 (#88). + +* Documentation: + + - Fix typos (#73, #84). + + +Version 2.3.1 +============= + +Released 2012-04-20 + +* Build issues: + + - Only use ``long long`` if ``strtoll()`` is also available. + +* Documentation: + + - Fix the names of library version constants in documentation. (#52) + + - Change the tutorial to use GitHub API v3. (#65) + +* Tests: + + - Make some tests locale independent. (#51) + + - Distribute the library exports test in the tarball. + + - Make test run on shells that don't support the ``export FOO=bar`` + syntax. + + +Version 2.3 +=========== + +Released 2012-01-27 + +* New features: + + - `json_unpack()` and friends: Add support for optional object keys + with the ``{s?o}`` syntax. + + - Add `json_object_update_existing()` and + `json_object_update_missing()`, for updating only existing keys or + only adding missing keys to an object. (#37) + + - Add `json_object_foreach()` for more convenient iteration over + objects. (#45, #46) + + - When decoding JSON, write the number of bytes that were read from + input to ``error.position`` also on success. This is handy with + ``JSON_DISABLE_EOF_CHECK``. + + - Add support for decoding any JSON value, not just arrays or + objects. The support is enabled with the new ``JSON_DECODE_ANY`` + flag. Patch by Andrea Marchesini. (#4) + +* Bug fixes + + - Avoid problems with object's serial number growing too big. (#40, + #41) + + - Decoding functions now return NULL if the first argument is NULL. + Patch by Andrea Marchesini. + + - Include ``jansson_config.h.win32`` in the distribution tarball. + + - Remove ``+`` and leading zeros from exponents in the encoder. + (#39) + + - Make Jansson build and work on MinGW. (#39, #38) + +* Documentation + + - Note that the same JSON values must not be encoded in parallel by + separate threads. (#42) + + - Document MinGW support. + + +Version 2.2.1 +============= + +Released 2011-10-06 + +* Bug fixes: + + - Fix real number encoding and decoding under non-C locales. (#32) + + - Fix identifier decoding under non-UTF-8 locales. (#35) + + - `json_load_file()`: Open the input file in binary mode for maximum + compatiblity. + +* Documentation: + + - Clarify the lifecycle of the result of the ``s`` fromat of + `json_unpack()`. (#31) + + - Add some portability info. (#36) + + - Little clarifications here and there. + +* Other: + + - Some style fixes, issues detected by static analyzers. + + +Version 2.2 +=========== + +Released 2011-09-03 + +* New features: + + - `json_dump_callback()`: Pass the encoder output to a callback + function in chunks. + +* Bug fixes: + + - `json_string_set()`: Check that target is a string and value is + not NULL. + +* Other: + + - Documentation typo fixes and clarifications. + + +Version 2.1 +=========== + +Released 2011-06-10 + +* New features: + + - `json_loadb()`: Decode a string with a given size, useful if the + string is not null terminated. + + - Add ``JSON_ENCODE_ANY`` encoding flag to allow encoding any JSON + value. By default, only arrays and objects can be encoded. (#19) + + - Add ``JSON_REJECT_DUPLICATES`` decoding flag to issue a decoding + error if any JSON object in the input contins duplicate keys. (#3) + + - Add ``JSON_DISABLE_EOF_CHECK`` decoding flag to stop decoding after a + valid JSON input. This allows other data after the JSON data. + +* Bug fixes: + + - Fix an additional memory leak when memory allocation fails in + `json_object_set()` and friends. + + - Clear errno before calling `strtod()` for better portability. (#27) + +* Building: + + - Avoid set-but-not-used warning/error in a test. (#20) + +* Other: + + - Minor clarifications to documentation. + + +Version 2.0.1 +============= + +Released 2011-03-31 + +* Bug fixes: + + - Replace a few `malloc()` and `free()` calls with their + counterparts that support custom memory management. + + - Fix object key hashing in json_unpack() strict checking mode. + + - Fix the parentheses in ``JANSSON_VERSION_HEX`` macro. + + - Fix `json_object_size()` return value. + + - Fix a few compilation issues. + +* Portability: + + - Enhance portability of `va_copy()`. + + - Test framework portability enhancements. + +* Documentation: + + - Distribute ``doc/upgrading.rst`` with the source tarball. + + - Build documentation in strict mode in ``make distcheck``. + + +Version 2.0 +=========== + +Released 2011-02-28 + +This release is backwards incompatible with the 1.x release series. +See the chapter "Upgrading from older versions" in documentation for +details. + +* Backwards incompatible changes: + + - Unify unsigned integer usage in the API: All occurences of + unsigned int and unsigned long have been replaced with size_t. + + - Change JSON integer's underlying type to the widest signed integer + type available, i.e. long long if it's supported, otherwise long. + Add a typedef json_int_t that defines the type. + + - Change the maximum indentation depth to 31 spaces in encoder. This + frees up bits from the flags parameter of encoding functions + `json_dumpf()`, `json_dumps()` and `json_dump_file()`. + + - For future needs, add a flags parameter to all decoding functions + `json_loadf()`, `json_loads()` and `json_load_file()`. + +* New features + + - `json_pack()`, `json_pack_ex()`, `json_vpack_ex()`: Create JSON + values based on a format string. + + - `json_unpack()`, `json_unpack_ex()`, `json_vunpack_ex()`: Simple + value extraction and validation functionality based on a format + string. + + - Add column, position and source fields to the ``json_error_t`` + struct. + + - Enhance error reporting in the decoder. + + - ``JANSSON_VERSION`` et al.: Preprocessor constants that define the + library version. + + - `json_set_alloc_funcs()`: Set custom memory allocation functions. + +* Fix many portability issues, especially on Windows. + +* Configuration + + - Add file ``jansson_config.h`` that contains site specific + configuration. It's created automatically by the configure script, + or can be created by hand if the configure script cannot be used. + The file ``jansson_config.h.win32`` can be used without + modifications on Windows systems. + + - Add a section to documentation describing how to build Jansson on + Windows. + + - Documentation now requires Sphinx 1.0 or newer. + + +Version 1.3 +=========== + +Released 2010-06-13 + +* New functions: + + - `json_object_iter_set()`, `json_object_iter_set_new()`: Change + object contents while iterating over it. + + - `json_object_iter_at()`: Return an iterator that points to a + specific object item. + +* New encoding flags: + + - ``JSON_PRESERVE_ORDER``: Preserve the insertion order of object + keys. + +* Bug fixes: + + - Fix an error that occured when an array or object was first + encoded as empty, then populated with some data, and then + re-encoded + + - Fix the situation like above, but when the first encoding resulted + in an error + +* Documentation: + + - Clarify the documentation on reference stealing, providing an + example usage pattern + + +Version 1.2.1 +============= + +Released 2010-04-03 + +* Bug fixes: + + - Fix reference counting on ``true``, ``false`` and ``null`` + - Estimate real number underflows in decoder with 0.0 instead of + issuing an error + +* Portability: + + - Make ``int32_t`` available on all systems + - Support compilers that don't have the ``inline`` keyword + - Require Autoconf 2.60 (for ``int32_t``) + +* Tests: + + - Print test names correctly when ``VERBOSE=1`` + - ``test/suites/api``: Fail when a test fails + - Enhance tests for iterators + - Enhance tests for decoding texts that contain null bytes + +* Documentation: + + - Don't remove ``changes.rst`` in ``make clean`` + - Add a chapter on RFC conformance + + +Version 1.2 +=========== + +Released 2010-01-21 + +* New functions: + + - `json_equal()`: Test whether two JSON values are equal + - `json_copy()` and `json_deep_copy()`: Make shallow and deep copies + of JSON values + - Add a version of all functions taking a string argument that + doesn't check for valid UTF-8: `json_string_nocheck()`, + `json_string_set_nocheck()`, `json_object_set_nocheck()`, + `json_object_set_new_nocheck()` + +* New encoding flags: + + - ``JSON_SORT_KEYS``: Sort objects by key + - ``JSON_ENSURE_ASCII``: Escape all non-ASCII Unicode characters + - ``JSON_COMPACT``: Use a compact representation with all unneeded + whitespace stripped + +* Bug fixes: + + - Revise and unify whitespace usage in encoder: Add spaces between + array and object items, never append newline to output. + - Remove const qualifier from the ``json_t`` parameter in + `json_string_set()`, `json_integer_set()` and `json_real_set`. + - Use ``int32_t`` internally for representing Unicode code points + (int is not enough on all platforms) + +* Other changes: + + - Convert ``CHANGES`` (this file) to reStructured text and add it to + HTML documentation + - The test system has been refactored. Python is no longer required + to run the tests. + - Documentation can now be built by invoking ``make html`` + - Support for pkg-config + + +Version 1.1.3 +============= + +Released 2009-12-18 + +* Encode reals correctly, so that first encoding and then decoding a + real always produces the same value +* Don't export private symbols in ``libjansson.so`` + + +Version 1.1.2 +============= + +Released 2009-11-08 + +* Fix a bug where an error message was not produced if the input file + could not be opened in `json_load_file()` +* Fix an assertion failure in decoder caused by a minus sign without a + digit after it +* Remove an unneeded include of ``stdint.h`` in ``jansson.h`` + + +Version 1.1.1 +============= + +Released 2009-10-26 + +* All documentation files were not distributed with v1.1; build + documentation in make distcheck to prevent this in the future +* Fix v1.1 release date in ``CHANGES`` + + +Version 1.1 +=========== + +Released 2009-10-20 + +* API additions and improvements: + + - Extend array and object APIs + - Add functions to modify integer, real and string values + - Improve argument validation + - Use unsigned int instead of ``uint32_t`` for encoding flags + +* Enhance documentation + + - Add getting started guide and tutorial + - Fix some typos + - General clarifications and cleanup + +* Check for integer and real overflows and underflows in decoder +* Make singleton values thread-safe (``true``, ``false`` and ``null``) +* Enhance circular reference handling +* Don't define ``-std=c99`` in ``AM_CFLAGS`` +* Add C++ guards to ``jansson.h`` +* Minor performance and portability improvements +* Expand test coverage + + +Version 1.0.4 +============= + +Released 2009-10-11 + +* Relax Autoconf version requirement to 2.59 +* Make Jansson compile on platforms where plain ``char`` is unsigned +* Fix API tests for object + + +Version 1.0.3 +============= + +Released 2009-09-14 + +* Check for integer and real overflows and underflows in decoder +* Use the Python json module for tests, or simplejson if the json + module is not found +* Distribute changelog (this file) + + +Version 1.0.2 +============= + +Released 2009-09-08 + +* Handle EOF correctly in decoder + + +Version 1.0.1 +============= + +Released 2009-09-04 + +* Fixed broken `json_is_boolean()` + + +Version 1.0 +=========== + +Released 2009-08-25 + +* Initial release diff --git a/compat/jansson-2.6/LICENSE b/compat/jansson-2.6/LICENSE new file mode 100644 index 0000000..a8fb5b8 --- /dev/null +++ b/compat/jansson-2.6/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2009-2013 Petri Lehtinen + +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. diff --git a/compat/jansson-2.6/Makefile.am b/compat/jansson-2.6/Makefile.am new file mode 100644 index 0000000..a6efeb0 --- /dev/null +++ b/compat/jansson-2.6/Makefile.am @@ -0,0 +1,17 @@ +ACLOCAL_AMFLAGS = -I m4 + +EXTRA_DIST = CHANGES LICENSE README.rst +SUBDIRS = src + +# "make distcheck" builds the dvi target, so use it to check that the +# documentation is built correctly. +dvi: + $(MAKE) SPHINXOPTS_EXTRA=-W html + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = jansson.pc + +if GCC +# These flags are gcc specific +export AM_CFLAGS = -Wall -Wextra -Wdeclaration-after-statement +endif diff --git a/compat/jansson-2.6/README.rst b/compat/jansson-2.6/README.rst new file mode 100644 index 0000000..a01cbc0 --- /dev/null +++ b/compat/jansson-2.6/README.rst @@ -0,0 +1,63 @@ +Jansson README +============== + +.. image:: https://travis-ci.org/akheron/jansson.png + :alt: Build status + :target: https://travis-ci.org/akheron/jansson + +Jansson_ is a C library for encoding, decoding and manipulating JSON +data. Its main features and design principles are: + +- Simple and intuitive API and data model + +- Comprehensive documentation + +- No dependencies on other libraries + +- Full Unicode support (UTF-8) + +- Extensive test suite + +Jansson is licensed under the `MIT license`_; see LICENSE in the +source distribution for details. + + +Compilation and Installation +---------------------------- + +If you obtained a source tarball, just use the standard autotools +commands:: + + $ ./configure + $ make + $ make install + +To run the test suite, invoke:: + + $ make check + +If the source has been checked out from a Git repository, the +./configure script has to be generated first. The easiest way is to +use autoreconf:: + + $ autoreconf -i + + +Documentation +------------- + +Prebuilt HTML documentation is available at +http://www.digip.org/jansson/doc/. + +The documentation source is in the ``doc/`` subdirectory. To generate +HTML documentation, invoke:: + + $ make html + +Then, point your browser to ``doc/_build/html/index.html``. Sphinx_ +1.0 or newer is required to generate the documentation. + + +.. _Jansson: http://www.digip.org/jansson/ +.. _`MIT license`: http://www.opensource.org/licenses/mit-license.php +.. _Sphinx: http://sphinx.pocoo.org/ diff --git a/compat/jansson-2.6/configure.ac b/compat/jansson-2.6/configure.ac new file mode 100644 index 0000000..1977aa6 --- /dev/null +++ b/compat/jansson-2.6/configure.ac @@ -0,0 +1,101 @@ +AC_PREREQ([2.60]) +AC_INIT([jansson], [2.6], [petri@digip.org]) + +AC_CONFIG_MACRO_DIR([m4]) + +AM_INIT_AUTOMAKE([1.10 foreign]) +m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) + +AC_CONFIG_SRCDIR([src/value.c]) +AC_CONFIG_HEADERS([config.h]) + +# Checks for programs. +AC_PROG_CC +AC_PROG_LIBTOOL +AM_CONDITIONAL([GCC], [test x$GCC = xyes]) + +# Checks for libraries. + +# Checks for header files. +AC_CHECK_HEADERS([endian.h fcntl.h locale.h sched.h unistd.h sys/param.h sys/stat.h sys/time.h sys/types.h]) + +# Checks for typedefs, structures, and compiler characteristics. +AC_TYPE_INT32_T +AC_TYPE_UINT32_T +AC_TYPE_LONG_LONG_INT + +AC_C_INLINE +case $ac_cv_c_inline in + yes) json_inline=inline;; + no) json_inline=;; + *) json_inline=$ac_cv_c_inline;; +esac +AC_SUBST([json_inline]) + +# Checks for library functions. +AC_CHECK_FUNCS([close getpid gettimeofday localeconv open read sched_yield strtoll]) + +AC_MSG_CHECKING([for gcc __sync builtins]) +have_sync_builtins=no +AC_TRY_LINK( + [], [unsigned long val; __sync_bool_compare_and_swap(&val, 0, 1);], + [have_sync_builtins=yes], +) +if test "x$have_sync_builtins" = "xyes"; then + AC_DEFINE([HAVE_SYNC_BUILTINS], [1], + [Define to 1 if gcc's __sync builtins are available]) +fi +AC_MSG_RESULT([$have_sync_builtins]) + +AC_MSG_CHECKING([for gcc __atomic builtins]) +have_atomic_builtins=no +AC_TRY_LINK( + [], [char l; unsigned long v; __atomic_test_and_set(&l, __ATOMIC_RELAXED); __atomic_store_n(&v, 1, __ATOMIC_ACQ_REL); __atomic_load_n(&v, __ATOMIC_ACQUIRE);], + [have_atomic_builtins=yes], +) +if test "x$have_atomic_builtins" = "xyes"; then + AC_DEFINE([HAVE_ATOMIC_BUILTINS], [1], + [Define to 1 if gcc's __atomic builtins are available]) +fi +AC_MSG_RESULT([$have_atomic_builtins]) + +case "$ac_cv_type_long_long_int$ac_cv_func_strtoll" in + yesyes) json_have_long_long=1;; + *) json_have_long_long=0;; +esac +AC_SUBST([json_have_long_long]) + +case "$ac_cv_header_locale_h$ac_cv_func_localeconv" in + yesyes) json_have_localeconv=1;; + *) json_have_localeconv=0;; +esac +AC_SUBST([json_have_localeconv]) + +# Features +AC_ARG_ENABLE([urandom], + [AS_HELP_STRING([--disable-urandom], + [Don't use /dev/urandom to seed the hash function])], + [use_urandom=$enableval], [use_urandom=yes]) + +if test "x$use_urandom" = xyes; then +AC_DEFINE([USE_URANDOM], [1], + [Define to 1 if /dev/urandom should be used for seeding the hash function]) +fi + +AC_ARG_ENABLE([windows-cryptoapi], + [AS_HELP_STRING([--disable-windows-cryptoapi], + [Don't use CryptGenRandom to seed the hash function])], + [use_windows_cryptoapi=$enableval], [use_windows_cryptoapi=yes]) + +if test "x$use_windows_cryptoapi" = xyes; then +AC_DEFINE([USE_WINDOWS_CRYPTOAPI], [1], + [Define to 1 if CryptGenRandom should be used for seeding the hash function]) +fi + +AC_CONFIG_FILES([ + jansson.pc + Makefile + src/Makefile + src/jansson_config.h +]) +AC_OUTPUT diff --git a/compat/jansson-2.6/jansson.pc.in b/compat/jansson-2.6/jansson.pc.in new file mode 100644 index 0000000..d9bf4da --- /dev/null +++ b/compat/jansson-2.6/jansson.pc.in @@ -0,0 +1,10 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=${prefix}/include + +Name: Jansson +Description: Library for encoding, decoding and manipulating JSON data +Version: @VERSION@ +Libs: -L${libdir} -ljansson +Cflags: -I${includedir} diff --git a/compat/jansson-2.6/m4/.gitignore b/compat/jansson-2.6/m4/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/compat/jansson-2.6/src/Makefile.am b/compat/jansson-2.6/src/Makefile.am new file mode 100644 index 0000000..e1a5493 --- /dev/null +++ b/compat/jansson-2.6/src/Makefile.am @@ -0,0 +1,24 @@ +EXTRA_DIST = jansson.def + +include_HEADERS = jansson.h jansson_config.h + +lib_LTLIBRARIES = libjansson.la +libjansson_la_SOURCES = \ + dump.c \ + error.c \ + hashtable.c \ + hashtable.h \ + jansson_private.h \ + load.c \ + memory.c \ + pack_unpack.c \ + strbuffer.c \ + strbuffer.h \ + strconv.c \ + utf.c \ + utf.h \ + value.c +libjansson_la_LDFLAGS = \ + -no-undefined \ + -export-symbols-regex '^json_' \ + -version-info 9:0:5 diff --git a/compat/jansson-2.6/src/dump.c b/compat/jansson-2.6/src/dump.c new file mode 100644 index 0000000..3b19c73 --- /dev/null +++ b/compat/jansson-2.6/src/dump.c @@ -0,0 +1,456 @@ +/* + * Copyright (c) 2009-2013 Petri Lehtinen + * + * Jansson is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include + +#include "jansson.h" +#include "jansson_private.h" +#include "strbuffer.h" +#include "utf.h" + +#define MAX_INTEGER_STR_LENGTH 100 +#define MAX_REAL_STR_LENGTH 100 + +struct object_key { + size_t serial; + const char *key; +}; + +static int dump_to_strbuffer(const char *buffer, size_t size, void *data) +{ + return strbuffer_append_bytes((strbuffer_t *)data, buffer, size); +} + +static int dump_to_file(const char *buffer, size_t size, void *data) +{ + FILE *dest = (FILE *)data; + if(fwrite(buffer, size, 1, dest) != 1) + return -1; + return 0; +} + +/* 32 spaces (the maximum indentation size) */ +static const char whitespace[] = " "; + +static int dump_indent(size_t flags, int depth, int space, json_dump_callback_t dump, void *data) +{ + if(JSON_INDENT(flags) > 0) + { + int i, ws_count = JSON_INDENT(flags); + + if(dump("\n", 1, data)) + return -1; + + for(i = 0; i < depth; i++) + { + if(dump(whitespace, ws_count, data)) + return -1; + } + } + else if(space && !(flags & JSON_COMPACT)) + { + return dump(" ", 1, data); + } + return 0; +} + +static int dump_string(const char *str, json_dump_callback_t dump, void *data, size_t flags) +{ + const char *pos, *end; + int32_t codepoint; + + if(dump("\"", 1, data)) + return -1; + + end = pos = str; + while(1) + { + const char *text; + char seq[13]; + int length; + + while(*end) + { + end = utf8_iterate(pos, &codepoint); + if(!end) + return -1; + + /* mandatory escape or control char */ + if(codepoint == '\\' || codepoint == '"' || codepoint < 0x20) + break; + + /* slash */ + if((flags & JSON_ESCAPE_SLASH) && codepoint == '/') + break; + + /* non-ASCII */ + if((flags & JSON_ENSURE_ASCII) && codepoint > 0x7F) + break; + + pos = end; + } + + if(pos != str) { + if(dump(str, pos - str, data)) + return -1; + } + + if(end == pos) + break; + + /* handle \, /, ", and control codes */ + length = 2; + switch(codepoint) + { + case '\\': text = "\\\\"; break; + case '\"': text = "\\\""; break; + case '\b': text = "\\b"; break; + case '\f': text = "\\f"; break; + case '\n': text = "\\n"; break; + case '\r': text = "\\r"; break; + case '\t': text = "\\t"; break; + case '/': text = "\\/"; break; + default: + { + /* codepoint is in BMP */ + if(codepoint < 0x10000) + { + sprintf(seq, "\\u%04x", codepoint); + length = 6; + } + + /* not in BMP -> construct a UTF-16 surrogate pair */ + else + { + int32_t first, last; + + codepoint -= 0x10000; + first = 0xD800 | ((codepoint & 0xffc00) >> 10); + last = 0xDC00 | (codepoint & 0x003ff); + + sprintf(seq, "\\u%04x\\u%04x", first, last); + length = 12; + } + + text = seq; + break; + } + } + + if(dump(text, length, data)) + return -1; + + str = pos = end; + } + + return dump("\"", 1, data); +} + +static int object_key_compare_keys(const void *key1, const void *key2) +{ + return strcmp(((const struct object_key *)key1)->key, + ((const struct object_key *)key2)->key); +} + +static int object_key_compare_serials(const void *key1, const void *key2) +{ + size_t a = ((const struct object_key *)key1)->serial; + size_t b = ((const struct object_key *)key2)->serial; + + return a < b ? -1 : a == b ? 0 : 1; +} + +static int do_dump(const json_t *json, size_t flags, int depth, + json_dump_callback_t dump, void *data) +{ + if(!json) + return -1; + + switch(json_typeof(json)) { + case JSON_NULL: + return dump("null", 4, data); + + case JSON_TRUE: + return dump("true", 4, data); + + case JSON_FALSE: + return dump("false", 5, data); + + case JSON_INTEGER: + { + char buffer[MAX_INTEGER_STR_LENGTH]; + int size; + + size = snprintf(buffer, MAX_INTEGER_STR_LENGTH, + "%" JSON_INTEGER_FORMAT, + json_integer_value(json)); + if(size < 0 || size >= MAX_INTEGER_STR_LENGTH) + return -1; + + return dump(buffer, size, data); + } + + case JSON_REAL: + { + char buffer[MAX_REAL_STR_LENGTH]; + int size; + double value = json_real_value(json); + + size = jsonp_dtostr(buffer, MAX_REAL_STR_LENGTH, value); + if(size < 0) + return -1; + + return dump(buffer, size, data); + } + + case JSON_STRING: + return dump_string(json_string_value(json), dump, data, flags); + + case JSON_ARRAY: + { + int i; + int n; + json_array_t *array; + + /* detect circular references */ + array = json_to_array(json); + if(array->visited) + goto array_error; + array->visited = 1; + + n = json_array_size(json); + + if(dump("[", 1, data)) + goto array_error; + if(n == 0) { + array->visited = 0; + return dump("]", 1, data); + } + if(dump_indent(flags, depth + 1, 0, dump, data)) + goto array_error; + + for(i = 0; i < n; ++i) { + if(do_dump(json_array_get(json, i), flags, depth + 1, + dump, data)) + goto array_error; + + if(i < n - 1) + { + if(dump(",", 1, data) || + dump_indent(flags, depth + 1, 1, dump, data)) + goto array_error; + } + else + { + if(dump_indent(flags, depth, 0, dump, data)) + goto array_error; + } + } + + array->visited = 0; + return dump("]", 1, data); + + array_error: + array->visited = 0; + return -1; + } + + case JSON_OBJECT: + { + json_object_t *object; + void *iter; + const char *separator; + int separator_length; + + if(flags & JSON_COMPACT) { + separator = ":"; + separator_length = 1; + } + else { + separator = ": "; + separator_length = 2; + } + + /* detect circular references */ + object = json_to_object(json); + if(object->visited) + goto object_error; + object->visited = 1; + + iter = json_object_iter((json_t *)json); + + if(dump("{", 1, data)) + goto object_error; + if(!iter) { + object->visited = 0; + return dump("}", 1, data); + } + if(dump_indent(flags, depth + 1, 0, dump, data)) + goto object_error; + + if(flags & JSON_SORT_KEYS || flags & JSON_PRESERVE_ORDER) + { + struct object_key *keys; + size_t size, i; + int (*cmp_func)(const void *, const void *); + + size = json_object_size(json); + keys = jsonp_malloc(size * sizeof(struct object_key)); + if(!keys) + goto object_error; + + i = 0; + while(iter) + { + keys[i].serial = hashtable_iter_serial(iter); + keys[i].key = json_object_iter_key(iter); + iter = json_object_iter_next((json_t *)json, iter); + i++; + } + assert(i == size); + + if(flags & JSON_SORT_KEYS) + cmp_func = object_key_compare_keys; + else + cmp_func = object_key_compare_serials; + + qsort(keys, size, sizeof(struct object_key), cmp_func); + + for(i = 0; i < size; i++) + { + const char *key; + json_t *value; + + key = keys[i].key; + value = json_object_get(json, key); + assert(value); + + dump_string(key, dump, data, flags); + if(dump(separator, separator_length, data) || + do_dump(value, flags, depth + 1, dump, data)) + { + jsonp_free(keys); + goto object_error; + } + + if(i < size - 1) + { + if(dump(",", 1, data) || + dump_indent(flags, depth + 1, 1, dump, data)) + { + jsonp_free(keys); + goto object_error; + } + } + else + { + if(dump_indent(flags, depth, 0, dump, data)) + { + jsonp_free(keys); + goto object_error; + } + } + } + + jsonp_free(keys); + } + else + { + /* Don't sort keys */ + + while(iter) + { + void *next = json_object_iter_next((json_t *)json, iter); + + dump_string(json_object_iter_key(iter), dump, data, flags); + if(dump(separator, separator_length, data) || + do_dump(json_object_iter_value(iter), flags, depth + 1, + dump, data)) + goto object_error; + + if(next) + { + if(dump(",", 1, data) || + dump_indent(flags, depth + 1, 1, dump, data)) + goto object_error; + } + else + { + if(dump_indent(flags, depth, 0, dump, data)) + goto object_error; + } + + iter = next; + } + } + + object->visited = 0; + return dump("}", 1, data); + + object_error: + object->visited = 0; + return -1; + } + + default: + /* not reached */ + return -1; + } +} + +char *json_dumps(const json_t *json, size_t flags) +{ + strbuffer_t strbuff; + char *result; + + if(strbuffer_init(&strbuff)) + return NULL; + + if(json_dump_callback(json, dump_to_strbuffer, (void *)&strbuff, flags)) + result = NULL; + else + result = jsonp_strdup(strbuffer_value(&strbuff)); + + strbuffer_close(&strbuff); + return result; +} + +int json_dumpf(const json_t *json, FILE *output, size_t flags) +{ + return json_dump_callback(json, dump_to_file, (void *)output, flags); +} + +int json_dump_file(const json_t *json, const char *path, size_t flags) +{ + int result; + + FILE *output = fopen(path, "w"); + if(!output) + return -1; + + result = json_dumpf(json, output, flags); + + fclose(output); + return result; +} + +int json_dump_callback(const json_t *json, json_dump_callback_t callback, void *data, size_t flags) +{ + if(!(flags & JSON_ENCODE_ANY)) { + if(!json_is_array(json) && !json_is_object(json)) + return -1; + } + + return do_dump(json, flags, 0, callback, data); +} diff --git a/compat/jansson-2.6/src/error.c b/compat/jansson-2.6/src/error.c new file mode 100644 index 0000000..a544a59 --- /dev/null +++ b/compat/jansson-2.6/src/error.c @@ -0,0 +1,63 @@ +#include +#include "jansson_private.h" + +void jsonp_error_init(json_error_t *error, const char *source) +{ + if(error) + { + error->text[0] = '\0'; + error->line = -1; + error->column = -1; + error->position = 0; + if(source) + jsonp_error_set_source(error, source); + else + error->source[0] = '\0'; + } +} + +void jsonp_error_set_source(json_error_t *error, const char *source) +{ + size_t length; + + if(!error || !source) + return; + + length = strlen(source); + if(length < JSON_ERROR_SOURCE_LENGTH) + strcpy(error->source, source); + else { + size_t extra = length - JSON_ERROR_SOURCE_LENGTH + 4; + strcpy(error->source, "..."); + strcpy(error->source + 3, source + extra); + } +} + +void jsonp_error_set(json_error_t *error, int line, int column, + size_t position, const char *msg, ...) +{ + va_list ap; + + va_start(ap, msg); + jsonp_error_vset(error, line, column, position, msg, ap); + va_end(ap); +} + +void jsonp_error_vset(json_error_t *error, int line, int column, + size_t position, const char *msg, va_list ap) +{ + if(!error) + return; + + if(error->text[0] != '\0') { + /* error already set */ + return; + } + + error->line = line; + error->column = column; + error->position = position; + + vsnprintf(error->text, JSON_ERROR_TEXT_LENGTH, msg, ap); + error->text[JSON_ERROR_TEXT_LENGTH - 1] = '\0'; +} diff --git a/compat/jansson-2.6/src/hashtable.c b/compat/jansson-2.6/src/hashtable.c new file mode 100644 index 0000000..5fb0467 --- /dev/null +++ b/compat/jansson-2.6/src/hashtable.c @@ -0,0 +1,360 @@ +/* + * Copyright (c) 2009-2013 Petri Lehtinen + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#include +#include +#include /* for JSON_INLINE */ +#include "jansson_private.h" /* for container_of() */ +#include "hashtable.h" + +typedef struct hashtable_list list_t; +typedef struct hashtable_pair pair_t; +typedef struct hashtable_bucket bucket_t; + +#define list_to_pair(list_) container_of(list_, pair_t, list) + +/* From http://www.cse.yorku.ca/~oz/hash.html */ +static size_t hash_str(const void *ptr) +{ + const char *str = (const char *)ptr; + + size_t hash = 5381; + size_t c; + + while((c = (size_t)*str)) + { + hash = ((hash << 5) + hash) + c; + str++; + } + + return hash; +} + +static JSON_INLINE void list_init(list_t *list) +{ + list->next = list; + list->prev = list; +} + +static JSON_INLINE void list_insert(list_t *list, list_t *node) +{ + node->next = list; + node->prev = list->prev; + list->prev->next = node; + list->prev = node; +} + +static JSON_INLINE void list_remove(list_t *list) +{ + list->prev->next = list->next; + list->next->prev = list->prev; +} + +static JSON_INLINE int bucket_is_empty(hashtable_t *hashtable, bucket_t *bucket) +{ + return bucket->first == &hashtable->list && bucket->first == bucket->last; +} + +static void insert_to_bucket(hashtable_t *hashtable, bucket_t *bucket, + list_t *list) +{ + if(bucket_is_empty(hashtable, bucket)) + { + list_insert(&hashtable->list, list); + bucket->first = bucket->last = list; + } + else + { + list_insert(bucket->first, list); + bucket->first = list; + } +} + +static const size_t primes[] = { + 5, 13, 23, 53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593, + 49157, 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469, + 12582917, 25165843, 50331653, 100663319, 201326611, 402653189, + 805306457, 1610612741 +}; + +static JSON_INLINE size_t num_buckets(hashtable_t *hashtable) +{ + return primes[hashtable->num_buckets]; +} + + +static pair_t *hashtable_find_pair(hashtable_t *hashtable, bucket_t *bucket, + const char *key, size_t hash) +{ + list_t *list; + pair_t *pair; + + if(bucket_is_empty(hashtable, bucket)) + return NULL; + + list = bucket->first; + while(1) + { + pair = list_to_pair(list); + if(pair->hash == hash && strcmp(pair->key, key) == 0) + return pair; + + if(list == bucket->last) + break; + + list = list->next; + } + + return NULL; +} + +/* returns 0 on success, -1 if key was not found */ +static int hashtable_do_del(hashtable_t *hashtable, + const char *key, size_t hash) +{ + pair_t *pair; + bucket_t *bucket; + size_t index; + + index = hash % num_buckets(hashtable); + bucket = &hashtable->buckets[index]; + + pair = hashtable_find_pair(hashtable, bucket, key, hash); + if(!pair) + return -1; + + if(&pair->list == bucket->first && &pair->list == bucket->last) + bucket->first = bucket->last = &hashtable->list; + + else if(&pair->list == bucket->first) + bucket->first = pair->list.next; + + else if(&pair->list == bucket->last) + bucket->last = pair->list.prev; + + list_remove(&pair->list); + json_decref(pair->value); + + jsonp_free(pair); + hashtable->size--; + + return 0; +} + +static void hashtable_do_clear(hashtable_t *hashtable) +{ + list_t *list, *next; + pair_t *pair; + + for(list = hashtable->list.next; list != &hashtable->list; list = next) + { + next = list->next; + pair = list_to_pair(list); + json_decref(pair->value); + jsonp_free(pair); + } +} + +static int hashtable_do_rehash(hashtable_t *hashtable) +{ + list_t *list, *next; + pair_t *pair; + size_t i, index, new_size; + + jsonp_free(hashtable->buckets); + + hashtable->num_buckets++; + new_size = num_buckets(hashtable); + + hashtable->buckets = jsonp_malloc(new_size * sizeof(bucket_t)); + if(!hashtable->buckets) + return -1; + + for(i = 0; i < num_buckets(hashtable); i++) + { + hashtable->buckets[i].first = hashtable->buckets[i].last = + &hashtable->list; + } + + list = hashtable->list.next; + list_init(&hashtable->list); + + for(; list != &hashtable->list; list = next) { + next = list->next; + pair = list_to_pair(list); + index = pair->hash % new_size; + insert_to_bucket(hashtable, &hashtable->buckets[index], &pair->list); + } + + return 0; +} + + +int hashtable_init(hashtable_t *hashtable) +{ + size_t i; + + hashtable->size = 0; + hashtable->num_buckets = 0; /* index to primes[] */ + hashtable->buckets = jsonp_malloc(num_buckets(hashtable) * sizeof(bucket_t)); + if(!hashtable->buckets) + return -1; + + list_init(&hashtable->list); + + for(i = 0; i < num_buckets(hashtable); i++) + { + hashtable->buckets[i].first = hashtable->buckets[i].last = + &hashtable->list; + } + + return 0; +} + +void hashtable_close(hashtable_t *hashtable) +{ + hashtable_do_clear(hashtable); + jsonp_free(hashtable->buckets); +} + +int hashtable_set(hashtable_t *hashtable, + const char *key, size_t serial, + json_t *value) +{ + pair_t *pair; + bucket_t *bucket; + size_t hash, index; + + /* rehash if the load ratio exceeds 1 */ + if(hashtable->size >= num_buckets(hashtable)) + if(hashtable_do_rehash(hashtable)) + return -1; + + hash = hash_str(key); + index = hash % num_buckets(hashtable); + bucket = &hashtable->buckets[index]; + pair = hashtable_find_pair(hashtable, bucket, key, hash); + + if(pair) + { + json_decref(pair->value); + pair->value = value; + } + else + { + /* offsetof(...) returns the size of pair_t without the last, + flexible member. This way, the correct amount is + allocated. */ + pair = jsonp_malloc(offsetof(pair_t, key) + strlen(key) + 1); + if(!pair) + return -1; + + pair->hash = hash; + pair->serial = serial; + strcpy(pair->key, key); + pair->value = value; + list_init(&pair->list); + + insert_to_bucket(hashtable, bucket, &pair->list); + + hashtable->size++; + } + return 0; +} + +void *hashtable_get(hashtable_t *hashtable, const char *key) +{ + pair_t *pair; + size_t hash; + bucket_t *bucket; + + hash = hash_str(key); + bucket = &hashtable->buckets[hash % num_buckets(hashtable)]; + + pair = hashtable_find_pair(hashtable, bucket, key, hash); + if(!pair) + return NULL; + + return pair->value; +} + +int hashtable_del(hashtable_t *hashtable, const char *key) +{ + size_t hash = hash_str(key); + return hashtable_do_del(hashtable, key, hash); +} + +void hashtable_clear(hashtable_t *hashtable) +{ + size_t i; + + hashtable_do_clear(hashtable); + + for(i = 0; i < num_buckets(hashtable); i++) + { + hashtable->buckets[i].first = hashtable->buckets[i].last = + &hashtable->list; + } + + list_init(&hashtable->list); + hashtable->size = 0; +} + +void *hashtable_iter(hashtable_t *hashtable) +{ + return hashtable_iter_next(hashtable, &hashtable->list); +} + +void *hashtable_iter_at(hashtable_t *hashtable, const char *key) +{ + pair_t *pair; + size_t hash; + bucket_t *bucket; + + hash = hash_str(key); + bucket = &hashtable->buckets[hash % num_buckets(hashtable)]; + + pair = hashtable_find_pair(hashtable, bucket, key, hash); + if(!pair) + return NULL; + + return &pair->list; +} + +void *hashtable_iter_next(hashtable_t *hashtable, void *iter) +{ + list_t *list = (list_t *)iter; + if(list->next == &hashtable->list) + return NULL; + return list->next; +} + +void *hashtable_iter_key(void *iter) +{ + pair_t *pair = list_to_pair((list_t *)iter); + return pair->key; +} + +size_t hashtable_iter_serial(void *iter) +{ + pair_t *pair = list_to_pair((list_t *)iter); + return pair->serial; +} + +void *hashtable_iter_value(void *iter) +{ + pair_t *pair = list_to_pair((list_t *)iter); + return pair->value; +} + +void hashtable_iter_set(void *iter, json_t *value) +{ + pair_t *pair = list_to_pair((list_t *)iter); + + json_decref(pair->value); + pair->value = value; +} diff --git a/compat/jansson-2.6/src/hashtable.h b/compat/jansson-2.6/src/hashtable.h new file mode 100644 index 0000000..4a7ce6f --- /dev/null +++ b/compat/jansson-2.6/src/hashtable.h @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2009-2013 Petri Lehtinen + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef HASHTABLE_H +#define HASHTABLE_H + +struct hashtable_list { + struct hashtable_list *prev; + struct hashtable_list *next; +}; + +/* "pair" may be a bit confusing a name, but think of it as a + key-value pair. In this case, it just encodes some extra data, + too */ +struct hashtable_pair { + size_t hash; + struct hashtable_list list; + json_t *value; + size_t serial; + char key[1]; +}; + +struct hashtable_bucket { + struct hashtable_list *first; + struct hashtable_list *last; +}; + +typedef struct hashtable { + size_t size; + struct hashtable_bucket *buckets; + size_t num_buckets; /* index to primes[] */ + struct hashtable_list list; +} hashtable_t; + + +#define hashtable_key_to_iter(key_) \ + (&(container_of(key_, struct hashtable_pair, key)->list)) + +/** + * hashtable_init - Initialize a hashtable object + * + * @hashtable: The (statically allocated) hashtable object + * + * Initializes a statically allocated hashtable object. The object + * should be cleared with hashtable_close when it's no longer used. + * + * Returns 0 on success, -1 on error (out of memory). + */ +int hashtable_init(hashtable_t *hashtable); + +/** + * hashtable_close - Release all resources used by a hashtable object + * + * @hashtable: The hashtable + * + * Destroys a statically allocated hashtable object. + */ +void hashtable_close(hashtable_t *hashtable); + +/** + * hashtable_set - Add/modify value in hashtable + * + * @hashtable: The hashtable object + * @key: The key + * @serial: For addition order of keys + * @value: The value + * + * If a value with the given key already exists, its value is replaced + * with the new value. Value is "stealed" in the sense that hashtable + * doesn't increment its refcount but decreases the refcount when the + * value is no longer needed. + * + * Returns 0 on success, -1 on failure (out of memory). + */ +int hashtable_set(hashtable_t *hashtable, + const char *key, size_t serial, + json_t *value); + +/** + * hashtable_get - Get a value associated with a key + * + * @hashtable: The hashtable object + * @key: The key + * + * Returns value if it is found, or NULL otherwise. + */ +void *hashtable_get(hashtable_t *hashtable, const char *key); + +/** + * hashtable_del - Remove a value from the hashtable + * + * @hashtable: The hashtable object + * @key: The key + * + * Returns 0 on success, or -1 if the key was not found. + */ +int hashtable_del(hashtable_t *hashtable, const char *key); + +/** + * hashtable_clear - Clear hashtable + * + * @hashtable: The hashtable object + * + * Removes all items from the hashtable. + */ +void hashtable_clear(hashtable_t *hashtable); + +/** + * hashtable_iter - Iterate over hashtable + * + * @hashtable: The hashtable object + * + * Returns an opaque iterator to the first element in the hashtable. + * The iterator should be passed to hashtable_iter_* functions. + * The hashtable items are not iterated over in any particular order. + * + * There's no need to free the iterator in any way. The iterator is + * valid as long as the item that is referenced by the iterator is not + * deleted. Other values may be added or deleted. In particular, + * hashtable_iter_next() may be called on an iterator, and after that + * the key/value pair pointed by the old iterator may be deleted. + */ +void *hashtable_iter(hashtable_t *hashtable); + +/** + * hashtable_iter_at - Return an iterator at a specific key + * + * @hashtable: The hashtable object + * @key: The key that the iterator should point to + * + * Like hashtable_iter() but returns an iterator pointing to a + * specific key. + */ +void *hashtable_iter_at(hashtable_t *hashtable, const char *key); + +/** + * hashtable_iter_next - Advance an iterator + * + * @hashtable: The hashtable object + * @iter: The iterator + * + * Returns a new iterator pointing to the next element in the + * hashtable or NULL if the whole hastable has been iterated over. + */ +void *hashtable_iter_next(hashtable_t *hashtable, void *iter); + +/** + * hashtable_iter_key - Retrieve the key pointed by an iterator + * + * @iter: The iterator + */ +void *hashtable_iter_key(void *iter); + +/** + * hashtable_iter_serial - Retrieve the serial number pointed to by an iterator + * + * @iter: The iterator + */ +size_t hashtable_iter_serial(void *iter); + +/** + * hashtable_iter_value - Retrieve the value pointed by an iterator + * + * @iter: The iterator + */ +void *hashtable_iter_value(void *iter); + +/** + * hashtable_iter_set - Set the value pointed by an iterator + * + * @iter: The iterator + * @value: The value to set + */ +void hashtable_iter_set(void *iter, json_t *value); + +#endif diff --git a/compat/jansson-2.6/src/jansson.def b/compat/jansson-2.6/src/jansson.def new file mode 100644 index 0000000..8cc2e9c --- /dev/null +++ b/compat/jansson-2.6/src/jansson.def @@ -0,0 +1,63 @@ +EXPORTS + json_delete + json_true + json_false + json_null + json_string + json_string_nocheck + json_string_value + json_string_set + json_string_set_nocheck + json_integer + json_integer_value + json_integer_set + json_real + json_real_value + json_real_set + json_number_value + json_array + json_array_size + json_array_get + json_array_set_new + json_array_append_new + json_array_insert_new + json_array_remove + json_array_clear + json_array_extend + json_object + json_object_size + json_object_get + json_object_set_new + json_object_set_new_nocheck + json_object_del + json_object_clear + json_object_update + json_object_update_existing + json_object_update_missing + json_object_iter + json_object_iter_at + json_object_iter_next + json_object_iter_key + json_object_iter_value + json_object_iter_set_new + json_object_key_to_iter + json_dumps + json_dumpf + json_dump_file + json_dump_callback + json_loads + json_loadb + json_loadf + json_load_file + json_load_callback + json_equal + json_copy + json_deep_copy + json_pack + json_pack_ex + json_vpack_ex + json_unpack + json_unpack_ex + json_vunpack_ex + json_set_alloc_funcs + diff --git a/compat/jansson-2.6/src/jansson.h b/compat/jansson-2.6/src/jansson.h new file mode 100644 index 0000000..52c8077 --- /dev/null +++ b/compat/jansson-2.6/src/jansson.h @@ -0,0 +1,281 @@ +/* + * Copyright (c) 2009-2013 Petri Lehtinen + * + * Jansson is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef JANSSON_H +#define JANSSON_H + +#include +#include /* for size_t */ +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* version */ + +#define JANSSON_MAJOR_VERSION 2 +#define JANSSON_MINOR_VERSION 5 +#define JANSSON_MICRO_VERSION 0 + +/* Micro version is omitted if it's 0 */ +#define JANSSON_VERSION "2.5" + +/* Version as a 3-byte hex number, e.g. 0x010201 == 1.2.1. Use this + for numeric comparisons, e.g. #if JANSSON_VERSION_HEX >= ... */ +#define JANSSON_VERSION_HEX ((JANSSON_MAJOR_VERSION << 16) | \ + (JANSSON_MINOR_VERSION << 8) | \ + (JANSSON_MICRO_VERSION << 0)) + + +/* types */ + +typedef enum { + JSON_OBJECT, + JSON_ARRAY, + JSON_STRING, + JSON_INTEGER, + JSON_REAL, + JSON_TRUE, + JSON_FALSE, + JSON_NULL +} json_type; + +typedef struct json_t { + json_type type; + size_t refcount; +} json_t; + +#ifndef JANSSON_USING_CMAKE /* disabled if using cmake */ +#if JSON_INTEGER_IS_LONG_LONG +#ifdef _WIN32 +#define JSON_INTEGER_FORMAT "I64d" +#else +#define JSON_INTEGER_FORMAT "lld" +#endif +typedef long long json_int_t; +#else +#define JSON_INTEGER_FORMAT "ld" +typedef long json_int_t; +#endif /* JSON_INTEGER_IS_LONG_LONG */ +#endif + +#define json_typeof(json) ((json)->type) +#define json_is_object(json) (json && json_typeof(json) == JSON_OBJECT) +#define json_is_array(json) (json && json_typeof(json) == JSON_ARRAY) +#define json_is_string(json) (json && json_typeof(json) == JSON_STRING) +#define json_is_integer(json) (json && json_typeof(json) == JSON_INTEGER) +#define json_is_real(json) (json && json_typeof(json) == JSON_REAL) +#define json_is_number(json) (json_is_integer(json) || json_is_real(json)) +#define json_is_true(json) (json && json_typeof(json) == JSON_TRUE) +#define json_is_false(json) (json && json_typeof(json) == JSON_FALSE) +#define json_is_boolean(json) (json_is_true(json) || json_is_false(json)) +#define json_is_null(json) (json && json_typeof(json) == JSON_NULL) + +/* construction, destruction, reference counting */ + +json_t *json_object(void); +json_t *json_array(void); +json_t *json_string(const char *value); +json_t *json_string_nocheck(const char *value); +json_t *json_integer(json_int_t value); +json_t *json_real(double value); +json_t *json_true(void); +json_t *json_false(void); +#define json_boolean(val) ((val) ? json_true() : json_false()) +json_t *json_null(void); + +static JSON_INLINE +json_t *json_incref(json_t *json) +{ + if(json && json->refcount != (size_t)-1) + ++json->refcount; + return json; +} + +/* do not call json_delete directly */ +void json_delete(json_t *json); + +static JSON_INLINE +void json_decref(json_t *json) +{ + if(json && json->refcount != (size_t)-1 && --json->refcount == 0) + json_delete(json); +} + + +/* error reporting */ + +#define JSON_ERROR_TEXT_LENGTH 160 +#define JSON_ERROR_SOURCE_LENGTH 80 + +typedef struct { + int line; + int column; + int position; + char source[JSON_ERROR_SOURCE_LENGTH]; + char text[JSON_ERROR_TEXT_LENGTH]; +} json_error_t; + + +/* getters, setters, manipulation */ + +size_t json_object_size(const json_t *object); +json_t *json_object_get(const json_t *object, const char *key); +int json_object_set_new(json_t *object, const char *key, json_t *value); +int json_object_set_new_nocheck(json_t *object, const char *key, json_t *value); +int json_object_del(json_t *object, const char *key); +int json_object_clear(json_t *object); +int json_object_update(json_t *object, json_t *other); +int json_object_update_existing(json_t *object, json_t *other); +int json_object_update_missing(json_t *object, json_t *other); +void *json_object_iter(json_t *object); +void *json_object_iter_at(json_t *object, const char *key); +void *json_object_key_to_iter(const char *key); +void *json_object_iter_next(json_t *object, void *iter); +const char *json_object_iter_key(void *iter); +json_t *json_object_iter_value(void *iter); +int json_object_iter_set_new(json_t *object, void *iter, json_t *value); + +#define json_object_foreach(object, key, value) \ + for(key = json_object_iter_key(json_object_iter(object)); \ + key && (value = json_object_iter_value(json_object_key_to_iter(key))); \ + key = json_object_iter_key(json_object_iter_next(object, json_object_key_to_iter(key)))) + +#define json_array_foreach(array, index, value) \ + for(index = 0; \ + index < json_array_size(array) && (value = json_array_get(array, index)); \ + index++) + +static JSON_INLINE +int json_object_set(json_t *object, const char *key, json_t *value) +{ + return json_object_set_new(object, key, json_incref(value)); +} + +static JSON_INLINE +int json_object_set_nocheck(json_t *object, const char *key, json_t *value) +{ + return json_object_set_new_nocheck(object, key, json_incref(value)); +} + +static JSON_INLINE +int json_object_iter_set(json_t *object, void *iter, json_t *value) +{ + return json_object_iter_set_new(object, iter, json_incref(value)); +} + +size_t json_array_size(const json_t *array); +json_t *json_array_get(const json_t *array, size_t index); +int json_array_set_new(json_t *array, size_t index, json_t *value); +int json_array_append_new(json_t *array, json_t *value); +int json_array_insert_new(json_t *array, size_t index, json_t *value); +int json_array_remove(json_t *array, size_t index); +int json_array_clear(json_t *array); +int json_array_extend(json_t *array, json_t *other); + +static JSON_INLINE +int json_array_set(json_t *array, size_t ind, json_t *value) +{ + return json_array_set_new(array, ind, json_incref(value)); +} + +static JSON_INLINE +int json_array_append(json_t *array, json_t *value) +{ + return json_array_append_new(array, json_incref(value)); +} + +static JSON_INLINE +int json_array_insert(json_t *array, size_t ind, json_t *value) +{ + return json_array_insert_new(array, ind, json_incref(value)); +} + +const char *json_string_value(const json_t *string); +json_int_t json_integer_value(const json_t *integer); +double json_real_value(const json_t *real); +double json_number_value(const json_t *json); + +int json_string_set(json_t *string, const char *value); +int json_string_set_nocheck(json_t *string, const char *value); +int json_integer_set(json_t *integer, json_int_t value); +int json_real_set(json_t *real, double value); + + +/* pack, unpack */ + +json_t *json_pack(const char *fmt, ...); +json_t *json_pack_ex(json_error_t *error, size_t flags, const char *fmt, ...); +json_t *json_vpack_ex(json_error_t *error, size_t flags, const char *fmt, va_list ap); + +#define JSON_VALIDATE_ONLY 0x1 +#define JSON_STRICT 0x2 + +int json_unpack(json_t *root, const char *fmt, ...); +int json_unpack_ex(json_t *root, json_error_t *error, size_t flags, const char *fmt, ...); +int json_vunpack_ex(json_t *root, json_error_t *error, size_t flags, const char *fmt, va_list ap); + + +/* equality */ + +int json_equal(json_t *value1, json_t *value2); + + +/* copying */ + +json_t *json_copy(json_t *value); +json_t *json_deep_copy(const json_t *value); + + +/* decoding */ + +#define JSON_REJECT_DUPLICATES 0x1 +#define JSON_DISABLE_EOF_CHECK 0x2 +#define JSON_DECODE_ANY 0x4 +#define JSON_DECODE_INT_AS_REAL 0x8 + +typedef size_t (*json_load_callback_t)(void *buffer, size_t buflen, void *data); + +json_t *json_loads(const char *input, size_t flags, json_error_t *error); +json_t *json_loadb(const char *buffer, size_t buflen, size_t flags, json_error_t *error); +json_t *json_loadf(FILE *input, size_t flags, json_error_t *error); +json_t *json_load_file(const char *path, size_t flags, json_error_t *error); +json_t *json_load_callback(json_load_callback_t callback, void *data, size_t flags, json_error_t *error); + + +/* encoding */ + +#define JSON_INDENT(n) (n & 0x1F) +#define JSON_COMPACT 0x20 +#define JSON_ENSURE_ASCII 0x40 +#define JSON_SORT_KEYS 0x80 +#define JSON_PRESERVE_ORDER 0x100 +#define JSON_ENCODE_ANY 0x200 +#define JSON_ESCAPE_SLASH 0x400 + +typedef int (*json_dump_callback_t)(const char *buffer, size_t size, void *data); + +char *json_dumps(const json_t *json, size_t flags); +int json_dumpf(const json_t *json, FILE *output, size_t flags); +int json_dump_file(const json_t *json, const char *path, size_t flags); +int json_dump_callback(const json_t *json, json_dump_callback_t callback, void *data, size_t flags); + +/* custom memory allocation */ + +typedef void *(*json_malloc_t)(size_t); +typedef void (*json_free_t)(void *); + +void json_set_alloc_funcs(json_malloc_t malloc_fn, json_free_t free_fn); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/compat/jansson-2.6/src/jansson_config.h.in b/compat/jansson-2.6/src/jansson_config.h.in new file mode 100644 index 0000000..785801f --- /dev/null +++ b/compat/jansson-2.6/src/jansson_config.h.in @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2010-2013 Petri Lehtinen + * + * Jansson is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + * + * + * This file specifies a part of the site-specific configuration for + * Jansson, namely those things that affect the public API in + * jansson.h. + * + * The configure script copies this file to jansson_config.h and + * replaces @var@ substitutions by values that fit your system. If you + * cannot run the configure script, you can do the value substitution + * by hand. + */ + +#ifndef JANSSON_CONFIG_H +#define JANSSON_CONFIG_H + +/* If your compiler supports the inline keyword in C, JSON_INLINE is + defined to `inline', otherwise empty. In C++, the inline is always + supported. */ +#ifdef __cplusplus +#define JSON_INLINE inline +#else +#define JSON_INLINE @json_inline@ +#endif + +/* If your compiler supports the `long long` type and the strtoll() + library function, JSON_INTEGER_IS_LONG_LONG is defined to 1, + otherwise to 0. */ +#define JSON_INTEGER_IS_LONG_LONG @json_have_long_long@ + +/* If locale.h and localeconv() are available, define to 1, + otherwise to 0. */ +#define JSON_HAVE_LOCALECONV @json_have_localeconv@ + +#endif diff --git a/compat/jansson-2.6/src/jansson_private.h b/compat/jansson-2.6/src/jansson_private.h new file mode 100644 index 0000000..403b53a --- /dev/null +++ b/compat/jansson-2.6/src/jansson_private.h @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2009-2013 Petri Lehtinen + * + * Jansson is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef JANSSON_PRIVATE_H +#define JANSSON_PRIVATE_H + +#include +#include "jansson.h" +#include "hashtable.h" +#include "strbuffer.h" + +#define container_of(ptr_, type_, member_) \ + ((type_ *)((char *)ptr_ - offsetof(type_, member_))) + +/* On some platforms, max() may already be defined */ +#ifndef max +#define max(a, b) ((a) > (b) ? (a) : (b)) +#endif + +/* va_copy is a C99 feature. In C89 implementations, it's sometimes + available as __va_copy. If not, memcpy() should do the trick. */ +#ifndef va_copy +#ifdef __va_copy +#define va_copy __va_copy +#else +#define va_copy(a, b) memcpy(&(a), &(b), sizeof(va_list)) +#endif +#endif + +typedef struct { + json_t json; + hashtable_t hashtable; + size_t serial; + int visited; +} json_object_t; + +typedef struct { + json_t json; + size_t size; + size_t entries; + json_t **table; + int visited; +} json_array_t; + +typedef struct { + json_t json; + char *value; +} json_string_t; + +typedef struct { + json_t json; + double value; +} json_real_t; + +typedef struct { + json_t json; + json_int_t value; +} json_integer_t; + +#define json_to_object(json_) container_of(json_, json_object_t, json) +#define json_to_array(json_) container_of(json_, json_array_t, json) +#define json_to_string(json_) container_of(json_, json_string_t, json) +#define json_to_real(json_) container_of(json_, json_real_t, json) +#define json_to_integer(json_) container_of(json_, json_integer_t, json) + +void jsonp_error_init(json_error_t *error, const char *source); +void jsonp_error_set_source(json_error_t *error, const char *source); +void jsonp_error_set(json_error_t *error, int line, int column, + size_t position, const char *msg, ...); +void jsonp_error_vset(json_error_t *error, int line, int column, + size_t position, const char *msg, va_list ap); + +/* Locale independent string<->double conversions */ +int jsonp_strtod(strbuffer_t *strbuffer, double *out); +int jsonp_dtostr(char *buffer, size_t size, double value); + +/* Wrappers for custom memory functions */ +void* jsonp_malloc(size_t size); +void jsonp_free(void *ptr); +char *jsonp_strndup(const char *str, size_t length); +char *jsonp_strdup(const char *str); + +/* Windows compatibility */ +#ifdef _WIN32 +#define snprintf _snprintf +#define vsnprintf _vsnprintf +#endif + +#endif diff --git a/compat/jansson-2.6/src/load.c b/compat/jansson-2.6/src/load.c new file mode 100644 index 0000000..c5536f5 --- /dev/null +++ b/compat/jansson-2.6/src/load.c @@ -0,0 +1,1077 @@ +/* + * Copyright (c) 2009-2013 Petri Lehtinen + * + * Jansson is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include +#include + +#include "jansson.h" +#include "jansson_private.h" +#include "strbuffer.h" +#include "utf.h" + +#define STREAM_STATE_OK 0 +#define STREAM_STATE_EOF -1 +#define STREAM_STATE_ERROR -2 + +#define TOKEN_INVALID -1 +#define TOKEN_EOF 0 +#define TOKEN_STRING 256 +#define TOKEN_INTEGER 257 +#define TOKEN_REAL 258 +#define TOKEN_TRUE 259 +#define TOKEN_FALSE 260 +#define TOKEN_NULL 261 + +/* Locale independent versions of isxxx() functions */ +#define l_isupper(c) ('A' <= (c) && (c) <= 'Z') +#define l_islower(c) ('a' <= (c) && (c) <= 'z') +#define l_isalpha(c) (l_isupper(c) || l_islower(c)) +#define l_isdigit(c) ('0' <= (c) && (c) <= '9') +#define l_isxdigit(c) \ + (l_isdigit(c) || ('A' <= (c) && (c) <= 'F') || ('a' <= (c) && (c) <= 'f')) + +/* Read one byte from stream, convert to unsigned char, then int, and + return. return EOF on end of file. This corresponds to the + behaviour of fgetc(). */ +typedef int (*get_func)(void *data); + +typedef struct { + get_func get; + void *data; + char buffer[5]; + size_t buffer_pos; + int state; + int line; + int column, last_column; + size_t position; +} stream_t; + +typedef struct { + stream_t stream; + strbuffer_t saved_text; + int token; + union { + char *string; + json_int_t integer; + double real; + } value; +} lex_t; + +#define stream_to_lex(stream) container_of(stream, lex_t, stream) + + +/*** error reporting ***/ + +static void error_set(json_error_t *error, const lex_t *lex, + const char *msg, ...) +{ + va_list ap; + char msg_text[JSON_ERROR_TEXT_LENGTH]; + char msg_with_context[JSON_ERROR_TEXT_LENGTH]; + + int line = -1, col = -1; + size_t pos = 0; + const char *result = msg_text; + + if(!error) + return; + + va_start(ap, msg); + vsnprintf(msg_text, JSON_ERROR_TEXT_LENGTH, msg, ap); + msg_text[JSON_ERROR_TEXT_LENGTH - 1] = '\0'; + va_end(ap); + + if(lex) + { + const char *saved_text = strbuffer_value(&lex->saved_text); + + line = lex->stream.line; + col = lex->stream.column; + pos = lex->stream.position; + + if(saved_text && saved_text[0]) + { + if(lex->saved_text.length <= 20) { + snprintf(msg_with_context, JSON_ERROR_TEXT_LENGTH, + "%s near '%s'", msg_text, saved_text); + msg_with_context[JSON_ERROR_TEXT_LENGTH - 1] = '\0'; + result = msg_with_context; + } + } + else + { + if(lex->stream.state == STREAM_STATE_ERROR) { + /* No context for UTF-8 decoding errors */ + result = msg_text; + } + else { + snprintf(msg_with_context, JSON_ERROR_TEXT_LENGTH, + "%s near end of file", msg_text); + msg_with_context[JSON_ERROR_TEXT_LENGTH - 1] = '\0'; + result = msg_with_context; + } + } + } + + jsonp_error_set(error, line, col, pos, "%s", result); +} + + +/*** lexical analyzer ***/ + +static void +stream_init(stream_t *stream, get_func get, void *data) +{ + stream->get = get; + stream->data = data; + stream->buffer[0] = '\0'; + stream->buffer_pos = 0; + + stream->state = STREAM_STATE_OK; + stream->line = 1; + stream->column = 0; + stream->position = 0; +} + +static int stream_get(stream_t *stream, json_error_t *error) +{ + int c; + + if(stream->state != STREAM_STATE_OK) + return stream->state; + + if(!stream->buffer[stream->buffer_pos]) + { + c = stream->get(stream->data); + if(c == EOF) { + stream->state = STREAM_STATE_EOF; + return STREAM_STATE_EOF; + } + + stream->buffer[0] = c; + stream->buffer_pos = 0; + + if(0x80 <= c && c <= 0xFF) + { + /* multi-byte UTF-8 sequence */ + int i, count; + + count = utf8_check_first(c); + if(!count) + goto out; + + assert(count >= 2); + + for(i = 1; i < count; i++) + stream->buffer[i] = stream->get(stream->data); + + if(!utf8_check_full(stream->buffer, count, NULL)) + goto out; + + stream->buffer[count] = '\0'; + } + else + stream->buffer[1] = '\0'; + } + + c = stream->buffer[stream->buffer_pos++]; + + stream->position++; + if(c == '\n') { + stream->line++; + stream->last_column = stream->column; + stream->column = 0; + } + else if(utf8_check_first(c)) { + /* track the Unicode character column, so increment only if + this is the first character of a UTF-8 sequence */ + stream->column++; + } + + return c; + +out: + stream->state = STREAM_STATE_ERROR; + error_set(error, stream_to_lex(stream), "unable to decode byte 0x%x", c); + return STREAM_STATE_ERROR; +} + +static void stream_unget(stream_t *stream, int c) +{ + if(c == STREAM_STATE_EOF || c == STREAM_STATE_ERROR) + return; + + stream->position--; + if(c == '\n') { + stream->line--; + stream->column = stream->last_column; + } + else if(utf8_check_first(c)) + stream->column--; + + assert(stream->buffer_pos > 0); + stream->buffer_pos--; + assert(stream->buffer[stream->buffer_pos] == c); +} + + +static int lex_get(lex_t *lex, json_error_t *error) +{ + return stream_get(&lex->stream, error); +} + +static void lex_save(lex_t *lex, int c) +{ + strbuffer_append_byte(&lex->saved_text, c); +} + +static int lex_get_save(lex_t *lex, json_error_t *error) +{ + int c = stream_get(&lex->stream, error); + if(c != STREAM_STATE_EOF && c != STREAM_STATE_ERROR) + lex_save(lex, c); + return c; +} + +static void lex_unget(lex_t *lex, int c) +{ + stream_unget(&lex->stream, c); +} + +static void lex_unget_unsave(lex_t *lex, int c) +{ + if(c != STREAM_STATE_EOF && c != STREAM_STATE_ERROR) { + /* Since we treat warnings as errors, when assertions are turned + * off the "d" variable would be set but never used. Which is + * treated as an error by GCC. + */ + #ifndef NDEBUG + char d; + #endif + stream_unget(&lex->stream, c); + #ifndef NDEBUG + d = + #endif + strbuffer_pop(&lex->saved_text); + assert(c == d); + } +} + +static void lex_save_cached(lex_t *lex) +{ + while(lex->stream.buffer[lex->stream.buffer_pos] != '\0') + { + lex_save(lex, lex->stream.buffer[lex->stream.buffer_pos]); + lex->stream.buffer_pos++; + lex->stream.position++; + } +} + +/* assumes that str points to 'u' plus at least 4 valid hex digits */ +static int32_t decode_unicode_escape(const char *str) +{ + int i; + int32_t value = 0; + + assert(str[0] == 'u'); + + for(i = 1; i <= 4; i++) { + char c = str[i]; + value <<= 4; + if(l_isdigit(c)) + value += c - '0'; + else if(l_islower(c)) + value += c - 'a' + 10; + else if(l_isupper(c)) + value += c - 'A' + 10; + else + assert(0); + } + + return value; +} + +static void lex_scan_string(lex_t *lex, json_error_t *error) +{ + int c; + const char *p; + char *t; + int i; + + lex->value.string = NULL; + lex->token = TOKEN_INVALID; + + c = lex_get_save(lex, error); + + while(c != '"') { + if(c == STREAM_STATE_ERROR) + goto out; + + else if(c == STREAM_STATE_EOF) { + error_set(error, lex, "premature end of input"); + goto out; + } + + else if(0 <= c && c <= 0x1F) { + /* control character */ + lex_unget_unsave(lex, c); + if(c == '\n') + error_set(error, lex, "unexpected newline", c); + else + error_set(error, lex, "control character 0x%x", c); + goto out; + } + + else if(c == '\\') { + c = lex_get_save(lex, error); + if(c == 'u') { + c = lex_get_save(lex, error); + for(i = 0; i < 4; i++) { + if(!l_isxdigit(c)) { + error_set(error, lex, "invalid escape"); + goto out; + } + c = lex_get_save(lex, error); + } + } + else if(c == '"' || c == '\\' || c == '/' || c == 'b' || + c == 'f' || c == 'n' || c == 'r' || c == 't') + c = lex_get_save(lex, error); + else { + error_set(error, lex, "invalid escape"); + goto out; + } + } + else + c = lex_get_save(lex, error); + } + + /* the actual value is at most of the same length as the source + string, because: + - shortcut escapes (e.g. "\t") (length 2) are converted to 1 byte + - a single \uXXXX escape (length 6) is converted to at most 3 bytes + - two \uXXXX escapes (length 12) forming an UTF-16 surrogate pair + are converted to 4 bytes + */ + lex->value.string = jsonp_malloc(lex->saved_text.length + 1); + if(!lex->value.string) { + /* this is not very nice, since TOKEN_INVALID is returned */ + goto out; + } + + /* the target */ + t = lex->value.string; + + /* + 1 to skip the " */ + p = strbuffer_value(&lex->saved_text) + 1; + + while(*p != '"') { + if(*p == '\\') { + p++; + if(*p == 'u') { + char buffer[4]; + int length; + int32_t value; + + value = decode_unicode_escape(p); + p += 5; + + if(0xD800 <= value && value <= 0xDBFF) { + /* surrogate pair */ + if(*p == '\\' && *(p + 1) == 'u') { + int32_t value2 = decode_unicode_escape(++p); + p += 5; + + if(0xDC00 <= value2 && value2 <= 0xDFFF) { + /* valid second surrogate */ + value = + ((value - 0xD800) << 10) + + (value2 - 0xDC00) + + 0x10000; + } + else { + /* invalid second surrogate */ + error_set(error, lex, + "invalid Unicode '\\u%04X\\u%04X'", + value, value2); + goto out; + } + } + else { + /* no second surrogate */ + error_set(error, lex, "invalid Unicode '\\u%04X'", + value); + goto out; + } + } + else if(0xDC00 <= value && value <= 0xDFFF) { + error_set(error, lex, "invalid Unicode '\\u%04X'", value); + goto out; + } + else if(value == 0) + { + error_set(error, lex, "\\u0000 is not allowed"); + goto out; + } + + if(utf8_encode(value, buffer, &length)) + assert(0); + + memcpy(t, buffer, length); + t += length; + } + else { + switch(*p) { + case '"': case '\\': case '/': + *t = *p; break; + case 'b': *t = '\b'; break; + case 'f': *t = '\f'; break; + case 'n': *t = '\n'; break; + case 'r': *t = '\r'; break; + case 't': *t = '\t'; break; + default: assert(0); + } + t++; + p++; + } + } + else + *(t++) = *(p++); + } + *t = '\0'; + lex->token = TOKEN_STRING; + return; + +out: + jsonp_free(lex->value.string); +} + +#ifndef JANSSON_USING_CMAKE /* disabled if using cmake */ +#if JSON_INTEGER_IS_LONG_LONG +#ifdef _MSC_VER /* Microsoft Visual Studio */ +#define json_strtoint _strtoi64 +#else +#define json_strtoint strtoll +#endif +#else +#define json_strtoint strtol +#endif +#endif + +static int lex_scan_number(lex_t *lex, int c, json_error_t *error) +{ + const char *saved_text; + char *end; + double value; + + lex->token = TOKEN_INVALID; + + if(c == '-') + c = lex_get_save(lex, error); + + if(c == '0') { + c = lex_get_save(lex, error); + if(l_isdigit(c)) { + lex_unget_unsave(lex, c); + goto out; + } + } + else if(l_isdigit(c)) { + c = lex_get_save(lex, error); + while(l_isdigit(c)) + c = lex_get_save(lex, error); + } + else { + lex_unget_unsave(lex, c); + goto out; + } + + if(c != '.' && c != 'E' && c != 'e') { + json_int_t value; + + lex_unget_unsave(lex, c); + + saved_text = strbuffer_value(&lex->saved_text); + + errno = 0; + value = json_strtoint(saved_text, &end, 10); + if(errno == ERANGE) { + if(value < 0) + error_set(error, lex, "too big negative integer"); + else + error_set(error, lex, "too big integer"); + goto out; + } + + assert(end == saved_text + lex->saved_text.length); + + lex->token = TOKEN_INTEGER; + lex->value.integer = value; + return 0; + } + + if(c == '.') { + c = lex_get(lex, error); + if(!l_isdigit(c)) { + lex_unget(lex, c); + goto out; + } + lex_save(lex, c); + + c = lex_get_save(lex, error); + while(l_isdigit(c)) + c = lex_get_save(lex, error); + } + + if(c == 'E' || c == 'e') { + c = lex_get_save(lex, error); + if(c == '+' || c == '-') + c = lex_get_save(lex, error); + + if(!l_isdigit(c)) { + lex_unget_unsave(lex, c); + goto out; + } + + c = lex_get_save(lex, error); + while(l_isdigit(c)) + c = lex_get_save(lex, error); + } + + lex_unget_unsave(lex, c); + + if(jsonp_strtod(&lex->saved_text, &value)) { + error_set(error, lex, "real number overflow"); + goto out; + } + + lex->token = TOKEN_REAL; + lex->value.real = value; + return 0; + +out: + return -1; +} + +static int lex_scan(lex_t *lex, json_error_t *error) +{ + int c; + + strbuffer_clear(&lex->saved_text); + + if(lex->token == TOKEN_STRING) { + jsonp_free(lex->value.string); + lex->value.string = NULL; + } + + c = lex_get(lex, error); + while(c == ' ' || c == '\t' || c == '\n' || c == '\r') + c = lex_get(lex, error); + + if(c == STREAM_STATE_EOF) { + lex->token = TOKEN_EOF; + goto out; + } + + if(c == STREAM_STATE_ERROR) { + lex->token = TOKEN_INVALID; + goto out; + } + + lex_save(lex, c); + + if(c == '{' || c == '}' || c == '[' || c == ']' || c == ':' || c == ',') + lex->token = c; + + else if(c == '"') + lex_scan_string(lex, error); + + else if(l_isdigit(c) || c == '-') { + if(lex_scan_number(lex, c, error)) + goto out; + } + + else if(l_isalpha(c)) { + /* eat up the whole identifier for clearer error messages */ + const char *saved_text; + + c = lex_get_save(lex, error); + while(l_isalpha(c)) + c = lex_get_save(lex, error); + lex_unget_unsave(lex, c); + + saved_text = strbuffer_value(&lex->saved_text); + + if(strcmp(saved_text, "true") == 0) + lex->token = TOKEN_TRUE; + else if(strcmp(saved_text, "false") == 0) + lex->token = TOKEN_FALSE; + else if(strcmp(saved_text, "null") == 0) + lex->token = TOKEN_NULL; + else + lex->token = TOKEN_INVALID; + } + + else { + /* save the rest of the input UTF-8 sequence to get an error + message of valid UTF-8 */ + lex_save_cached(lex); + lex->token = TOKEN_INVALID; + } + +out: + return lex->token; +} + +static char *lex_steal_string(lex_t *lex) +{ + char *result = NULL; + if(lex->token == TOKEN_STRING) + { + result = lex->value.string; + lex->value.string = NULL; + } + return result; +} + +static int lex_init(lex_t *lex, get_func get, void *data) +{ + stream_init(&lex->stream, get, data); + if(strbuffer_init(&lex->saved_text)) + return -1; + + lex->token = TOKEN_INVALID; + return 0; +} + +static void lex_close(lex_t *lex) +{ + if(lex->token == TOKEN_STRING) + jsonp_free(lex->value.string); + strbuffer_close(&lex->saved_text); +} + + +/*** parser ***/ + +static json_t *parse_value(lex_t *lex, size_t flags, json_error_t *error); + +static json_t *parse_object(lex_t *lex, size_t flags, json_error_t *error) +{ + json_t *object = json_object(); + if(!object) + return NULL; + + lex_scan(lex, error); + if(lex->token == '}') + return object; + + while(1) { + char *key; + json_t *value; + + if(lex->token != TOKEN_STRING) { + error_set(error, lex, "string or '}' expected"); + goto error; + } + + key = lex_steal_string(lex); + if(!key) + return NULL; + + if(flags & JSON_REJECT_DUPLICATES) { + if(json_object_get(object, key)) { + jsonp_free(key); + error_set(error, lex, "duplicate object key"); + goto error; + } + } + + lex_scan(lex, error); + if(lex->token != ':') { + jsonp_free(key); + error_set(error, lex, "':' expected"); + goto error; + } + + lex_scan(lex, error); + value = parse_value(lex, flags, error); + if(!value) { + jsonp_free(key); + goto error; + } + + if(json_object_set_nocheck(object, key, value)) { + jsonp_free(key); + json_decref(value); + goto error; + } + + json_decref(value); + jsonp_free(key); + + lex_scan(lex, error); + if(lex->token != ',') + break; + + lex_scan(lex, error); + } + + if(lex->token != '}') { + error_set(error, lex, "'}' expected"); + goto error; + } + + return object; + +error: + json_decref(object); + return NULL; +} + +static json_t *parse_array(lex_t *lex, size_t flags, json_error_t *error) +{ + json_t *array = json_array(); + if(!array) + return NULL; + + lex_scan(lex, error); + if(lex->token == ']') + return array; + + while(lex->token) { + json_t *elem = parse_value(lex, flags, error); + if(!elem) + goto error; + + if(json_array_append(array, elem)) { + json_decref(elem); + goto error; + } + json_decref(elem); + + lex_scan(lex, error); + if(lex->token != ',') + break; + + lex_scan(lex, error); + } + + if(lex->token != ']') { + error_set(error, lex, "']' expected"); + goto error; + } + + return array; + +error: + json_decref(array); + return NULL; +} + +static json_t *parse_value(lex_t *lex, size_t flags, json_error_t *error) +{ + json_t *json; + double value; + + switch(lex->token) { + case TOKEN_STRING: { + json = json_string_nocheck(lex->value.string); + break; + } + + case TOKEN_INTEGER: { + if (flags & JSON_DECODE_INT_AS_REAL) { + if(jsonp_strtod(&lex->saved_text, &value)) { + error_set(error, lex, "real number overflow"); + return NULL; + } + json = json_real(value); + } else { + json = json_integer(lex->value.integer); + } + break; + } + + case TOKEN_REAL: { + json = json_real(lex->value.real); + break; + } + + case TOKEN_TRUE: + json = json_true(); + break; + + case TOKEN_FALSE: + json = json_false(); + break; + + case TOKEN_NULL: + json = json_null(); + break; + + case '{': + json = parse_object(lex, flags, error); + break; + + case '[': + json = parse_array(lex, flags, error); + break; + + case TOKEN_INVALID: + error_set(error, lex, "invalid token"); + return NULL; + + default: + error_set(error, lex, "unexpected token"); + return NULL; + } + + if(!json) + return NULL; + + return json; +} + +static json_t *parse_json(lex_t *lex, size_t flags, json_error_t *error) +{ + json_t *result; + + lex_scan(lex, error); + if(!(flags & JSON_DECODE_ANY)) { + if(lex->token != '[' && lex->token != '{') { + error_set(error, lex, "'[' or '{' expected"); + return NULL; + } + } + + result = parse_value(lex, flags, error); + if(!result) + return NULL; + + if(!(flags & JSON_DISABLE_EOF_CHECK)) { + lex_scan(lex, error); + if(lex->token != TOKEN_EOF) { + error_set(error, lex, "end of file expected"); + json_decref(result); + return NULL; + } + } + + if(error) { + /* Save the position even though there was no error */ + error->position = lex->stream.position; + } + + return result; +} + +typedef struct +{ + const char *data; + int pos; +} string_data_t; + +static int string_get(void *data) +{ + char c; + string_data_t *stream = (string_data_t *)data; + c = stream->data[stream->pos]; + if(c == '\0') + return EOF; + else + { + stream->pos++; + return (unsigned char)c; + } +} + +json_t *json_loads(const char *string, size_t flags, json_error_t *error) +{ + lex_t lex; + json_t *result; + string_data_t stream_data; + + jsonp_error_init(error, ""); + + if (string == NULL) { + error_set(error, NULL, "wrong arguments"); + return NULL; + } + + stream_data.data = string; + stream_data.pos = 0; + + if(lex_init(&lex, string_get, (void *)&stream_data)) + return NULL; + + result = parse_json(&lex, flags, error); + + lex_close(&lex); + return result; +} + +typedef struct +{ + const char *data; + size_t len; + size_t pos; +} buffer_data_t; + +static int buffer_get(void *data) +{ + char c; + buffer_data_t *stream = data; + if(stream->pos >= stream->len) + return EOF; + + c = stream->data[stream->pos]; + stream->pos++; + return (unsigned char)c; +} + +json_t *json_loadb(const char *buffer, size_t buflen, size_t flags, json_error_t *error) +{ + lex_t lex; + json_t *result; + buffer_data_t stream_data; + + jsonp_error_init(error, ""); + + if (buffer == NULL) { + error_set(error, NULL, "wrong arguments"); + return NULL; + } + + stream_data.data = buffer; + stream_data.pos = 0; + stream_data.len = buflen; + + if(lex_init(&lex, buffer_get, (void *)&stream_data)) + return NULL; + + result = parse_json(&lex, flags, error); + + lex_close(&lex); + return result; +} + +json_t *json_loadf(FILE *input, size_t flags, json_error_t *error) +{ + lex_t lex; + const char *source; + json_t *result; + + if(input == stdin) + source = ""; + else + source = ""; + + jsonp_error_init(error, source); + + if (input == NULL) { + error_set(error, NULL, "wrong arguments"); + return NULL; + } + + if(lex_init(&lex, (get_func)fgetc, input)) + return NULL; + + result = parse_json(&lex, flags, error); + + lex_close(&lex); + return result; +} + +json_t *json_load_file(const char *path, size_t flags, json_error_t *error) +{ + json_t *result; + FILE *fp; + + jsonp_error_init(error, path); + + if (path == NULL) { + error_set(error, NULL, "wrong arguments"); + return NULL; + } + + fp = fopen(path, "rb"); + if(!fp) + { + error_set(error, NULL, "unable to open %s: %s", + path, strerror(errno)); + return NULL; + } + + result = json_loadf(fp, flags, error); + + fclose(fp); + return result; +} + +#define MAX_BUF_LEN 1024 + +typedef struct +{ + char data[MAX_BUF_LEN]; + size_t len; + size_t pos; + json_load_callback_t callback; + void *arg; +} callback_data_t; + +static int callback_get(void *data) +{ + char c; + callback_data_t *stream = data; + + if(stream->pos >= stream->len) { + stream->pos = 0; + stream->len = stream->callback(stream->data, MAX_BUF_LEN, stream->arg); + if(stream->len == 0 || stream->len == (size_t)-1) + return EOF; + } + + c = stream->data[stream->pos]; + stream->pos++; + return (unsigned char)c; +} + +json_t *json_load_callback(json_load_callback_t callback, void *arg, size_t flags, json_error_t *error) +{ + lex_t lex; + json_t *result; + + callback_data_t stream_data; + + memset(&stream_data, 0, sizeof(stream_data)); + stream_data.callback = callback; + stream_data.arg = arg; + + jsonp_error_init(error, ""); + + if (callback == NULL) { + error_set(error, NULL, "wrong arguments"); + return NULL; + } + + if(lex_init(&lex, (get_func)callback_get, &stream_data)) + return NULL; + + result = parse_json(&lex, flags, error); + + lex_close(&lex); + return result; +} diff --git a/compat/jansson-2.6/src/memory.c b/compat/jansson-2.6/src/memory.c new file mode 100644 index 0000000..eb6cec5 --- /dev/null +++ b/compat/jansson-2.6/src/memory.c @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2009-2013 Petri Lehtinen + * Copyright (c) 2011-2012 Basile Starynkevitch + * + * Jansson is free software; you can redistribute it and/or modify it + * under the terms of the MIT license. See LICENSE for details. + */ + +#include +#include + +#include "jansson.h" +#include "jansson_private.h" + +/* memory function pointers */ +static json_malloc_t do_malloc = malloc; +static json_free_t do_free = free; + +void *jsonp_malloc(size_t size) +{ + if(!size) + return NULL; + + return (*do_malloc)(size); +} + +void jsonp_free(void *ptr) +{ + if(!ptr) + return; + + (*do_free)(ptr); +} + +char *jsonp_strdup(const char *str) +{ + char *new_str; + size_t len; + + len = strlen(str); + if(len == (size_t)-1) + return NULL; + + new_str = jsonp_malloc(len + 1); + if(!new_str) + return NULL; + + memcpy(new_str, str, len + 1); + return new_str; +} + +void json_set_alloc_funcs(json_malloc_t malloc_fn, json_free_t free_fn) +{ + do_malloc = malloc_fn; + do_free = free_fn; +} diff --git a/compat/jansson-2.6/src/pack_unpack.c b/compat/jansson-2.6/src/pack_unpack.c new file mode 100644 index 0000000..0d932f7 --- /dev/null +++ b/compat/jansson-2.6/src/pack_unpack.c @@ -0,0 +1,762 @@ +/* + * Copyright (c) 2009-2013 Petri Lehtinen + * Copyright (c) 2011-2012 Graeme Smecher + * + * Jansson is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#include +#include "jansson.h" +#include "jansson_private.h" +#include "utf.h" + +typedef struct { + int line; + int column; + size_t pos; + char token; +} token_t; + +typedef struct { + const char *start; + const char *fmt; + token_t prev_token; + token_t token; + token_t next_token; + json_error_t *error; + size_t flags; + int line; + int column; + size_t pos; +} scanner_t; + +#define token(scanner) ((scanner)->token.token) + +static const char * const type_names[] = { + "object", + "array", + "string", + "integer", + "real", + "true", + "false", + "null" +}; + +#define type_name(x) type_names[json_typeof(x)] + +static const char unpack_value_starters[] = "{[siIbfFOon"; + + +static void scanner_init(scanner_t *s, json_error_t *error, + size_t flags, const char *fmt) +{ + s->error = error; + s->flags = flags; + s->fmt = s->start = fmt; + memset(&s->prev_token, 0, sizeof(token_t)); + memset(&s->token, 0, sizeof(token_t)); + memset(&s->next_token, 0, sizeof(token_t)); + s->line = 1; + s->column = 0; + s->pos = 0; +} + +static void next_token(scanner_t *s) +{ + const char *t; + s->prev_token = s->token; + + if(s->next_token.line) { + s->token = s->next_token; + s->next_token.line = 0; + return; + } + + t = s->fmt; + s->column++; + s->pos++; + + /* skip space and ignored chars */ + while(*t == ' ' || *t == '\t' || *t == '\n' || *t == ',' || *t == ':') { + if(*t == '\n') { + s->line++; + s->column = 1; + } + else + s->column++; + + s->pos++; + t++; + } + + s->token.token = *t; + s->token.line = s->line; + s->token.column = s->column; + s->token.pos = s->pos; + + t++; + s->fmt = t; +} + +static void prev_token(scanner_t *s) +{ + s->next_token = s->token; + s->token = s->prev_token; +} + +static void set_error(scanner_t *s, const char *source, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + + jsonp_error_vset(s->error, s->token.line, s->token.column, s->token.pos, + fmt, ap); + + jsonp_error_set_source(s->error, source); + + va_end(ap); +} + +static json_t *pack(scanner_t *s, va_list *ap); + + +/* ours will be set to 1 if jsonp_free() must be called for the result + afterwards */ +static char *read_string(scanner_t *s, va_list *ap, + const char *purpose, int *ours) +{ + char t; + strbuffer_t strbuff; + const char *str; + size_t length; + char *result; + + next_token(s); + t = token(s); + prev_token(s); + + if(t != '#' && t != '+') { + /* Optimize the simple case */ + str = va_arg(*ap, const char *); + + if(!str) { + set_error(s, "", "NULL string argument"); + return NULL; + } + + if(!utf8_check_string(str, -1)) { + set_error(s, "", "Invalid UTF-8 %s", purpose); + return NULL; + } + + *ours = 0; + return (char *)str; + } + + strbuffer_init(&strbuff); + + while(1) { + str = va_arg(*ap, const char *); + if(!str) { + set_error(s, "", "NULL string argument"); + strbuffer_close(&strbuff); + return NULL; + } + + next_token(s); + + if(token(s) == '#') { + length = va_arg(*ap, int); + } + else { + prev_token(s); + length = strlen(str); + } + + if(strbuffer_append_bytes(&strbuff, str, length) == -1) { + set_error(s, "", "Out of memory"); + strbuffer_close(&strbuff); + return NULL; + } + + next_token(s); + if(token(s) != '+') { + prev_token(s); + break; + } + } + + result = strbuffer_steal_value(&strbuff); + + if(!utf8_check_string(result, -1)) { + set_error(s, "", "Invalid UTF-8 %s", purpose); + return NULL; + } + + *ours = 1; + return result; +} + +static json_t *pack_object(scanner_t *s, va_list *ap) +{ + json_t *object = json_object(); + next_token(s); + + while(token(s) != '}') { + char *key; + int ours; + json_t *value; + + if(!token(s)) { + set_error(s, "", "Unexpected end of format string"); + goto error; + } + + if(token(s) != 's') { + set_error(s, "", "Expected format 's', got '%c'", token(s)); + goto error; + } + + key = read_string(s, ap, "object key", &ours); + if(!key) + goto error; + + next_token(s); + + value = pack(s, ap); + if(!value) + goto error; + + if(json_object_set_new_nocheck(object, key, value)) { + if(ours) + jsonp_free(key); + + set_error(s, "", "Unable to add key \"%s\"", key); + goto error; + } + + if(ours) + jsonp_free(key); + + next_token(s); + } + + return object; + +error: + json_decref(object); + return NULL; +} + +static json_t *pack_array(scanner_t *s, va_list *ap) +{ + json_t *array = json_array(); + next_token(s); + + while(token(s) != ']') { + json_t *value; + + if(!token(s)) { + set_error(s, "", "Unexpected end of format string"); + goto error; + } + + value = pack(s, ap); + if(!value) + goto error; + + if(json_array_append_new(array, value)) { + set_error(s, "", "Unable to append to array"); + goto error; + } + + next_token(s); + } + return array; + +error: + json_decref(array); + return NULL; +} + +static json_t *pack(scanner_t *s, va_list *ap) +{ + switch(token(s)) { + case '{': + return pack_object(s, ap); + + case '[': + return pack_array(s, ap); + + case 's': { /* string */ + char *str; + int ours; + json_t *result; + + str = read_string(s, ap, "string", &ours); + if(!str) + return NULL; + + result = json_string_nocheck(str); + if(ours) + jsonp_free(str); + + return result; + } + + case 'n': /* null */ + return json_null(); + + case 'b': /* boolean */ + return va_arg(*ap, int) ? json_true() : json_false(); + + case 'i': /* integer from int */ + return json_integer(va_arg(*ap, int)); + + case 'I': /* integer from json_int_t */ + return json_integer(va_arg(*ap, json_int_t)); + + case 'f': /* real */ + return json_real(va_arg(*ap, double)); + + case 'O': /* a json_t object; increments refcount */ + return json_incref(va_arg(*ap, json_t *)); + + case 'o': /* a json_t object; doesn't increment refcount */ + return va_arg(*ap, json_t *); + + default: + set_error(s, "", "Unexpected format character '%c'", + token(s)); + return NULL; + } +} + +static int unpack(scanner_t *s, json_t *root, va_list *ap); + +static int unpack_object(scanner_t *s, json_t *root, va_list *ap) +{ + int ret = -1; + int strict = 0; + + /* Use a set (emulated by a hashtable) to check that all object + keys are accessed. Checking that the correct number of keys + were accessed is not enough, as the same key can be unpacked + multiple times. + */ + hashtable_t key_set; + + if(hashtable_init(&key_set)) { + set_error(s, "", "Out of memory"); + return -1; + } + + if(root && !json_is_object(root)) { + set_error(s, "", "Expected object, got %s", + type_name(root)); + goto out; + } + next_token(s); + + while(token(s) != '}') { + const char *key; + json_t *value; + int opt = 0; + + if(strict != 0) { + set_error(s, "", "Expected '}' after '%c', got '%c'", + (strict == 1 ? '!' : '*'), token(s)); + goto out; + } + + if(!token(s)) { + set_error(s, "", "Unexpected end of format string"); + goto out; + } + + if(token(s) == '!' || token(s) == '*') { + strict = (token(s) == '!' ? 1 : -1); + next_token(s); + continue; + } + + if(token(s) != 's') { + set_error(s, "", "Expected format 's', got '%c'", token(s)); + goto out; + } + + key = va_arg(*ap, const char *); + if(!key) { + set_error(s, "", "NULL object key"); + goto out; + } + + next_token(s); + + if(token(s) == '?') { + opt = 1; + next_token(s); + } + + if(!root) { + /* skipping */ + value = NULL; + } + else { + value = json_object_get(root, key); + if(!value && !opt) { + set_error(s, "", "Object item not found: %s", key); + goto out; + } + } + + if(unpack(s, value, ap)) + goto out; + + hashtable_set(&key_set, key, 0, json_null()); + next_token(s); + } + + if(strict == 0 && (s->flags & JSON_STRICT)) + strict = 1; + + if(root && strict == 1 && key_set.size != json_object_size(root)) { + long diff = (long)json_object_size(root) - (long)key_set.size; + set_error(s, "", "%li object item(s) left unpacked", diff); + goto out; + } + + ret = 0; + +out: + hashtable_close(&key_set); + return ret; +} + +static int unpack_array(scanner_t *s, json_t *root, va_list *ap) +{ + size_t i = 0; + int strict = 0; + + if(root && !json_is_array(root)) { + set_error(s, "", "Expected array, got %s", type_name(root)); + return -1; + } + next_token(s); + + while(token(s) != ']') { + json_t *value; + + if(strict != 0) { + set_error(s, "", "Expected ']' after '%c', got '%c'", + (strict == 1 ? '!' : '*'), + token(s)); + return -1; + } + + if(!token(s)) { + set_error(s, "", "Unexpected end of format string"); + return -1; + } + + if(token(s) == '!' || token(s) == '*') { + strict = (token(s) == '!' ? 1 : -1); + next_token(s); + continue; + } + + if(!strchr(unpack_value_starters, token(s))) { + set_error(s, "", "Unexpected format character '%c'", + token(s)); + return -1; + } + + if(!root) { + /* skipping */ + value = NULL; + } + else { + value = json_array_get(root, i); + if(!value) { + set_error(s, "", "Array index %lu out of range", + (unsigned long)i); + return -1; + } + } + + if(unpack(s, value, ap)) + return -1; + + next_token(s); + i++; + } + + if(strict == 0 && (s->flags & JSON_STRICT)) + strict = 1; + + if(root && strict == 1 && i != json_array_size(root)) { + long diff = (long)json_array_size(root) - (long)i; + set_error(s, "", "%li array item(s) left unpacked", diff); + return -1; + } + + return 0; +} + +static int unpack(scanner_t *s, json_t *root, va_list *ap) +{ + switch(token(s)) + { + case '{': + return unpack_object(s, root, ap); + + case '[': + return unpack_array(s, root, ap); + + case 's': + if(root && !json_is_string(root)) { + set_error(s, "", "Expected string, got %s", + type_name(root)); + return -1; + } + + if(!(s->flags & JSON_VALIDATE_ONLY)) { + const char **target; + + target = va_arg(*ap, const char **); + if(!target) { + set_error(s, "", "NULL string argument"); + return -1; + } + + if(root) + *target = json_string_value(root); + } + return 0; + + case 'i': + if(root && !json_is_integer(root)) { + set_error(s, "", "Expected integer, got %s", + type_name(root)); + return -1; + } + + if(!(s->flags & JSON_VALIDATE_ONLY)) { + int *target = va_arg(*ap, int*); + if(root) + *target = (int)json_integer_value(root); + } + + return 0; + + case 'I': + if(root && !json_is_integer(root)) { + set_error(s, "", "Expected integer, got %s", + type_name(root)); + return -1; + } + + if(!(s->flags & JSON_VALIDATE_ONLY)) { + json_int_t *target = va_arg(*ap, json_int_t*); + if(root) + *target = json_integer_value(root); + } + + return 0; + + case 'b': + if(root && !json_is_boolean(root)) { + set_error(s, "", "Expected true or false, got %s", + type_name(root)); + return -1; + } + + if(!(s->flags & JSON_VALIDATE_ONLY)) { + int *target = va_arg(*ap, int*); + if(root) + *target = json_is_true(root); + } + + return 0; + + case 'f': + if(root && !json_is_real(root)) { + set_error(s, "", "Expected real, got %s", + type_name(root)); + return -1; + } + + if(!(s->flags & JSON_VALIDATE_ONLY)) { + double *target = va_arg(*ap, double*); + if(root) + *target = json_real_value(root); + } + + return 0; + + case 'F': + if(root && !json_is_number(root)) { + set_error(s, "", "Expected real or integer, got %s", + type_name(root)); + return -1; + } + + if(!(s->flags & JSON_VALIDATE_ONLY)) { + double *target = va_arg(*ap, double*); + if(root) + *target = json_number_value(root); + } + + return 0; + + case 'O': + if(root && !(s->flags & JSON_VALIDATE_ONLY)) + json_incref(root); + /* Fall through */ + + case 'o': + if(!(s->flags & JSON_VALIDATE_ONLY)) { + json_t **target = va_arg(*ap, json_t**); + if(root) + *target = root; + } + + return 0; + + case 'n': + /* Never assign, just validate */ + if(root && !json_is_null(root)) { + set_error(s, "", "Expected null, got %s", + type_name(root)); + return -1; + } + return 0; + + default: + set_error(s, "", "Unexpected format character '%c'", + token(s)); + return -1; + } +} + +json_t *json_vpack_ex(json_error_t *error, size_t flags, + const char *fmt, va_list ap) +{ + scanner_t s; + va_list ap_copy; + json_t *value; + + if(!fmt || !*fmt) { + jsonp_error_init(error, ""); + jsonp_error_set(error, -1, -1, 0, "NULL or empty format string"); + return NULL; + } + jsonp_error_init(error, NULL); + + scanner_init(&s, error, flags, fmt); + next_token(&s); + + va_copy(ap_copy, ap); + value = pack(&s, &ap_copy); + va_end(ap_copy); + + if(!value) + return NULL; + + next_token(&s); + if(token(&s)) { + json_decref(value); + set_error(&s, "", "Garbage after format string"); + return NULL; + } + + return value; +} + +json_t *json_pack_ex(json_error_t *error, size_t flags, const char *fmt, ...) +{ + json_t *value; + va_list ap; + + va_start(ap, fmt); + value = json_vpack_ex(error, flags, fmt, ap); + va_end(ap); + + return value; +} + +json_t *json_pack(const char *fmt, ...) +{ + json_t *value; + va_list ap; + + va_start(ap, fmt); + value = json_vpack_ex(NULL, 0, fmt, ap); + va_end(ap); + + return value; +} + +int json_vunpack_ex(json_t *root, json_error_t *error, size_t flags, + const char *fmt, va_list ap) +{ + scanner_t s; + va_list ap_copy; + + if(!root) { + jsonp_error_init(error, ""); + jsonp_error_set(error, -1, -1, 0, "NULL root value"); + return -1; + } + + if(!fmt || !*fmt) { + jsonp_error_init(error, ""); + jsonp_error_set(error, -1, -1, 0, "NULL or empty format string"); + return -1; + } + jsonp_error_init(error, NULL); + + scanner_init(&s, error, flags, fmt); + next_token(&s); + + va_copy(ap_copy, ap); + if(unpack(&s, root, &ap_copy)) { + va_end(ap_copy); + return -1; + } + va_end(ap_copy); + + next_token(&s); + if(token(&s)) { + set_error(&s, "", "Garbage after format string"); + return -1; + } + + return 0; +} + +int json_unpack_ex(json_t *root, json_error_t *error, size_t flags, const char *fmt, ...) +{ + int ret; + va_list ap; + + va_start(ap, fmt); + ret = json_vunpack_ex(root, error, flags, fmt, ap); + va_end(ap); + + return ret; +} + +int json_unpack(json_t *root, const char *fmt, ...) +{ + int ret; + va_list ap; + + va_start(ap, fmt); + ret = json_vunpack_ex(root, NULL, 0, fmt, ap); + va_end(ap); + + return ret; +} diff --git a/compat/jansson-2.6/src/strbuffer.c b/compat/jansson-2.6/src/strbuffer.c new file mode 100644 index 0000000..2d6ff31 --- /dev/null +++ b/compat/jansson-2.6/src/strbuffer.c @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2009-2013 Petri Lehtinen + * + * Jansson is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include "jansson_private.h" +#include "strbuffer.h" + +#define STRBUFFER_MIN_SIZE 16 +#define STRBUFFER_FACTOR 2 +#define STRBUFFER_SIZE_MAX ((size_t)-1) + +int strbuffer_init(strbuffer_t *strbuff) +{ + strbuff->size = STRBUFFER_MIN_SIZE; + strbuff->length = 0; + + strbuff->value = jsonp_malloc(strbuff->size); + if(!strbuff->value) + return -1; + + /* initialize to empty */ + strbuff->value[0] = '\0'; + return 0; +} + +void strbuffer_close(strbuffer_t *strbuff) +{ + if(strbuff->value) + jsonp_free(strbuff->value); + + strbuff->size = 0; + strbuff->length = 0; + strbuff->value = NULL; +} + +void strbuffer_clear(strbuffer_t *strbuff) +{ + strbuff->length = 0; + strbuff->value[0] = '\0'; +} + +const char *strbuffer_value(const strbuffer_t *strbuff) +{ + return strbuff->value; +} + +char *strbuffer_steal_value(strbuffer_t *strbuff) +{ + char *result = strbuff->value; + strbuff->value = NULL; + return result; +} + +int strbuffer_append(strbuffer_t *strbuff, const char *string) +{ + return strbuffer_append_bytes(strbuff, string, strlen(string)); +} + +int strbuffer_append_byte(strbuffer_t *strbuff, char byte) +{ + return strbuffer_append_bytes(strbuff, &byte, 1); +} + +int strbuffer_append_bytes(strbuffer_t *strbuff, const char *data, size_t size) +{ + if(size >= strbuff->size - strbuff->length) + { + size_t new_size; + char *new_value; + + /* avoid integer overflow */ + if (strbuff->size > STRBUFFER_SIZE_MAX / STRBUFFER_FACTOR + || size > STRBUFFER_SIZE_MAX - 1 + || strbuff->length > STRBUFFER_SIZE_MAX - 1 - size) + return -1; + + new_size = max(strbuff->size * STRBUFFER_FACTOR, + strbuff->length + size + 1); + + new_value = jsonp_malloc(new_size); + if(!new_value) + return -1; + + memcpy(new_value, strbuff->value, strbuff->length); + + jsonp_free(strbuff->value); + strbuff->value = new_value; + strbuff->size = new_size; + } + + memcpy(strbuff->value + strbuff->length, data, size); + strbuff->length += size; + strbuff->value[strbuff->length] = '\0'; + + return 0; +} + +char strbuffer_pop(strbuffer_t *strbuff) +{ + if(strbuff->length > 0) { + char c = strbuff->value[--strbuff->length]; + strbuff->value[strbuff->length] = '\0'; + return c; + } + else + return '\0'; +} diff --git a/compat/jansson-2.6/src/strbuffer.h b/compat/jansson-2.6/src/strbuffer.h new file mode 100644 index 0000000..06fd065 --- /dev/null +++ b/compat/jansson-2.6/src/strbuffer.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2009-2013 Petri Lehtinen + * + * Jansson is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef STRBUFFER_H +#define STRBUFFER_H + +typedef struct { + char *value; + size_t length; /* bytes used */ + size_t size; /* bytes allocated */ +} strbuffer_t; + +int strbuffer_init(strbuffer_t *strbuff); +void strbuffer_close(strbuffer_t *strbuff); + +void strbuffer_clear(strbuffer_t *strbuff); + +const char *strbuffer_value(const strbuffer_t *strbuff); + +/* Steal the value and close the strbuffer */ +char *strbuffer_steal_value(strbuffer_t *strbuff); + +int strbuffer_append(strbuffer_t *strbuff, const char *string); +int strbuffer_append_byte(strbuffer_t *strbuff, char byte); +int strbuffer_append_bytes(strbuffer_t *strbuff, const char *data, size_t size); + +char strbuffer_pop(strbuffer_t *strbuff); + +#endif diff --git a/compat/jansson-2.6/src/strconv.c b/compat/jansson-2.6/src/strconv.c new file mode 100644 index 0000000..3e2cb7c --- /dev/null +++ b/compat/jansson-2.6/src/strconv.c @@ -0,0 +1,134 @@ +#include +#include +#include +#include +#include "jansson_private.h" +#include "strbuffer.h" + +/* need config.h to get the correct snprintf */ +#ifdef HAVE_CONFIG_H +#include +#endif + +#if JSON_HAVE_LOCALECONV +#include + +/* + - This code assumes that the decimal separator is exactly one + character. + + - If setlocale() is called by another thread between the call to + localeconv() and the call to sprintf() or strtod(), the result may + be wrong. setlocale() is not thread-safe and should not be used + this way. Multi-threaded programs should use uselocale() instead. +*/ + +static void to_locale(strbuffer_t *strbuffer) +{ + const char *point; + char *pos; + + point = localeconv()->decimal_point; + if(*point == '.') { + /* No conversion needed */ + return; + } + + pos = strchr(strbuffer->value, '.'); + if(pos) + *pos = *point; +} + +static void from_locale(char *buffer) +{ + const char *point; + char *pos; + + point = localeconv()->decimal_point; + if(*point == '.') { + /* No conversion needed */ + return; + } + + pos = strchr(buffer, *point); + if(pos) + *pos = '.'; +} +#endif + +int jsonp_strtod(strbuffer_t *strbuffer, double *out) +{ + double value; + char *end; + +#if JSON_HAVE_LOCALECONV + to_locale(strbuffer); +#endif + + errno = 0; + value = strtod(strbuffer->value, &end); + assert(end == strbuffer->value + strbuffer->length); + + if(errno == ERANGE && value != 0) { + /* Overflow */ + return -1; + } + + *out = value; + return 0; +} + +int jsonp_dtostr(char *buffer, size_t size, double value) +{ + int ret; + char *start, *end; + size_t length; + + ret = snprintf(buffer, size, "%.17g", value); + if(ret < 0) + return -1; + + length = (size_t)ret; + if(length >= size) + return -1; + +#if JSON_HAVE_LOCALECONV + from_locale(buffer); +#endif + + /* Make sure there's a dot or 'e' in the output. Otherwise + a real is converted to an integer when decoding */ + if(strchr(buffer, '.') == NULL && + strchr(buffer, 'e') == NULL) + { + if(length + 3 >= size) { + /* No space to append ".0" */ + return -1; + } + buffer[length] = '.'; + buffer[length + 1] = '0'; + buffer[length + 2] = '\0'; + length += 2; + } + + /* Remove leading '+' from positive exponent. Also remove leading + zeros from exponents (added by some printf() implementations) */ + start = strchr(buffer, 'e'); + if(start) { + start++; + end = start + 1; + + if(*start == '-') + start++; + + while(*end == '0') + end++; + + if(end != start) { + memmove(start, end, length - (size_t)(end - buffer)); + length -= (size_t)(end - start); + } + } + + return (int)length; +} diff --git a/compat/jansson-2.6/src/utf.c b/compat/jansson-2.6/src/utf.c new file mode 100644 index 0000000..65b849b --- /dev/null +++ b/compat/jansson-2.6/src/utf.c @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2009-2013 Petri Lehtinen + * + * Jansson is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#include +#include "utf.h" + +int utf8_encode(int32_t codepoint, char *buffer, int *size) +{ + if(codepoint < 0) + return -1; + else if(codepoint < 0x80) + { + buffer[0] = (char)codepoint; + *size = 1; + } + else if(codepoint < 0x800) + { + buffer[0] = 0xC0 + ((codepoint & 0x7C0) >> 6); + buffer[1] = 0x80 + ((codepoint & 0x03F)); + *size = 2; + } + else if(codepoint < 0x10000) + { + buffer[0] = 0xE0 + ((codepoint & 0xF000) >> 12); + buffer[1] = 0x80 + ((codepoint & 0x0FC0) >> 6); + buffer[2] = 0x80 + ((codepoint & 0x003F)); + *size = 3; + } + else if(codepoint <= 0x10FFFF) + { + buffer[0] = 0xF0 + ((codepoint & 0x1C0000) >> 18); + buffer[1] = 0x80 + ((codepoint & 0x03F000) >> 12); + buffer[2] = 0x80 + ((codepoint & 0x000FC0) >> 6); + buffer[3] = 0x80 + ((codepoint & 0x00003F)); + *size = 4; + } + else + return -1; + + return 0; +} + +int utf8_check_first(char byte) +{ + unsigned char u = (unsigned char)byte; + + if(u < 0x80) + return 1; + + if(0x80 <= u && u <= 0xBF) { + /* second, third or fourth byte of a multi-byte + sequence, i.e. a "continuation byte" */ + return 0; + } + else if(u == 0xC0 || u == 0xC1) { + /* overlong encoding of an ASCII byte */ + return 0; + } + else if(0xC2 <= u && u <= 0xDF) { + /* 2-byte sequence */ + return 2; + } + + else if(0xE0 <= u && u <= 0xEF) { + /* 3-byte sequence */ + return 3; + } + else if(0xF0 <= u && u <= 0xF4) { + /* 4-byte sequence */ + return 4; + } + else { /* u >= 0xF5 */ + /* Restricted (start of 4-, 5- or 6-byte sequence) or invalid + UTF-8 */ + return 0; + } +} + +int utf8_check_full(const char *buffer, int size, int32_t *codepoint) +{ + int i; + int32_t value = 0; + unsigned char u = (unsigned char)buffer[0]; + + if(size == 2) + { + value = u & 0x1F; + } + else if(size == 3) + { + value = u & 0xF; + } + else if(size == 4) + { + value = u & 0x7; + } + else + return 0; + + for(i = 1; i < size; i++) + { + u = (unsigned char)buffer[i]; + + if(u < 0x80 || u > 0xBF) { + /* not a continuation byte */ + return 0; + } + + value = (value << 6) + (u & 0x3F); + } + + if(value > 0x10FFFF) { + /* not in Unicode range */ + return 0; + } + + else if(0xD800 <= value && value <= 0xDFFF) { + /* invalid code point (UTF-16 surrogate halves) */ + return 0; + } + + else if((size == 2 && value < 0x80) || + (size == 3 && value < 0x800) || + (size == 4 && value < 0x10000)) { + /* overlong encoding */ + return 0; + } + + if(codepoint) + *codepoint = value; + + return 1; +} + +const char *utf8_iterate(const char *buffer, int32_t *codepoint) +{ + int count; + int32_t value; + + if(!*buffer) + return buffer; + + count = utf8_check_first(buffer[0]); + if(count <= 0) + return NULL; + + if(count == 1) + value = (unsigned char)buffer[0]; + else + { + if(!utf8_check_full(buffer, count, &value)) + return NULL; + } + + if(codepoint) + *codepoint = value; + + return buffer + count; +} + +int utf8_check_string(const char *string, int length) +{ + int i; + + if(length == -1) + length = strlen(string); + + for(i = 0; i < length; i++) + { + int count = utf8_check_first(string[i]); + if(count == 0) + return 0; + else if(count > 1) + { + if(i + count > length) + return 0; + + if(!utf8_check_full(&string[i], count, NULL)) + return 0; + + i += count - 1; + } + } + + return 1; +} diff --git a/compat/jansson-2.6/src/utf.h b/compat/jansson-2.6/src/utf.h new file mode 100644 index 0000000..cb10c24 --- /dev/null +++ b/compat/jansson-2.6/src/utf.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2009-2013 Petri Lehtinen + * + * Jansson is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef UTF_H +#define UTF_H + +#ifdef HAVE_CONFIG_H +#include + +#ifdef HAVE_INTTYPES_H +/* inttypes.h includes stdint.h in a standard environment, so there's +no need to include stdint.h separately. If inttypes.h doesn't define +int32_t, it's defined in config.h. */ +#include +#endif /* HAVE_INTTYPES_H */ + +#else /* !HAVE_CONFIG_H */ +#ifdef _WIN32 +typedef int int32_t; +#else /* !_WIN32 */ +/* Assume a standard environment */ +#include +#endif /* _WIN32 */ + +#endif /* HAVE_CONFIG_H */ + +int utf8_encode(int codepoint, char *buffer, int *size); + +int utf8_check_first(char byte); +int utf8_check_full(const char *buffer, int size, int32_t *codepoint); +const char *utf8_iterate(const char *buffer, int32_t *codepoint); + +int utf8_check_string(const char *string, int length); + +#endif diff --git a/compat/jansson-2.6/src/value.c b/compat/jansson-2.6/src/value.c new file mode 100644 index 0000000..582849b --- /dev/null +++ b/compat/jansson-2.6/src/value.c @@ -0,0 +1,950 @@ +/* + * Copyright (c) 2009-2013 Petri Lehtinen + * + * Jansson is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include + +#include "jansson.h" +#include "hashtable.h" +#include "jansson_private.h" +#include "utf.h" + +/* Work around nonstandard isnan() and isinf() implementations */ +#ifndef isnan +static JSON_INLINE int isnan(double x) { return x != x; } +#endif +#ifndef isinf +static JSON_INLINE int isinf(double x) { return !isnan(x) && isnan(x - x); } +#endif + +static JSON_INLINE void json_init(json_t *json, json_type type) +{ + json->type = type; + json->refcount = 1; +} + + +/*** object ***/ + +json_t *json_object(void) +{ + json_object_t *object = jsonp_malloc(sizeof(json_object_t)); + if(!object) + return NULL; + json_init(&object->json, JSON_OBJECT); + + if(hashtable_init(&object->hashtable)) + { + jsonp_free(object); + return NULL; + } + + object->serial = 0; + object->visited = 0; + + return &object->json; +} + +static void json_delete_object(json_object_t *object) +{ + hashtable_close(&object->hashtable); + jsonp_free(object); +} + +size_t json_object_size(const json_t *json) +{ + json_object_t *object; + + if(!json_is_object(json)) + return 0; + + object = json_to_object(json); + return object->hashtable.size; +} + +json_t *json_object_get(const json_t *json, const char *key) +{ + json_object_t *object; + + if(!json_is_object(json)) + return NULL; + + object = json_to_object(json); + return hashtable_get(&object->hashtable, key); +} + +int json_object_set_new_nocheck(json_t *json, const char *key, json_t *value) +{ + json_object_t *object; + + if(!value) + return -1; + + if(!key || !json_is_object(json) || json == value) + { + json_decref(value); + return -1; + } + object = json_to_object(json); + + if(hashtable_set(&object->hashtable, key, object->serial++, value)) + { + json_decref(value); + return -1; + } + + return 0; +} + +int json_object_set_new(json_t *json, const char *key, json_t *value) +{ + if(!key || !utf8_check_string(key, -1)) + { + json_decref(value); + return -1; + } + + return json_object_set_new_nocheck(json, key, value); +} + +int json_object_del(json_t *json, const char *key) +{ + json_object_t *object; + + if(!json_is_object(json)) + return -1; + + object = json_to_object(json); + return hashtable_del(&object->hashtable, key); +} + +int json_object_clear(json_t *json) +{ + json_object_t *object; + + if(!json_is_object(json)) + return -1; + + object = json_to_object(json); + + hashtable_clear(&object->hashtable); + object->serial = 0; + + return 0; +} + +int json_object_update(json_t *object, json_t *other) +{ + const char *key; + json_t *value; + + if(!json_is_object(object) || !json_is_object(other)) + return -1; + + json_object_foreach(other, key, value) { + if(json_object_set_nocheck(object, key, value)) + return -1; + } + + return 0; +} + +int json_object_update_existing(json_t *object, json_t *other) +{ + const char *key; + json_t *value; + + if(!json_is_object(object) || !json_is_object(other)) + return -1; + + json_object_foreach(other, key, value) { + if(json_object_get(object, key)) + json_object_set_nocheck(object, key, value); + } + + return 0; +} + +int json_object_update_missing(json_t *object, json_t *other) +{ + const char *key; + json_t *value; + + if(!json_is_object(object) || !json_is_object(other)) + return -1; + + json_object_foreach(other, key, value) { + if(!json_object_get(object, key)) + json_object_set_nocheck(object, key, value); + } + + return 0; +} + +void *json_object_iter(json_t *json) +{ + json_object_t *object; + + if(!json_is_object(json)) + return NULL; + + object = json_to_object(json); + return hashtable_iter(&object->hashtable); +} + +void *json_object_iter_at(json_t *json, const char *key) +{ + json_object_t *object; + + if(!key || !json_is_object(json)) + return NULL; + + object = json_to_object(json); + return hashtable_iter_at(&object->hashtable, key); +} + +void *json_object_iter_next(json_t *json, void *iter) +{ + json_object_t *object; + + if(!json_is_object(json) || iter == NULL) + return NULL; + + object = json_to_object(json); + return hashtable_iter_next(&object->hashtable, iter); +} + +const char *json_object_iter_key(void *iter) +{ + if(!iter) + return NULL; + + return hashtable_iter_key(iter); +} + +json_t *json_object_iter_value(void *iter) +{ + if(!iter) + return NULL; + + return (json_t *)hashtable_iter_value(iter); +} + +int json_object_iter_set_new(json_t *json, void *iter, json_t *value) +{ + if(!json_is_object(json) || !iter || !value) + return -1; + + hashtable_iter_set(iter, value); + return 0; +} + +void *json_object_key_to_iter(const char *key) +{ + if(!key) + return NULL; + + return hashtable_key_to_iter(key); +} + +static int json_object_equal(json_t *object1, json_t *object2) +{ + const char *key; + json_t *value1, *value2; + + if(json_object_size(object1) != json_object_size(object2)) + return 0; + + json_object_foreach(object1, key, value1) { + value2 = json_object_get(object2, key); + + if(!json_equal(value1, value2)) + return 0; + } + + return 1; +} + +static json_t *json_object_copy(json_t *object) +{ + json_t *result; + + const char *key; + json_t *value; + + result = json_object(); + if(!result) + return NULL; + + json_object_foreach(object, key, value) + json_object_set_nocheck(result, key, value); + + return result; +} + +static json_t *json_object_deep_copy(const json_t *object) +{ + json_t *result; + void *iter; + + result = json_object(); + if(!result) + return NULL; + + /* Cannot use json_object_foreach because object has to be cast + non-const */ + iter = json_object_iter((json_t *)object); + while(iter) { + const char *key; + const json_t *value; + key = json_object_iter_key(iter); + value = json_object_iter_value(iter); + + json_object_set_new_nocheck(result, key, json_deep_copy(value)); + iter = json_object_iter_next((json_t *)object, iter); + } + + return result; +} + + +/*** array ***/ + +json_t *json_array(void) +{ + json_array_t *array = jsonp_malloc(sizeof(json_array_t)); + if(!array) + return NULL; + json_init(&array->json, JSON_ARRAY); + + array->entries = 0; + array->size = 8; + + array->table = jsonp_malloc(array->size * sizeof(json_t *)); + if(!array->table) { + jsonp_free(array); + return NULL; + } + + array->visited = 0; + + return &array->json; +} + +static void json_delete_array(json_array_t *array) +{ + size_t i; + + for(i = 0; i < array->entries; i++) + json_decref(array->table[i]); + + jsonp_free(array->table); + jsonp_free(array); +} + +size_t json_array_size(const json_t *json) +{ + if(!json_is_array(json)) + return 0; + + return json_to_array(json)->entries; +} + +json_t *json_array_get(const json_t *json, size_t index) +{ + json_array_t *array; + if(!json_is_array(json)) + return NULL; + array = json_to_array(json); + + if(index >= array->entries) + return NULL; + + return array->table[index]; +} + +int json_array_set_new(json_t *json, size_t index, json_t *value) +{ + json_array_t *array; + + if(!value) + return -1; + + if(!json_is_array(json) || json == value) + { + json_decref(value); + return -1; + } + array = json_to_array(json); + + if(index >= array->entries) + { + json_decref(value); + return -1; + } + + json_decref(array->table[index]); + array->table[index] = value; + + return 0; +} + +static void array_move(json_array_t *array, size_t dest, + size_t src, size_t count) +{ + memmove(&array->table[dest], &array->table[src], count * sizeof(json_t *)); +} + +static void array_copy(json_t **dest, size_t dpos, + json_t **src, size_t spos, + size_t count) +{ + memcpy(&dest[dpos], &src[spos], count * sizeof(json_t *)); +} + +static json_t **json_array_grow(json_array_t *array, + size_t amount, + int copy) +{ + size_t new_size; + json_t **old_table, **new_table; + + if(array->entries + amount <= array->size) + return array->table; + + old_table = array->table; + + new_size = max(array->size + amount, array->size * 2); + new_table = jsonp_malloc(new_size * sizeof(json_t *)); + if(!new_table) + return NULL; + + array->size = new_size; + array->table = new_table; + + if(copy) { + array_copy(array->table, 0, old_table, 0, array->entries); + jsonp_free(old_table); + return array->table; + } + + return old_table; +} + +int json_array_append_new(json_t *json, json_t *value) +{ + json_array_t *array; + + if(!value) + return -1; + + if(!json_is_array(json) || json == value) + { + json_decref(value); + return -1; + } + array = json_to_array(json); + + if(!json_array_grow(array, 1, 1)) { + json_decref(value); + return -1; + } + + array->table[array->entries] = value; + array->entries++; + + return 0; +} + +int json_array_insert_new(json_t *json, size_t index, json_t *value) +{ + json_array_t *array; + json_t **old_table; + + if(!value) + return -1; + + if(!json_is_array(json) || json == value) { + json_decref(value); + return -1; + } + array = json_to_array(json); + + if(index > array->entries) { + json_decref(value); + return -1; + } + + old_table = json_array_grow(array, 1, 0); + if(!old_table) { + json_decref(value); + return -1; + } + + if(old_table != array->table) { + array_copy(array->table, 0, old_table, 0, index); + array_copy(array->table, index + 1, old_table, index, + array->entries - index); + jsonp_free(old_table); + } + else + array_move(array, index + 1, index, array->entries - index); + + array->table[index] = value; + array->entries++; + + return 0; +} + +int json_array_remove(json_t *json, size_t index) +{ + json_array_t *array; + + if(!json_is_array(json)) + return -1; + array = json_to_array(json); + + if(index >= array->entries) + return -1; + + json_decref(array->table[index]); + + /* If we're removing the last element, nothing has to be moved */ + if(index < array->entries - 1) + array_move(array, index, index + 1, array->entries - index - 1); + + array->entries--; + + return 0; +} + +int json_array_clear(json_t *json) +{ + json_array_t *array; + size_t i; + + if(!json_is_array(json)) + return -1; + array = json_to_array(json); + + for(i = 0; i < array->entries; i++) + json_decref(array->table[i]); + + array->entries = 0; + return 0; +} + +int json_array_extend(json_t *json, json_t *other_json) +{ + json_array_t *array, *other; + size_t i; + + if(!json_is_array(json) || !json_is_array(other_json)) + return -1; + array = json_to_array(json); + other = json_to_array(other_json); + + if(!json_array_grow(array, other->entries, 1)) + return -1; + + for(i = 0; i < other->entries; i++) + json_incref(other->table[i]); + + array_copy(array->table, array->entries, other->table, 0, other->entries); + + array->entries += other->entries; + return 0; +} + +static int json_array_equal(json_t *array1, json_t *array2) +{ + size_t i, size; + + size = json_array_size(array1); + if(size != json_array_size(array2)) + return 0; + + for(i = 0; i < size; i++) + { + json_t *value1, *value2; + + value1 = json_array_get(array1, i); + value2 = json_array_get(array2, i); + + if(!json_equal(value1, value2)) + return 0; + } + + return 1; +} + +static json_t *json_array_copy(json_t *array) +{ + json_t *result; + size_t i; + + result = json_array(); + if(!result) + return NULL; + + for(i = 0; i < json_array_size(array); i++) + json_array_append(result, json_array_get(array, i)); + + return result; +} + +static json_t *json_array_deep_copy(const json_t *array) +{ + json_t *result; + size_t i; + + result = json_array(); + if(!result) + return NULL; + + for(i = 0; i < json_array_size(array); i++) + json_array_append_new(result, json_deep_copy(json_array_get(array, i))); + + return result; +} + +/*** string ***/ + +json_t *json_string_nocheck(const char *value) +{ + json_string_t *string; + + if(!value) + return NULL; + + string = jsonp_malloc(sizeof(json_string_t)); + if(!string) + return NULL; + json_init(&string->json, JSON_STRING); + + string->value = jsonp_strdup(value); + if(!string->value) { + jsonp_free(string); + return NULL; + } + + return &string->json; +} + +json_t *json_string(const char *value) +{ + if(!value || !utf8_check_string(value, -1)) + return NULL; + + return json_string_nocheck(value); +} + +const char *json_string_value(const json_t *json) +{ + if(!json_is_string(json)) + return NULL; + + return json_to_string(json)->value; +} + +int json_string_set_nocheck(json_t *json, const char *value) +{ + char *dup; + json_string_t *string; + + if(!json_is_string(json) || !value) + return -1; + + dup = jsonp_strdup(value); + if(!dup) + return -1; + + string = json_to_string(json); + jsonp_free(string->value); + string->value = dup; + + return 0; +} + +int json_string_set(json_t *json, const char *value) +{ + if(!value || !utf8_check_string(value, -1)) + return -1; + + return json_string_set_nocheck(json, value); +} + +static void json_delete_string(json_string_t *string) +{ + jsonp_free(string->value); + jsonp_free(string); +} + +static int json_string_equal(json_t *string1, json_t *string2) +{ + return strcmp(json_string_value(string1), json_string_value(string2)) == 0; +} + +static json_t *json_string_copy(const json_t *string) +{ + return json_string_nocheck(json_string_value(string)); +} + + +/*** integer ***/ + +json_t *json_integer(json_int_t value) +{ + json_integer_t *integer = jsonp_malloc(sizeof(json_integer_t)); + if(!integer) + return NULL; + json_init(&integer->json, JSON_INTEGER); + + integer->value = value; + return &integer->json; +} + +json_int_t json_integer_value(const json_t *json) +{ + if(!json_is_integer(json)) + return 0; + + return json_to_integer(json)->value; +} + +int json_integer_set(json_t *json, json_int_t value) +{ + if(!json_is_integer(json)) + return -1; + + json_to_integer(json)->value = value; + + return 0; +} + +static void json_delete_integer(json_integer_t *integer) +{ + jsonp_free(integer); +} + +static int json_integer_equal(json_t *integer1, json_t *integer2) +{ + return json_integer_value(integer1) == json_integer_value(integer2); +} + +static json_t *json_integer_copy(const json_t *integer) +{ + return json_integer(json_integer_value(integer)); +} + + +/*** real ***/ + +json_t *json_real(double value) +{ + json_real_t *real; + + if(isnan(value) || isinf(value)) + return NULL; + + real = jsonp_malloc(sizeof(json_real_t)); + if(!real) + return NULL; + json_init(&real->json, JSON_REAL); + + real->value = value; + return &real->json; +} + +double json_real_value(const json_t *json) +{ + if(!json_is_real(json)) + return 0; + + return json_to_real(json)->value; +} + +int json_real_set(json_t *json, double value) +{ + if(!json_is_real(json) || isnan(value) || isinf(value)) + return -1; + + json_to_real(json)->value = value; + + return 0; +} + +static void json_delete_real(json_real_t *real) +{ + jsonp_free(real); +} + +static int json_real_equal(json_t *real1, json_t *real2) +{ + return json_real_value(real1) == json_real_value(real2); +} + +static json_t *json_real_copy(const json_t *real) +{ + return json_real(json_real_value(real)); +} + + +/*** number ***/ + +double json_number_value(const json_t *json) +{ + if(json_is_integer(json)) + return (double)json_integer_value(json); + else if(json_is_real(json)) + return json_real_value(json); + else + return 0.0; +} + + +/*** simple values ***/ + +json_t *json_true(void) +{ + static json_t the_true = {JSON_TRUE, (size_t)-1}; + return &the_true; +} + + +json_t *json_false(void) +{ + static json_t the_false = {JSON_FALSE, (size_t)-1}; + return &the_false; +} + + +json_t *json_null(void) +{ + static json_t the_null = {JSON_NULL, (size_t)-1}; + return &the_null; +} + + +/*** deletion ***/ + +void json_delete(json_t *json) +{ + if(json_is_object(json)) + json_delete_object(json_to_object(json)); + + else if(json_is_array(json)) + json_delete_array(json_to_array(json)); + + else if(json_is_string(json)) + json_delete_string(json_to_string(json)); + + else if(json_is_integer(json)) + json_delete_integer(json_to_integer(json)); + + else if(json_is_real(json)) + json_delete_real(json_to_real(json)); + + /* json_delete is not called for true, false or null */ +} + + +/*** equality ***/ + +int json_equal(json_t *json1, json_t *json2) +{ + if(!json1 || !json2) + return 0; + + if(json_typeof(json1) != json_typeof(json2)) + return 0; + + /* this covers true, false and null as they are singletons */ + if(json1 == json2) + return 1; + + if(json_is_object(json1)) + return json_object_equal(json1, json2); + + if(json_is_array(json1)) + return json_array_equal(json1, json2); + + if(json_is_string(json1)) + return json_string_equal(json1, json2); + + if(json_is_integer(json1)) + return json_integer_equal(json1, json2); + + if(json_is_real(json1)) + return json_real_equal(json1, json2); + + return 0; +} + + +/*** copying ***/ + +json_t *json_copy(json_t *json) +{ + if(!json) + return NULL; + + if(json_is_object(json)) + return json_object_copy(json); + + if(json_is_array(json)) + return json_array_copy(json); + + if(json_is_string(json)) + return json_string_copy(json); + + if(json_is_integer(json)) + return json_integer_copy(json); + + if(json_is_real(json)) + return json_real_copy(json); + + if(json_is_true(json) || json_is_false(json) || json_is_null(json)) + return json; + + return NULL; +} + +json_t *json_deep_copy(const json_t *json) +{ + if(!json) + return NULL; + + if(json_is_object(json)) + return json_object_deep_copy(json); + + if(json_is_array(json)) + return json_array_deep_copy(json); + + /* for the rest of the types, deep copying doesn't differ from + shallow copying */ + + if(json_is_string(json)) + return json_string_copy(json); + + if(json_is_integer(json)) + return json_integer_copy(json); + + if(json_is_real(json)) + return json_real_copy(json); + + if(json_is_true(json) || json_is_false(json) || json_is_null(json)) + return (json_t *)json; + + return NULL; +} diff --git a/compat/libusb-1.0/AUTHORS b/compat/libusb-1.0/AUTHORS new file mode 100644 index 0000000..32d6948 --- /dev/null +++ b/compat/libusb-1.0/AUTHORS @@ -0,0 +1,46 @@ +Copyright (C) 2007-2009 Daniel Drake +Copyright (c) 2001 Johannes Erdfelt +Copyright (C) 2008-2013 Nathan Hjelm +Copyright (C) 2009-2012 Pete Batard +Copyright (C) 2010 Michael Plante +Copyright (C) 2010-2012 Peter Stuge +Copyright (C) 2011-2012 Hans de Goede +Copyright (C) 2012 Martin Pieuchot + +Other contributors: +Alan Ott +Alan Stern +Alex Vatchenko +Artem Egorkine +Aurelien Jarno +Bastien Nocera +Brian Shirley +David Engraf +David Moore +Felipe Balbi +Graeme Gill +Hans de Goede +Hans Ulrich Niedermann +Hector Martin +Hoi-Ho Chan +James Hanko +Konrad Rzepecki +Ludovic Rousseau +Martin Koegler +Martin Pieuchot +Maya Erez +Mike Frysinger +Mikhail Gusarov +Orin Eman +Pekka Nikander +Peter Stuge +Rob Walker +Sean McBride +Sebastian Pipping +Stephan Meyer +Thomas Röfer +Toby Peterson +Trygve Laugstøl +Vasily Khoruzhick +Vitali Lovich +Xiaofan Chen diff --git a/compat/libusb-1.0/COPYING b/compat/libusb-1.0/COPYING new file mode 100644 index 0000000..5ab7695 --- /dev/null +++ b/compat/libusb-1.0/COPYING @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/compat/libusb-1.0/Makefile.am b/compat/libusb-1.0/Makefile.am new file mode 100644 index 0000000..3f1e7d2 --- /dev/null +++ b/compat/libusb-1.0/Makefile.am @@ -0,0 +1,24 @@ +AUTOMAKE_OPTIONS = dist-bzip2 no-dist-gzip +ACLOCAL_AMFLAGS = -I m4 +DISTCLEANFILES = libusb-1.0.pc +MAINTAINERCLEANFILES = ChangeLog +EXTRA_DIST = TODO PORTING +SUBDIRS = libusb + +pkgconfigdir=$(libdir)/pkgconfig +pkgconfig_DATA=libusb-1.0.pc + +.PHONY: ChangeLog dist-up +ChangeLog: + git --git-dir $(top_srcdir)/.git log > ChangeLog || touch ChangeLog + +dist-hook: ChangeLog + +reldir = .release/$(distdir) +dist-up: dist + rm -rf $(reldir) + mkdir -p $(reldir) + cp $(distdir).tar.bz2 $(reldir) + rsync -rv $(reldir) frs.sourceforge.net:/home/frs/project/l/li/libusb/libusb-1.0/ + rm -rf $(reldir) + diff --git a/compat/libusb-1.0/NEWS b/compat/libusb-1.0/NEWS new file mode 100644 index 0000000..f948700 --- /dev/null +++ b/compat/libusb-1.0/NEWS @@ -0,0 +1,65 @@ +This file lists notable changes in each release. For the full history of all +changes, see ChangeLog. + +2012-04-20: 1.0.9 +* Numerous bug fixes and improvements +* Backend for Windows, for devices using the WinUSB.sys driver +* Backend for OpenBSD and NetBSD, for devices using the ugen driver +* Add libusb_get_device_speed() +* Add libusb_has_capability() +* Add libusb_error_name() +* Add libusb_get_version() + +2010-05-07: v1.0.8 +* Bug fixes + +2010-04-19: v1.0.7 +* Bug fixes and documentation tweaks +* Add more interface class definitions + +2009-11-22: v1.0.6 +* Bug fixes +* Increase libusb_handle_events() timeout to 60s for powersaving + +2009-11-15: v1.0.5 + * Use timerfd when available for timer management + * Small fixes/updates + +2009-11-06: v1.0.4 release + * Bug fixes including transfer locking to fix some potential threading races + * More flexibility with clock types on Linux + * Use new bulk continuation tracking in Linux 2.6.32 for improved handling + of short/failed transfers + +2009-08-27: v1.0.3 release + * Bug fixes + * Add libusb_get_max_iso_packet_size() + +2009-06-13: v1.0.2 release + * Bug fixes + +2009-05-12: v1.0.1 release + * Bug fixes + * Darwin backend + +2008-12-13: v1.0.0 release + * Bug fixes + +2008-11-21: v0.9.4 release + * Bug fixes + * Add libusb_attach_kernel_driver() + +2008-08-23: v0.9.3 release + * Bug fixes + +2008-07-19: v0.9.2 release + * Bug fixes + +2008-06-28: v0.9.1 release + * Bug fixes + * Introduce contexts to the API + * Compatibility with new Linux kernel features + +2008-05-25: v0.9.0 release + * First libusb-1.0 beta release + diff --git a/compat/libusb-1.0/PORTING b/compat/libusb-1.0/PORTING new file mode 100644 index 0000000..7070784 --- /dev/null +++ b/compat/libusb-1.0/PORTING @@ -0,0 +1,95 @@ +PORTING LIBUSB TO OTHER PLATFORMS + +Introduction +============ + +This document is aimed at developers wishing to port libusb to unsupported +platforms. I believe the libusb API is OS-independent, so by supporting +multiple operating systems we pave the way for cross-platform USB device +drivers. + +Implementation-wise, the basic idea is that you provide an interface to +libusb's internal "backend" API, which performs the appropriate operations on +your target platform. + +In terms of USB I/O, your backend provides functionality to submit +asynchronous transfers (synchronous transfers are implemented in the higher +layers, based on the async interface). Your backend must also provide +functionality to cancel those transfers. + +Your backend must also provide an event handling function to "reap" ongoing +transfers and process their results. + +The backend must also provide standard functions for other USB operations, +e.g. setting configuration, obtaining descriptors, etc. + + +File descriptors for I/O polling +================================ + +For libusb to work, your event handling function obviously needs to be called +at various points in time. Your backend must provide a set of file descriptors +which libusb and its users can pass to poll() or select() to determine when +it is time to call the event handling function. + +On Linux, this is easy: the usbfs kernel interface exposes a file descriptor +which can be passed to poll(). If something similar is not true for your +platform, you can emulate this using an internal library thread to reap I/O as +necessary, and a pipe() with the main library to raise events. The file +descriptor of the pipe can then be provided to libusb as an event source. + + +Interface semantics and documentation +===================================== + +Documentation of the backend interface can be found in libusbi.h inside the +usbi_os_backend structure definition. + +Your implementations of these functions will need to call various internal +libusb functions, prefixed with "usbi_". Documentation for these functions +can be found in the .c files where they are implemented. + +You probably want to skim over *all* the documentation before starting your +implementation. For example, you probably need to allocate and store private +OS-specific data for device handles, but the documentation for the mechanism +for doing so is probably not the first thing you will see. + +The Linux backend acts as a good example - view it as a reference +implementation which you should try to match the behaviour of. + + +Getting started +=============== + +1. Modify configure.ac to detect your platform appropriately (see the OS_LINUX +stuff for an example). + +2. Implement your backend in the libusb/os/ directory, modifying +libusb/os/Makefile.am appropriately. + +3. Add preprocessor logic to the top of libusb/core.c to statically assign the +right usbi_backend for your platform. + +4. Produce and test your implementation. + +5. Send your implementation to libusb-devel mailing list. + + +Implementation difficulties? Questions? +======================================= + +If you encounter difficulties porting libusb to your platform, please raise +these issues on the libusb-devel mailing list. Where possible and sensible, I +am interested in solving problems preventing libusb from operating on other +platforms. + +The libusb-devel mailing list is also a good place to ask questions and +make suggestions about the internal API. Hopefully we can produce some +better documentation based on your questions and other input. + +You are encouraged to get involved in the process; if the library needs +some infrastructure additions/modifications to better support your platform, +you are encouraged to make such changes (in cleanly distinct patch +submissions). Even if you do not make such changes yourself, please do raise +the issues on the mailing list at the very minimum. + diff --git a/compat/libusb-1.0/README b/compat/libusb-1.0/README new file mode 100644 index 0000000..08ae169 --- /dev/null +++ b/compat/libusb-1.0/README @@ -0,0 +1,22 @@ +libusb +====== + +libusb is a library for USB device access from Linux, Mac OS X, +OpenBSD, NetBSD, and Windows userspace. +It is written in C and licensed under the LGPL-2.1 (see COPYING). + +libusb is abstracted internally in such a way that it can hopefully +be ported to other operating systems. See the PORTING file for some +information, if you fancy a challenge. :) + +libusb homepage: +http://libusb.org/ + +Developers will wish to consult the API documentation: +http://libusb.sourceforge.net/api-1.0/ + +Use the mailing list for questions, comments, etc: +http://libusb.org/wiki/MailingList + +- Peter Stuge +(use the mailing list rather than mailing developers directly) diff --git a/compat/libusb-1.0/THANKS b/compat/libusb-1.0/THANKS new file mode 100644 index 0000000..d926126 --- /dev/null +++ b/compat/libusb-1.0/THANKS @@ -0,0 +1,8 @@ +Development contributors are listed in the AUTHORS file. Other community +members who have made significant contributions in other areas are listed +in this file: + +Alan Stern +Ludovic Rousseau +Tim Roberts +Xiaofan Chen diff --git a/compat/libusb-1.0/TODO b/compat/libusb-1.0/TODO new file mode 100644 index 0000000..6c162a3 --- /dev/null +++ b/compat/libusb-1.0/TODO @@ -0,0 +1,9 @@ +for 1.1 or future +================== +optional timerfd support (runtime detection) +notifications of hotplugged/unplugged devices +offer API to create/destroy handle_events thread +isochronous sync I/O? +exposing of parent-child device relationships +"usb primer" introduction docs +more examples diff --git a/compat/libusb-1.0/configure.ac b/compat/libusb-1.0/configure.ac new file mode 100644 index 0000000..1fccea7 --- /dev/null +++ b/compat/libusb-1.0/configure.ac @@ -0,0 +1,229 @@ +dnl These m4 macros are whitespace sensitive and break if moved around much. +m4_define([LU_VERSION_H], m4_include([libusb/version.h])) +m4_define([LU_DEFINE_VERSION_ATOM], + [m4_define([$1], m4_bregexp(LU_VERSION_H, + [^#define\s*$1\s*\([0-9]*\).*], [\1]))]) +m4_define([LU_DEFINE_VERSION_RC_ATOM], + [m4_define([$1], m4_bregexp(LU_VERSION_H, + [^#define\s*$1\s*"\(-rc[0-9]*\)".*], [\1]))]) +dnl The m4_bregexp() returns (only) the numbers following the #define named +dnl in the first macro parameter. m4_define() then defines the name for use +dnl in AC_INIT(). + +LU_DEFINE_VERSION_ATOM([LIBUSB_MAJOR]) +LU_DEFINE_VERSION_ATOM([LIBUSB_MINOR]) +LU_DEFINE_VERSION_ATOM([LIBUSB_MICRO]) +LU_DEFINE_VERSION_RC_ATOM([LIBUSB_RC]) + +AC_INIT([libusb], LIBUSB_MAJOR[.]LIBUSB_MINOR[.]LIBUSB_MICRO[]LIBUSB_RC, [libusb-devel@lists.sourceforge.net], [libusb], [http://www.libusb.org/]) + +# Library versioning +# These numbers should be tweaked on every release. Read carefully: +# http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html +# http://sourceware.org/autobook/autobook/autobook_91.html +lt_current="2" +lt_revision="0" +lt_age="0" +LTLDFLAGS="-version-info ${lt_current}:${lt_revision}:${lt_age}" + +AM_INIT_AUTOMAKE([foreign subdir-objects]) +AM_MAINTAINER_MODE + +AC_CONFIG_SRCDIR([libusb/core.c]) +AC_CONFIG_MACRO_DIR([m4]) +AC_CONFIG_HEADERS([config.h]) +m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])]) + +AC_PREREQ([2.50]) +AC_PROG_CC +AC_PROG_LIBTOOL +LT_LANG([Windows Resource]) +AC_C_INLINE +AM_PROG_CC_C_O +AC_DEFINE([_GNU_SOURCE], 1, [Use GNU extensions]) + +LTLDFLAGS="${LTLDFLAGS} -no-undefined" + +AC_MSG_CHECKING([operating system]) +case $host in +*-linux*) + AC_MSG_RESULT([Linux]) + backend="linux" + ;; +*-darwin*) + AC_MSG_RESULT([Darwin/Mac OS X]) + backend="darwin" + ;; +*-openbsd*) + AC_MSG_RESULT([OpenBSD]) + backend="openbsd" + ;; +*-netbsd*) + AC_MSG_RESULT([NetBSD (using OpenBSD backend)]) + backend="openbsd" + ;; +*-mingw*) + AC_MSG_RESULT([Windows]) + backend="windows" + ;; +*-cygwin*) + AC_MSG_RESULT([Cygwin (using Windows backend)]) + backend="windows" + threads="posix" + ;; +*) + AC_MSG_ERROR([unsupported operating system]) +esac +case $backend in +linux) + AC_DEFINE(OS_LINUX, 1, [Linux backend]) + AC_SUBST(OS_LINUX) + AC_CHECK_LIB(rt, clock_gettime, -pthread) + AC_ARG_ENABLE([udev], + [AC_HELP_STRING([--with-udev], [use udev for device enumeration and hotplug support (recommended, default: yes)])], + [], [enable_udev="yes"]) + if test "x$enable_udev" = "xyes" ; then + # system has udev. use it or fail! + AC_CHECK_HEADERS([libudev.h],[],[AC_ERROR(["udev support requested but libudev not installed"])]) + AC_CHECK_LIB([udev], [udev_new], [], [AC_ERROR(["udev support requested but libudev not installed"])]) + AC_DEFINE(USE_UDEV, 1, [Use udev for device enumeration/hotplug]) + else + AC_CHECK_HEADERS([linux/netlink.h linux/filter.h], [], [AC_ERROR(["Linux netlink headers not found"])]) + fi + AC_SUBST(USE_UDEV) + threads="posix" + THREAD_CFLAGS="-pthread" + LIBS="${LIBS} -pthread" + AC_CHECK_HEADERS([poll.h]) + AC_DEFINE([POLL_NFDS_TYPE],[nfds_t],[type of second poll() argument]) + ;; +darwin) + AC_DEFINE(OS_DARWIN, 1, [Darwin backend]) + AC_SUBST(OS_DARWIN) + threads="posix" + LIBS="-lobjc -Wl,-framework,IOKit -Wl,-framework,CoreFoundation" + LTLDFLAGS="${LTLDFLAGS} -Wl,-prebind" + AC_CHECK_HEADERS([poll.h]) + AC_CHECK_TYPE([nfds_t], + [AC_DEFINE([POLL_NFDS_TYPE],[nfds_t],[type of second poll() argument])], + [AC_DEFINE([POLL_NFDS_TYPE],[unsigned int],[type of second poll() argument])], + [#include ]) + ;; +openbsd) + AC_DEFINE(OS_OPENBSD, 1, [OpenBSD backend]) + AC_SUBST(OS_OPENBSD) + threads="posix" + THREAD_CFLAGS="-pthread" + LIBS="-pthread" + AC_CHECK_HEADERS([poll.h]) + AC_DEFINE([POLL_NFDS_TYPE],[nfds_t],[type of second poll() argument]) + ;; +windows) + AC_DEFINE(OS_WINDOWS, 1, [Windows backend]) + AC_SUBST(OS_WINDOWS) + LIBS="" + LTLDFLAGS="${LTLDFLAGS} -avoid-version -Wl,--add-stdcall-alias" + AC_DEFINE([POLL_NFDS_TYPE],[unsigned int],[type of second poll() argument]) + ;; +esac +AC_SUBST(LIBS) + +AM_CONDITIONAL(OS_LINUX, test "x$backend" = xlinux) +AM_CONDITIONAL(OS_DARWIN, test "x$backend" = xdarwin) +AM_CONDITIONAL(OS_OPENBSD, test "x$backend" = xopenbsd) +AM_CONDITIONAL(OS_WINDOWS, test "x$backend" = xwindows) +AM_CONDITIONAL(THREADS_POSIX, test "x$threads" = xposix) +AM_CONDITIONAL(USE_UDEV, test "x$enable_udev" = xyes) +if test "$threads" = posix; then + AC_DEFINE(THREADS_POSIX, 1, [Use POSIX Threads]) +fi + +# timerfd +AC_CHECK_HEADER([sys/timerfd.h], [timerfd_h=1], [timerfd_h=0]) +AC_ARG_ENABLE([timerfd], + [AS_HELP_STRING([--enable-timerfd], + [use timerfd for timing (default auto)])], + [use_timerfd=$enableval], [use_timerfd='auto']) + +if test "x$use_timerfd" = "xyes" -a "x$timerfd_h" = "x0"; then + AC_MSG_ERROR([timerfd header not available; glibc 2.9+ required]) +fi + +AC_CHECK_DECL([TFD_NONBLOCK], [tfd_hdr_ok=yes], [tfd_hdr_ok=no], [#include ]) +if test "x$use_timerfd" = "xyes" -a "x$tfd_hdr_ok" = "xno"; then + AC_MSG_ERROR([timerfd header not usable; glibc 2.9+ required]) +fi + +AC_MSG_CHECKING([whether to use timerfd for timing]) +if test "x$use_timerfd" = "xno"; then + AC_MSG_RESULT([no (disabled by user)]) +else + if test "x$timerfd_h" = "x1" -a "x$tfd_hdr_ok" = "xyes"; then + AC_MSG_RESULT([yes]) + AC_DEFINE(USBI_TIMERFD_AVAILABLE, 1, [timerfd headers available]) + else + AC_MSG_RESULT([no (header not available)]) + fi +fi + +AC_CHECK_TYPES(struct timespec) + +# Message logging +AC_ARG_ENABLE([log], [AS_HELP_STRING([--disable-log], [disable all logging])], + [log_enabled=$enableval], + [log_enabled='yes']) +if test "x$log_enabled" != "xno"; then + AC_DEFINE([ENABLE_LOGGING], 1, [Message logging]) +fi + +AC_ARG_ENABLE([debug-log], [AS_HELP_STRING([--enable-debug-log], + [enable debug logging (default n)])], + [debug_log_enabled=$enableval], + [debug_log_enabled='no']) +if test "x$debug_log_enabled" != "xno"; then + AC_DEFINE([ENABLE_DEBUG_LOGGING], 1, [Debug message logging]) +fi + +# Examples build +AC_ARG_ENABLE([examples-build], [AS_HELP_STRING([--enable-examples-build], + [build example applications (default n)])], + [build_examples=$enableval], + [build_examples='no']) +AM_CONDITIONAL([BUILD_EXAMPLES], [test "x$build_examples" != "xno"]) + +# check for -fvisibility=hidden compiler support (GCC >= 3.4) +saved_cflags="$CFLAGS" +# -Werror required for cygwin +CFLAGS="$CFLAGS -Werror -fvisibility=hidden" +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([])], + [VISIBILITY_CFLAGS="-fvisibility=hidden" + AC_DEFINE([DEFAULT_VISIBILITY], [__attribute__((visibility("default")))], [Default visibility]) ], + [ VISIBILITY_CFLAGS="" + AC_DEFINE([DEFAULT_VISIBILITY], [], [Default visibility]) ], + ]) +CFLAGS="$saved_cflags" + +# check for -Wno-pointer-sign compiler support (GCC >= 4) +saved_cflags="$CFLAGS" +CFLAGS="$CFLAGS -Wno-pointer-sign" +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([])], + nopointersign_cflags="-Wno-pointer-sign", nopointersign_cflags="") +CFLAGS="$saved_cflags" + +# sigaction not available on MinGW +AC_CHECK_FUNC([sigaction], [have_sigaction=yes], [have_sigaction=no]) +AM_CONDITIONAL([HAVE_SIGACTION], [test "x$have_sigaction" = "xyes"]) + +# headers not available on all platforms but required on others +AC_CHECK_HEADERS([sys/time.h]) +AC_CHECK_FUNCS(gettimeofday) + +AM_CFLAGS="-std=gnu99 -Wall -Wundef -Wunused -Wstrict-prototypes -Werror-implicit-function-declaration $nopointersign_cflags -Wshadow ${THREAD_CFLAGS} ${VISIBILITY_CFLAGS}" + +AC_SUBST(AM_CFLAGS) +AC_SUBST(LTLDFLAGS) + +AC_CONFIG_FILES([libusb-1.0.pc]) +AC_CONFIG_FILES([Makefile]) +AC_CONFIG_FILES([libusb/Makefile]) +AC_OUTPUT diff --git a/compat/libusb-1.0/libusb-1.0.pc.in b/compat/libusb-1.0/libusb-1.0.pc.in new file mode 100644 index 0000000..bb371d1 --- /dev/null +++ b/compat/libusb-1.0/libusb-1.0.pc.in @@ -0,0 +1,12 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libusb-1.0 +Description: C API for USB device access from Linux, Mac OS X, OpenBSD, NetBSD and Windows userspace +Version: @VERSION@ +Libs: -L${libdir} -lusb-1.0 +Libs.private: @LIBS@ +Cflags: -I${includedir}/libusb-1.0 + diff --git a/compat/libusb-1.0/libusb/Makefile.am b/compat/libusb-1.0/libusb/Makefile.am new file mode 100644 index 0000000..2f3f95f --- /dev/null +++ b/compat/libusb-1.0/libusb/Makefile.am @@ -0,0 +1,55 @@ +lib_LTLIBRARIES = libusb-1.0.la + +LINUX_USBFS_SRC = os/linux_usbfs.c +DARWIN_USB_SRC = os/darwin_usb.c +OPENBSD_USB_SRC = os/openbsd_usb.c +WINDOWS_USB_SRC = os/poll_windows.c os/windows_usb.c libusb-1.0.rc \ + libusb-1.0.def + +EXTRA_DIST = $(LINUX_USBFS_SRC) $(DARWIN_USB_SRC) $(OPENBSD_USB_SRC) \ + $(WINDOWS_USB_SRC) os/threads_posix.c os/threads_windows.c \ + os/linux_udev.c os/linux_netlink.c + +if OS_LINUX + +if USE_UDEV +OS_SRC = $(LINUX_USBFS_SRC) os/linux_udev.c +else +OS_SRC = $(LINUX_USBFS_SRC) os/linux_netlink.c +endif + +endif + +if OS_DARWIN +OS_SRC = $(DARWIN_USB_SRC) +AM_CFLAGS_EXT = -no-cpp-precomp +endif + +if OS_OPENBSD +OS_SRC = $(OPENBSD_USB_SRC) +endif + +if OS_WINDOWS +OS_SRC = $(WINDOWS_USB_SRC) + +.rc.lo: + $(AM_V_GEN)$(LIBTOOL) $(AM_V_lt) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --tag=RC --mode=compile $(RC) $(RCFLAGS) -i $< -o $@ + +libusb-1.0.rc: version.h +endif + +if THREADS_POSIX +THREADS_SRC = os/threads_posix.h os/threads_posix.c +else +THREADS_SRC = os/threads_windows.h os/threads_windows.c +endif + +libusb_1_0_la_CFLAGS = $(AM_CFLAGS) \ + -DLIBUSB_DESCRIBE=\"`git --git-dir "$(top_srcdir)/.git" describe --tags 2>/dev/null`\" +libusb_1_0_la_LDFLAGS = $(LTLDFLAGS) +libusb_1_0_la_SOURCES = libusbi.h core.c descriptor.c io.c sync.c $(OS_SRC) \ + hotplug.h hotplug.c os/linux_usbfs.h os/darwin_usb.h os/windows_usb.h \ + $(THREADS_SRC) os/poll_posix.h os/poll_windows.h + +hdrdir = $(includedir)/libusb-1.0 +hdr_HEADERS = libusb.h diff --git a/compat/libusb-1.0/libusb/core.c b/compat/libusb-1.0/libusb/core.c new file mode 100644 index 0000000..ed8c326 --- /dev/null +++ b/compat/libusb-1.0/libusb/core.c @@ -0,0 +1,2049 @@ +/* -*- Mode: C; indent-tabs-mode:nil ; c-basic-offset:8 -*- */ +/* + * Core functions for libusb + * Copyright (c) 2012-2013 Nathan Hjelm + * Copyright (C) 2007-2008 Daniel Drake + * Copyright (c) 2001 Johannes Erdfelt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_SYS_TIME_H +#include +#endif + +#include "libusbi.h" +#include "hotplug.h" + +#if defined(OS_LINUX) +const struct usbi_os_backend * const usbi_backend = &linux_usbfs_backend; +#elif defined(OS_DARWIN) +const struct usbi_os_backend * const usbi_backend = &darwin_backend; +#elif defined(OS_OPENBSD) +const struct usbi_os_backend * const usbi_backend = &openbsd_backend; +#elif defined(OS_WINDOWS) +const struct usbi_os_backend * const usbi_backend = &windows_backend; +#else +#error "Unsupported OS" +#endif + +const struct libusb_version libusb_version_internal = { + LIBUSB_MAJOR, LIBUSB_MINOR, LIBUSB_MICRO, LIBUSB_NANO, LIBUSB_RC, + LIBUSB_DESCRIBE +}; + +struct libusb_context *usbi_default_context = NULL; +static int default_context_refcnt = 0; +static usbi_mutex_static_t default_context_lock = USBI_MUTEX_INITIALIZER; + +usbi_mutex_static_t active_contexts_lock = USBI_MUTEX_INITIALIZER; +struct list_head active_contexts_list; + +/** + * \mainpage libusb-1.0 API Reference + * + * \section intro Introduction + * + * libusb is an open source library that allows you to communicate with USB + * devices from userspace. For more info, see the + * libusb homepage. + * + * This documentation is aimed at application developers wishing to + * communicate with USB peripherals from their own software. After reviewing + * this documentation, feedback and questions can be sent to the + * libusb-devel mailing + * list. + * + * This documentation assumes knowledge of how to operate USB devices from + * a software standpoint (descriptors, configurations, interfaces, endpoints, + * control/bulk/interrupt/isochronous transfers, etc). Full information + * can be found in the USB 2.0 + * Specification which is available for free download. You can probably + * find less verbose introductions by searching the web. + * + * \section features Library features + * + * - All transfer types supported (control/bulk/interrupt/isochronous) + * - 2 transfer interfaces: + * -# Synchronous (simple) + * -# Asynchronous (more complicated, but more powerful) + * - Thread safe (although the asynchronous interface means that you + * usually won't need to thread) + * - Lightweight with lean API + * - Compatible with libusb-0.1 through the libusb-compat-0.1 translation layer + * - Hotplug support (see \ref hotplug) + * + * \section gettingstarted Getting Started + * + * To begin reading the API documentation, start with the Modules page which + * links to the different categories of libusb's functionality. + * + * One decision you will have to make is whether to use the synchronous + * or the asynchronous data transfer interface. The \ref io documentation + * provides some insight into this topic. + * + * Some example programs can be found in the libusb source distribution under + * the "examples" subdirectory. The libusb homepage includes a list of + * real-life project examples which use libusb. + * + * \section errorhandling Error handling + * + * libusb functions typically return 0 on success or a negative error code + * on failure. These negative error codes relate to LIBUSB_ERROR constants + * which are listed on the \ref misc "miscellaneous" documentation page. + * + * \section msglog Debug message logging + * + * libusb does not log any messages by default. Your application is therefore + * free to close stdout/stderr and those descriptors may be reused without + * worry. + * + * The libusb_set_debug() function can be used to enable stdout/stderr logging + * of certain messages. Under standard configuration, libusb doesn't really + * log much at all, so you are advised to use this function to enable all + * error/warning/informational messages. It will help you debug problems with + * your software. + * + * The logged messages are unstructured. There is no one-to-one correspondence + * between messages being logged and success or failure return codes from + * libusb functions. There is no format to the messages, so you should not + * try to capture or parse them. They are not and will not be localized. + * These messages are not suitable for being passed to your application user; + * instead, you should interpret the error codes returned from libusb functions + * and provide appropriate notification to the user. The messages are simply + * there to aid you as a programmer, and if you're confused because you're + * getting a strange error code from a libusb function, enabling message + * logging may give you a suitable explanation. + * + * The LIBUSB_DEBUG environment variable can be used to enable message logging + * at run-time. This environment variable should be set to a number, which is + * interpreted the same as the libusb_set_debug() parameter. When this + * environment variable is set, the message logging verbosity level is fixed + * and libusb_set_debug() effectively does nothing. + * + * libusb can be compiled without any logging functions, useful for embedded + * systems. In this case, libusb_set_debug() and the LIBUSB_DEBUG environment + * variable have no effects. + * + * libusb can also be compiled with verbose debugging messages. When the + * library is compiled in this way, all messages of all verbosities are always + * logged. libusb_set_debug() and the LIBUSB_DEBUG environment variable have + * no effects. + * + * \section remarks Other remarks + * + * libusb does have imperfections. The \ref caveats "caveats" page attempts + * to document these. + */ + +/** + * \page caveats Caveats + * + * \section devresets Device resets + * + * The libusb_reset_device() function allows you to reset a device. If your + * program has to call such a function, it should obviously be aware that + * the reset will cause device state to change (e.g. register values may be + * reset). + * + * The problem is that any other program could reset the device your program + * is working with, at any time. libusb does not offer a mechanism to inform + * you when this has happened, so if someone else resets your device it will + * not be clear to your own program why the device state has changed. + * + * Ultimately, this is a limitation of writing drivers in userspace. + * Separation from the USB stack in the underlying kernel makes it difficult + * for the operating system to deliver such notifications to your program. + * The Linux kernel USB stack allows such reset notifications to be delivered + * to in-kernel USB drivers, but it is not clear how such notifications could + * be delivered to second-class drivers that live in userspace. + * + * \section blockonly Blocking-only functionality + * + * The functionality listed below is only available through synchronous, + * blocking functions. There are no asynchronous/non-blocking alternatives, + * and no clear ways of implementing these. + * + * - Configuration activation (libusb_set_configuration()) + * - Interface/alternate setting activation (libusb_set_interface_alt_setting()) + * - Releasing of interfaces (libusb_release_interface()) + * - Clearing of halt/stall condition (libusb_clear_halt()) + * - Device resets (libusb_reset_device()) + * + * \section configsel Configuration selection and handling + * + * When libusb presents a device handle to an application, there is a chance + * that the corresponding device may be in unconfigured state. For devices + * with multiple configurations, there is also a chance that the configuration + * currently selected is not the one that the application wants to use. + * + * The obvious solution is to add a call to libusb_set_configuration() early + * on during your device initialization routines, but there are caveats to + * be aware of: + * -# If the device is already in the desired configuration, calling + * libusb_set_configuration() using the same configuration value will cause + * a lightweight device reset. This may not be desirable behaviour. + * -# libusb will be unable to change configuration if the device is in + * another configuration and other programs or drivers have claimed + * interfaces under that configuration. + * -# In the case where the desired configuration is already active, libusb + * may not even be able to perform a lightweight device reset. For example, + * take my USB keyboard with fingerprint reader: I'm interested in driving + * the fingerprint reader interface through libusb, but the kernel's + * USB-HID driver will almost always have claimed the keyboard interface. + * Because the kernel has claimed an interface, it is not even possible to + * perform the lightweight device reset, so libusb_set_configuration() will + * fail. (Luckily the device in question only has a single configuration.) + * + * One solution to some of the above problems is to consider the currently + * active configuration. If the configuration we want is already active, then + * we don't have to select any configuration: +\code +cfg = libusb_get_configuration(dev); +if (cfg != desired) + libusb_set_configuration(dev, desired); +\endcode + * + * This is probably suitable for most scenarios, but is inherently racy: + * another application or driver may change the selected configuration + * after the libusb_get_configuration() call. + * + * Even in cases where libusb_set_configuration() succeeds, consider that other + * applications or drivers may change configuration after your application + * calls libusb_set_configuration(). + * + * One possible way to lock your device into a specific configuration is as + * follows: + * -# Set the desired configuration (or use the logic above to realise that + * it is already in the desired configuration) + * -# Claim the interface that you wish to use + * -# Check that the currently active configuration is the one that you want + * to use. + * + * The above method works because once an interface is claimed, no application + * or driver is able to select another configuration. + * + * \section earlycomp Early transfer completion + * + * NOTE: This section is currently Linux-centric. I am not sure if any of these + * considerations apply to Darwin or other platforms. + * + * When a transfer completes early (i.e. when less data is received/sent in + * any one packet than the transfer buffer allows for) then libusb is designed + * to terminate the transfer immediately, not transferring or receiving any + * more data unless other transfers have been queued by the user. + * + * On legacy platforms, libusb is unable to do this in all situations. After + * the incomplete packet occurs, "surplus" data may be transferred. Prior to + * libusb v1.0.2, this information was lost (and for device-to-host transfers, + * the corresponding data was discarded). As of libusb v1.0.3, this information + * is kept (the data length of the transfer is updated) and, for device-to-host + * transfers, any surplus data was added to the buffer. Still, this is not + * a nice solution because it loses the information about the end of the short + * packet, and the user probably wanted that surplus data to arrive in the next + * logical transfer. + * + * A previous workaround was to only ever submit transfers of size 16kb or + * less. + * + * As of libusb v1.0.4 and Linux v2.6.32, this is fixed. A technical + * explanation of this issue follows. + * + * When you ask libusb to submit a bulk transfer larger than 16kb in size, + * libusb breaks it up into a number of smaller subtransfers. This is because + * the usbfs kernel interface only accepts transfers of up to 16kb in size. + * The subtransfers are submitted all at once so that the kernel can queue + * them at the hardware level, therefore maximizing bus throughput. + * + * On legacy platforms, this caused problems when transfers completed early. + * Upon this event, the kernel would terminate all further packets in that + * subtransfer (but not any following ones). libusb would note this event and + * immediately cancel any following subtransfers that had been queued, + * but often libusb was not fast enough, and the following subtransfers had + * started before libusb got around to cancelling them. + * + * Thanks to an API extension to usbfs, this is fixed with recent kernel and + * libusb releases. The solution was to allow libusb to communicate to the + * kernel where boundaries occur between logical libusb-level transfers. When + * a short transfer (or other error) occurs, the kernel will cancel all the + * subtransfers until the boundary without allowing those transfers to start. + * + * \section zlp Zero length packets + * + * - libusb is able to send a packet of zero length to an endpoint simply by + * submitting a transfer of zero length. On Linux, this did not work with + * libusb versions prior to 1.0.3 and kernel versions prior to 2.6.31. + * - The \ref libusb_transfer_flags::LIBUSB_TRANSFER_ADD_ZERO_PACKET + * "LIBUSB_TRANSFER_ADD_ZERO_PACKET" flag is currently only supported on Linux. + */ + +/** + * \page contexts Contexts + * + * It is possible that libusb may be used simultaneously from two independent + * libraries linked into the same executable. For example, if your application + * has a plugin-like system which allows the user to dynamically load a range + * of modules into your program, it is feasible that two independently + * developed modules may both use libusb. + * + * libusb is written to allow for these multiple user scenarios. The two + * "instances" of libusb will not interfere: libusb_set_debug() calls + * from one user will not affect the same settings for other users, other + * users can continue using libusb after one of them calls libusb_exit(), etc. + * + * This is made possible through libusb's context concept. When you + * call libusb_init(), you are (optionally) given a context. You can then pass + * this context pointer back into future libusb functions. + * + * In order to keep things simple for more simplistic applications, it is + * legal to pass NULL to all functions requiring a context pointer (as long as + * you're sure no other code will attempt to use libusb from the same process). + * When you pass NULL, the default context will be used. The default context + * is created the first time a process calls libusb_init() when no other + * context is alive. Contexts are destroyed during libusb_exit(). + * + * The default context is reference-counted and can be shared. That means that + * if libusb_init(NULL) is called twice within the same process, the two + * users end up sharing the same context. The deinitialization and freeing of + * the default context will only happen when the last user calls libusb_exit(). + * In other words, the default context is created and initialized when its + * reference count goes from 0 to 1, and is deinitialized and destroyed when + * its reference count goes from 1 to 0. + * + * You may be wondering why only a subset of libusb functions require a + * context pointer in their function definition. Internally, libusb stores + * context pointers in other objects (e.g. libusb_device instances) and hence + * can infer the context from those objects. + */ + +/** + * @defgroup lib Library initialization/deinitialization + * This page details how to initialize and deinitialize libusb. Initialization + * must be performed before using any libusb functionality, and similarly you + * must not call any libusb functions after deinitialization. + */ + +/** + * @defgroup dev Device handling and enumeration + * The functionality documented below is designed to help with the following + * operations: + * - Enumerating the USB devices currently attached to the system + * - Choosing a device to operate from your software + * - Opening and closing the chosen device + * + * \section nutshell In a nutshell... + * + * The description below really makes things sound more complicated than they + * actually are. The following sequence of function calls will be suitable + * for almost all scenarios and does not require you to have such a deep + * understanding of the resource management issues: + * \code +// discover devices +libusb_device **list; +libusb_device *found = NULL; +ssize_t cnt = libusb_get_device_list(NULL, &list); +ssize_t i = 0; +int err = 0; +if (cnt < 0) + error(); + +for (i = 0; i < cnt; i++) { + libusb_device *device = list[i]; + if (is_interesting(device)) { + found = device; + break; + } +} + +if (found) { + libusb_device_handle *handle; + + err = libusb_open(found, &handle); + if (err) + error(); + // etc +} + +libusb_free_device_list(list, 1); +\endcode + * + * The two important points: + * - You asked libusb_free_device_list() to unreference the devices (2nd + * parameter) + * - You opened the device before freeing the list and unreferencing the + * devices + * + * If you ended up with a handle, you can now proceed to perform I/O on the + * device. + * + * \section devshandles Devices and device handles + * libusb has a concept of a USB device, represented by the + * \ref libusb_device opaque type. A device represents a USB device that + * is currently or was previously connected to the system. Using a reference + * to a device, you can determine certain information about the device (e.g. + * you can read the descriptor data). + * + * The libusb_get_device_list() function can be used to obtain a list of + * devices currently connected to the system. This is known as device + * discovery. + * + * Just because you have a reference to a device does not mean it is + * necessarily usable. The device may have been unplugged, you may not have + * permission to operate such device, or another program or driver may be + * using the device. + * + * When you've found a device that you'd like to operate, you must ask + * libusb to open the device using the libusb_open() function. Assuming + * success, libusb then returns you a device handle + * (a \ref libusb_device_handle pointer). All "real" I/O operations then + * operate on the handle rather than the original device pointer. + * + * \section devref Device discovery and reference counting + * + * Device discovery (i.e. calling libusb_get_device_list()) returns a + * freshly-allocated list of devices. The list itself must be freed when + * you are done with it. libusb also needs to know when it is OK to free + * the contents of the list - the devices themselves. + * + * To handle these issues, libusb provides you with two separate items: + * - A function to free the list itself + * - A reference counting system for the devices inside + * + * New devices presented by the libusb_get_device_list() function all have a + * reference count of 1. You can increase and decrease reference count using + * libusb_ref_device() and libusb_unref_device(). A device is destroyed when + * its reference count reaches 0. + * + * With the above information in mind, the process of opening a device can + * be viewed as follows: + * -# Discover devices using libusb_get_device_list(). + * -# Choose the device that you want to operate, and call libusb_open(). + * -# Unref all devices in the discovered device list. + * -# Free the discovered device list. + * + * The order is important - you must not unreference the device before + * attempting to open it, because unreferencing it may destroy the device. + * + * For convenience, the libusb_free_device_list() function includes a + * parameter to optionally unreference all the devices in the list before + * freeing the list itself. This combines steps 3 and 4 above. + * + * As an implementation detail, libusb_open() actually adds a reference to + * the device in question. This is because the device remains available + * through the handle via libusb_get_device(). The reference is deleted during + * libusb_close(). + */ + +/** @defgroup misc Miscellaneous */ + +/* we traverse usbfs without knowing how many devices we are going to find. + * so we create this discovered_devs model which is similar to a linked-list + * which grows when required. it can be freed once discovery has completed, + * eliminating the need for a list node in the libusb_device structure + * itself. */ +#define DISCOVERED_DEVICES_SIZE_STEP 8 + +static struct discovered_devs *discovered_devs_alloc(void) +{ + struct discovered_devs *ret = + malloc(sizeof(*ret) + (sizeof(void *) * DISCOVERED_DEVICES_SIZE_STEP)); + + if (ret) { + ret->len = 0; + ret->capacity = DISCOVERED_DEVICES_SIZE_STEP; + } + return ret; +} + +/* append a device to the discovered devices collection. may realloc itself, + * returning new discdevs. returns NULL on realloc failure. */ +struct discovered_devs *discovered_devs_append( + struct discovered_devs *discdevs, struct libusb_device *dev) +{ + size_t len = discdevs->len; + size_t capacity; + + /* if there is space, just append the device */ + if (len < discdevs->capacity) { + discdevs->devices[len] = libusb_ref_device(dev); + discdevs->len++; + return discdevs; + } + + /* exceeded capacity, need to grow */ + usbi_dbg("need to increase capacity"); + capacity = discdevs->capacity + DISCOVERED_DEVICES_SIZE_STEP; + discdevs = realloc(discdevs, + sizeof(*discdevs) + (sizeof(void *) * capacity)); + if (discdevs) { + discdevs->capacity = capacity; + discdevs->devices[len] = libusb_ref_device(dev); + discdevs->len++; + } + + return discdevs; +} + +static void discovered_devs_free(struct discovered_devs *discdevs) +{ + size_t i; + + for (i = 0; i < discdevs->len; i++) + libusb_unref_device(discdevs->devices[i]); + + free(discdevs); +} + +/* Allocate a new device with a specific session ID. The returned device has + * a reference count of 1. */ +struct libusb_device *usbi_alloc_device(struct libusb_context *ctx, + unsigned long session_id) +{ + size_t priv_size = usbi_backend->device_priv_size; + struct libusb_device *dev = calloc(1, sizeof(*dev) + priv_size); + int r; + + if (!dev) + return NULL; + + r = usbi_mutex_init(&dev->lock, NULL); + if (r) { + free(dev); + return NULL; + } + + dev->ctx = ctx; + dev->refcnt = 1; + dev->session_data = session_id; + dev->speed = LIBUSB_SPEED_UNKNOWN; + memset(&dev->os_priv, 0, priv_size); + + if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) { + usbi_connect_device (dev); + } + + return dev; +} + +void usbi_connect_device(struct libusb_device *dev) +{ + libusb_hotplug_message message = {LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED, dev}; + int ret; + + dev->attached = 1; + + usbi_mutex_lock(&dev->ctx->usb_devs_lock); + list_add(&dev->list, &dev->ctx->usb_devs); + usbi_mutex_unlock(&dev->ctx->usb_devs_lock); + + /* Signal that an event has occurred for this device if we support hotplug AND + the hotplug pipe is ready. This prevents an event from getting raised during + initial enumeration. */ + if (libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG) && dev->ctx->hotplug_pipe[1] > 0) { + ret = usbi_write(dev->ctx->hotplug_pipe[1], &message, sizeof (message)); + if (sizeof (message) != ret) { + usbi_err(DEVICE_CTX(dev), "error writing hotplug message"); + } + } +} + +void usbi_disconnect_device(struct libusb_device *dev) +{ + libusb_hotplug_message message = {LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, dev}; + struct libusb_context *ctx = dev->ctx; + int ret; + + usbi_mutex_lock(&dev->lock); + dev->attached = 0; + usbi_mutex_unlock(&dev->lock); + + /* Signal that an event has occurred for this device if we support hotplug AND + the hotplug pipe is ready. This prevents an event from getting raised during + initial enumeration. libusb_handle_events will take care of dereferencing the + device. */ + if (libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG) && dev->ctx->hotplug_pipe[1] > 0) { + ret = usbi_write(dev->ctx->hotplug_pipe[1], &message, sizeof (message)); + if (sizeof(message) != ret) { + usbi_err(DEVICE_CTX(dev), "error writing hotplug message"); + } + } + + usbi_mutex_lock(&ctx->usb_devs_lock); + list_del(&dev->list); + usbi_mutex_unlock(&ctx->usb_devs_lock); +} + +/* Perform some final sanity checks on a newly discovered device. If this + * function fails (negative return code), the device should not be added + * to the discovered device list. */ +int usbi_sanitize_device(struct libusb_device *dev) +{ + int r; + uint8_t num_configurations; + + r = usbi_device_cache_descriptor(dev); + if (r < 0) + return r; + + num_configurations = dev->device_descriptor.bNumConfigurations; + if (num_configurations > USB_MAXCONFIG) { + usbi_err(DEVICE_CTX(dev), "too many configurations"); + return LIBUSB_ERROR_IO; + } else if (0 == num_configurations) + usbi_dbg("zero configurations, maybe an unauthorized device"); + + dev->num_configurations = num_configurations; + return 0; +} + +/* Examine libusb's internal list of known devices, looking for one with + * a specific session ID. Returns the matching device if it was found, and + * NULL otherwise. */ +struct libusb_device *usbi_get_device_by_session_id(struct libusb_context *ctx, + unsigned long session_id) +{ + struct libusb_device *dev; + struct libusb_device *ret = NULL; + + usbi_mutex_lock(&ctx->usb_devs_lock); + list_for_each_entry(dev, &ctx->usb_devs, list, struct libusb_device) + if (dev->session_data == session_id) { + ret = dev; + break; + } + usbi_mutex_unlock(&ctx->usb_devs_lock); + + return ret; +} + +/** @ingroup dev + * Returns a list of USB devices currently attached to the system. This is + * your entry point into finding a USB device to operate. + * + * You are expected to unreference all the devices when you are done with + * them, and then free the list with libusb_free_device_list(). Note that + * libusb_free_device_list() can unref all the devices for you. Be careful + * not to unreference a device you are about to open until after you have + * opened it. + * + * This return value of this function indicates the number of devices in + * the resultant list. The list is actually one element larger, as it is + * NULL-terminated. + * + * \param ctx the context to operate on, or NULL for the default context + * \param list output location for a list of devices. Must be later freed with + * libusb_free_device_list(). + * \returns The number of devices in the outputted list, or any + * \ref libusb_error according to errors encountered by the backend. + */ +ssize_t API_EXPORTED libusb_get_device_list(libusb_context *ctx, + libusb_device ***list) +{ + struct discovered_devs *discdevs = discovered_devs_alloc(); + struct libusb_device **ret; + int r = 0; + ssize_t i, len; + USBI_GET_CONTEXT(ctx); + usbi_dbg(""); + + if (!discdevs) + return LIBUSB_ERROR_NO_MEM; + + if (libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) { + /* backend provides hotplug support */ + struct libusb_device *dev; + + usbi_mutex_lock(&ctx->usb_devs_lock); + list_for_each_entry(dev, &ctx->usb_devs, list, struct libusb_device) { + discdevs = discovered_devs_append(discdevs, dev); + + if (!discdevs) { + r = LIBUSB_ERROR_NO_MEM; + break; + } + } + usbi_mutex_unlock(&ctx->usb_devs_lock); + } else { + /* backend does not provide hotplug support */ + r = usbi_backend->get_device_list(ctx, &discdevs); + } + + if (r < 0) { + len = r; + goto out; + } + + /* convert discovered_devs into a list */ + len = discdevs->len; + ret = malloc(sizeof(void *) * (len + 1)); + if (!ret) { + len = LIBUSB_ERROR_NO_MEM; + goto out; + } + + ret[len] = NULL; + for (i = 0; i < len; i++) { + struct libusb_device *dev = discdevs->devices[i]; + ret[i] = libusb_ref_device(dev); + } + *list = ret; + +out: + discovered_devs_free(discdevs); + return len; +} + +/** \ingroup dev + * Frees a list of devices previously discovered using + * libusb_get_device_list(). If the unref_devices parameter is set, the + * reference count of each device in the list is decremented by 1. + * \param list the list to free + * \param unref_devices whether to unref the devices in the list + */ +void API_EXPORTED libusb_free_device_list(libusb_device **list, + int unref_devices) +{ + if (!list) + return; + + if (unref_devices) { + int i = 0; + struct libusb_device *dev; + + while ((dev = list[i++]) != NULL) + libusb_unref_device(dev); + } + free(list); +} + +/** \ingroup dev + * Get the number of the bus that a device is connected to. + * \param dev a device + * \returns the bus number + */ +uint8_t API_EXPORTED libusb_get_bus_number(libusb_device *dev) +{ + return dev->bus_number; +} + +/** \ingroup dev + * Get the address of the device on the bus it is connected to. + * \param dev a device + * \returns the device address + */ +uint8_t API_EXPORTED libusb_get_device_address(libusb_device *dev) +{ + return dev->device_address; +} + +/** \ingroup dev + * Get the negotiated connection speed for a device. + * \param dev a device + * \returns a \ref libusb_speed code, where LIBUSB_SPEED_UNKNOWN means that + * the OS doesn't know or doesn't support returning the negotiated speed. + */ +int API_EXPORTED libusb_get_device_speed(libusb_device *dev) +{ + return dev->speed; +} + +static const struct libusb_endpoint_descriptor *find_endpoint( + struct libusb_config_descriptor *config, unsigned char endpoint) +{ + int iface_idx; + for (iface_idx = 0; iface_idx < config->bNumInterfaces; iface_idx++) { + const struct libusb_interface *iface = &config->interface[iface_idx]; + int altsetting_idx; + + for (altsetting_idx = 0; altsetting_idx < iface->num_altsetting; + altsetting_idx++) { + const struct libusb_interface_descriptor *altsetting + = &iface->altsetting[altsetting_idx]; + int ep_idx; + + for (ep_idx = 0; ep_idx < altsetting->bNumEndpoints; ep_idx++) { + const struct libusb_endpoint_descriptor *ep = + &altsetting->endpoint[ep_idx]; + if (ep->bEndpointAddress == endpoint) + return ep; + } + } + } + return NULL; +} + +/** \ingroup dev + * Convenience function to retrieve the wMaxPacketSize value for a particular + * endpoint in the active device configuration. + * + * This function was originally intended to be of assistance when setting up + * isochronous transfers, but a design mistake resulted in this function + * instead. It simply returns the wMaxPacketSize value without considering + * its contents. If you're dealing with isochronous transfers, you probably + * want libusb_get_max_iso_packet_size() instead. + * + * \param dev a device + * \param endpoint address of the endpoint in question + * \returns the wMaxPacketSize value + * \returns LIBUSB_ERROR_NOT_FOUND if the endpoint does not exist + * \returns LIBUSB_ERROR_OTHER on other failure + */ +int API_EXPORTED libusb_get_max_packet_size(libusb_device *dev, + unsigned char endpoint) +{ + struct libusb_config_descriptor *config; + const struct libusb_endpoint_descriptor *ep; + int r; + + r = libusb_get_active_config_descriptor(dev, &config); + if (r < 0) { + usbi_err(DEVICE_CTX(dev), + "could not retrieve active config descriptor"); + return LIBUSB_ERROR_OTHER; + } + + ep = find_endpoint(config, endpoint); + if (!ep) + return LIBUSB_ERROR_NOT_FOUND; + + r = ep->wMaxPacketSize; + libusb_free_config_descriptor(config); + return r; +} + +/** \ingroup dev + * Calculate the maximum packet size which a specific endpoint is capable is + * sending or receiving in the duration of 1 microframe + * + * Only the active configution is examined. The calculation is based on the + * wMaxPacketSize field in the endpoint descriptor as described in section + * 9.6.6 in the USB 2.0 specifications. + * + * If acting on an isochronous or interrupt endpoint, this function will + * multiply the value found in bits 0:10 by the number of transactions per + * microframe (determined by bits 11:12). Otherwise, this function just + * returns the numeric value found in bits 0:10. + * + * This function is useful for setting up isochronous transfers, for example + * you might pass the return value from this function to + * libusb_set_iso_packet_lengths() in order to set the length field of every + * isochronous packet in a transfer. + * + * Since v1.0.3. + * + * \param dev a device + * \param endpoint address of the endpoint in question + * \returns the maximum packet size which can be sent/received on this endpoint + * \returns LIBUSB_ERROR_NOT_FOUND if the endpoint does not exist + * \returns LIBUSB_ERROR_OTHER on other failure + */ +int API_EXPORTED libusb_get_max_iso_packet_size(libusb_device *dev, + unsigned char endpoint) +{ + struct libusb_config_descriptor *config; + const struct libusb_endpoint_descriptor *ep; + enum libusb_transfer_type ep_type; + uint16_t val; + int r; + + r = libusb_get_active_config_descriptor(dev, &config); + if (r < 0) { + usbi_err(DEVICE_CTX(dev), + "could not retrieve active config descriptor"); + return LIBUSB_ERROR_OTHER; + } + + ep = find_endpoint(config, endpoint); + if (!ep) + return LIBUSB_ERROR_NOT_FOUND; + + val = ep->wMaxPacketSize; + ep_type = ep->bmAttributes & 0x3; + libusb_free_config_descriptor(config); + + r = val & 0x07ff; + if (ep_type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS + || ep_type == LIBUSB_TRANSFER_TYPE_INTERRUPT) + r *= (1 + ((val >> 11) & 3)); + return r; +} + +/** \ingroup dev + * Increment the reference count of a device. + * \param dev the device to reference + * \returns the same device + */ +DEFAULT_VISIBILITY +libusb_device * LIBUSB_CALL libusb_ref_device(libusb_device *dev) +{ + usbi_mutex_lock(&dev->lock); + dev->refcnt++; + usbi_mutex_unlock(&dev->lock); + return dev; +} + +/** \ingroup dev + * Decrement the reference count of a device. If the decrement operation + * causes the reference count to reach zero, the device shall be destroyed. + * \param dev the device to unreference + */ +void API_EXPORTED libusb_unref_device(libusb_device *dev) +{ + int refcnt; + + if (!dev) + return; + + usbi_mutex_lock(&dev->lock); + refcnt = --dev->refcnt; + usbi_mutex_unlock(&dev->lock); + + if (refcnt == 0) { + usbi_dbg("destroy device %d.%d", dev->bus_number, dev->device_address); + + if (usbi_backend->destroy_device) + usbi_backend->destroy_device(dev); + + if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) { + /* backend does not support hotplug */ + usbi_disconnect_device(dev); + } + + usbi_mutex_destroy(&dev->lock); + free(dev); + } +} + +/* + * Interrupt the iteration of the event handling thread, so that it picks + * up the new fd. + */ +void usbi_fd_notification(struct libusb_context *ctx) +{ + unsigned char dummy = 1; + ssize_t r; + + if (ctx == NULL) + return; + + /* record that we are messing with poll fds */ + usbi_mutex_lock(&ctx->pollfd_modify_lock); + ctx->pollfd_modify++; + usbi_mutex_unlock(&ctx->pollfd_modify_lock); + + /* write some data on control pipe to interrupt event handlers */ + r = usbi_write(ctx->ctrl_pipe[1], &dummy, sizeof(dummy)); + if (r <= 0) { + usbi_warn(ctx, "internal signalling write failed"); + usbi_mutex_lock(&ctx->pollfd_modify_lock); + ctx->pollfd_modify--; + usbi_mutex_unlock(&ctx->pollfd_modify_lock); + return; + } + + /* take event handling lock */ + libusb_lock_events(ctx); + + /* read the dummy data */ + r = usbi_read(ctx->ctrl_pipe[0], &dummy, sizeof(dummy)); + if (r <= 0) + usbi_warn(ctx, "internal signalling read failed"); + + /* we're done with modifying poll fds */ + usbi_mutex_lock(&ctx->pollfd_modify_lock); + ctx->pollfd_modify--; + usbi_mutex_unlock(&ctx->pollfd_modify_lock); + + /* Release event handling lock and wake up event waiters */ + libusb_unlock_events(ctx); +} + +/** \ingroup dev + * Open a device and obtain a device handle. A handle allows you to perform + * I/O on the device in question. + * + * Internally, this function adds a reference to the device and makes it + * available to you through libusb_get_device(). This reference is removed + * during libusb_close(). + * + * This is a non-blocking function; no requests are sent over the bus. + * + * \param dev the device to open + * \param handle output location for the returned device handle pointer. Only + * populated when the return code is 0. + * \returns 0 on success + * \returns LIBUSB_ERROR_NO_MEM on memory allocation failure + * \returns LIBUSB_ERROR_ACCESS if the user has insufficient permissions + * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected + * \returns another LIBUSB_ERROR code on other failure + */ +int API_EXPORTED libusb_open(libusb_device *dev, + libusb_device_handle **handle) +{ + struct libusb_context *ctx = DEVICE_CTX(dev); + struct libusb_device_handle *_handle; + size_t priv_size = usbi_backend->device_handle_priv_size; + int r; + usbi_dbg("open %d.%d", dev->bus_number, dev->device_address); + + if (!dev->attached) { + return LIBUSB_ERROR_NO_DEVICE; + } + + _handle = malloc(sizeof(*_handle) + priv_size); + if (!_handle) + return LIBUSB_ERROR_NO_MEM; + + r = usbi_mutex_init(&_handle->lock, NULL); + if (r) { + free(_handle); + return LIBUSB_ERROR_OTHER; + } + + _handle->dev = libusb_ref_device(dev); + _handle->claimed_interfaces = 0; + memset(&_handle->os_priv, 0, priv_size); + + r = usbi_backend->open(_handle); + if (r < 0) { + usbi_dbg("open %d.%d returns %d", dev->bus_number, dev->device_address, r); + libusb_unref_device(dev); + usbi_mutex_destroy(&_handle->lock); + free(_handle); + return r; + } + + usbi_mutex_lock(&ctx->open_devs_lock); + list_add(&_handle->list, &ctx->open_devs); + usbi_mutex_unlock(&ctx->open_devs_lock); + *handle = _handle; + + /* At this point, we want to interrupt any existing event handlers so + * that they realise the addition of the new device's poll fd. One + * example when this is desirable is if the user is running a separate + * dedicated libusb events handling thread, which is running with a long + * or infinite timeout. We want to interrupt that iteration of the loop, + * so that it picks up the new fd, and then continues. */ + usbi_fd_notification(ctx); + + return 0; +} + +/** \ingroup dev + * Convenience function for finding a device with a particular + * idVendor/idProduct combination. This function is intended + * for those scenarios where you are using libusb to knock up a quick test + * application - it allows you to avoid calling libusb_get_device_list() and + * worrying about traversing/freeing the list. + * + * This function has limitations and is hence not intended for use in real + * applications: if multiple devices have the same IDs it will only + * give you the first one, etc. + * + * \param ctx the context to operate on, or NULL for the default context + * \param vendor_id the idVendor value to search for + * \param product_id the idProduct value to search for + * \returns a handle for the first found device, or NULL on error or if the + * device could not be found. */ +DEFAULT_VISIBILITY +libusb_device_handle * LIBUSB_CALL libusb_open_device_with_vid_pid( + libusb_context *ctx, uint16_t vendor_id, uint16_t product_id) +{ + struct libusb_device **devs; + struct libusb_device *found = NULL; + struct libusb_device *dev; + struct libusb_device_handle *handle = NULL; + size_t i = 0; + int r; + + if (libusb_get_device_list(ctx, &devs) < 0) + return NULL; + + while ((dev = devs[i++]) != NULL) { + struct libusb_device_descriptor desc; + r = libusb_get_device_descriptor(dev, &desc); + if (r < 0) + goto out; + if (desc.idVendor == vendor_id && desc.idProduct == product_id) { + found = dev; + break; + } + } + + if (found) { + r = libusb_open(found, &handle); + if (r < 0) + handle = NULL; + } + +out: + libusb_free_device_list(devs, 1); + return handle; +} + +static void do_close(struct libusb_context *ctx, + struct libusb_device_handle *dev_handle) +{ + struct usbi_transfer *itransfer; + struct usbi_transfer *tmp; + + libusb_lock_events(ctx); + + /* remove any transfers in flight that are for this device */ + usbi_mutex_lock(&ctx->flying_transfers_lock); + + /* safe iteration because transfers may be being deleted */ + list_for_each_entry_safe(itransfer, tmp, &ctx->flying_transfers, list, struct usbi_transfer) { + struct libusb_transfer *transfer = + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + + if (transfer->dev_handle != dev_handle) + continue; + + if (!(itransfer->flags & USBI_TRANSFER_DEVICE_DISAPPEARED)) { + usbi_err(ctx, "Device handle closed while transfer was still being processed, but the device is still connected as far as we know"); + + if (itransfer->flags & USBI_TRANSFER_CANCELLING) + usbi_warn(ctx, "A cancellation for an in-flight transfer hasn't completed but closing the device handle"); + else + usbi_err(ctx, "A cancellation hasn't even been scheduled on the transfer for which the device is closing"); + } + + /* remove from the list of in-flight transfers and make sure + * we don't accidentally use the device handle in the future + * (or that such accesses will be easily caught and identified as a crash) + */ + usbi_mutex_lock(&itransfer->lock); + list_del(&itransfer->list); + transfer->dev_handle = NULL; + usbi_mutex_unlock(&itransfer->lock); + + /* it is up to the user to free up the actual transfer struct. this is + * just making sure that we don't attempt to process the transfer after + * the device handle is invalid + */ + usbi_dbg("Removed transfer %p from the in-flight list because device handle %p closed", + transfer, dev_handle); + } + usbi_mutex_unlock(&ctx->flying_transfers_lock); + + libusb_unlock_events(ctx); + + usbi_mutex_lock(&ctx->open_devs_lock); + list_del(&dev_handle->list); + usbi_mutex_unlock(&ctx->open_devs_lock); + + usbi_backend->close(dev_handle); + libusb_unref_device(dev_handle->dev); + usbi_mutex_destroy(&dev_handle->lock); + free(dev_handle); +} + +/** \ingroup dev + * Close a device handle. Should be called on all open handles before your + * application exits. + * + * Internally, this function destroys the reference that was added by + * libusb_open() on the given device. + * + * This is a non-blocking function; no requests are sent over the bus. + * + * \param dev_handle the handle to close + */ +void API_EXPORTED libusb_close(libusb_device_handle *dev_handle) +{ + struct libusb_context *ctx; + unsigned char dummy = 1; + ssize_t r; + + if (!dev_handle) + return; + usbi_dbg(""); + + ctx = HANDLE_CTX(dev_handle); + + /* Similarly to libusb_open(), we want to interrupt all event handlers + * at this point. More importantly, we want to perform the actual close of + * the device while holding the event handling lock (preventing any other + * thread from doing event handling) because we will be removing a file + * descriptor from the polling loop. */ + + /* record that we are messing with poll fds */ + usbi_mutex_lock(&ctx->pollfd_modify_lock); + ctx->pollfd_modify++; + usbi_mutex_unlock(&ctx->pollfd_modify_lock); + + /* write some data on control pipe to interrupt event handlers */ + r = usbi_write(ctx->ctrl_pipe[1], &dummy, sizeof(dummy)); + if (r <= 0) { + usbi_warn(ctx, "internal signalling write failed, closing anyway"); + do_close(ctx, dev_handle); + usbi_mutex_lock(&ctx->pollfd_modify_lock); + ctx->pollfd_modify--; + usbi_mutex_unlock(&ctx->pollfd_modify_lock); + return; + } + + /* take event handling lock */ + libusb_lock_events(ctx); + + /* read the dummy data */ + r = usbi_read(ctx->ctrl_pipe[0], &dummy, sizeof(dummy)); + if (r <= 0) + usbi_warn(ctx, "internal signalling read failed, closing anyway"); + + /* Close the device */ + do_close(ctx, dev_handle); + + /* we're done with modifying poll fds */ + usbi_mutex_lock(&ctx->pollfd_modify_lock); + ctx->pollfd_modify--; + usbi_mutex_unlock(&ctx->pollfd_modify_lock); + + /* Release event handling lock and wake up event waiters */ + libusb_unlock_events(ctx); +} + +/** \ingroup dev + * Get the underlying device for a handle. This function does not modify + * the reference count of the returned device, so do not feel compelled to + * unreference it when you are done. + * \param dev_handle a device handle + * \returns the underlying device + */ +DEFAULT_VISIBILITY +libusb_device * LIBUSB_CALL libusb_get_device(libusb_device_handle *dev_handle) +{ + return dev_handle->dev; +} + +/** \ingroup dev + * Determine the bConfigurationValue of the currently active configuration. + * + * You could formulate your own control request to obtain this information, + * but this function has the advantage that it may be able to retrieve the + * information from operating system caches (no I/O involved). + * + * If the OS does not cache this information, then this function will block + * while a control transfer is submitted to retrieve the information. + * + * This function will return a value of 0 in the config output + * parameter if the device is in unconfigured state. + * + * \param dev a device handle + * \param config output location for the bConfigurationValue of the active + * configuration (only valid for return code 0) + * \returns 0 on success + * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected + * \returns another LIBUSB_ERROR code on other failure + */ +int API_EXPORTED libusb_get_configuration(libusb_device_handle *dev, + int *config) +{ + int r = LIBUSB_ERROR_NOT_SUPPORTED; + + usbi_dbg(""); + if (usbi_backend->get_configuration) + r = usbi_backend->get_configuration(dev, config); + + if (r == LIBUSB_ERROR_NOT_SUPPORTED) { + uint8_t tmp = 0; + usbi_dbg("falling back to control message"); + r = libusb_control_transfer(dev, LIBUSB_ENDPOINT_IN, + LIBUSB_REQUEST_GET_CONFIGURATION, 0, 0, &tmp, 1, 1000); + if (r == 0) { + usbi_err(HANDLE_CTX(dev), "zero bytes returned in ctrl transfer?"); + r = LIBUSB_ERROR_IO; + } else if (r == 1) { + r = 0; + *config = tmp; + } else { + usbi_dbg("control failed, error %d", r); + } + } + + if (r == 0) + usbi_dbg("active config %d", *config); + + return r; +} + +/** \ingroup dev + * Set the active configuration for a device. + * + * The operating system may or may not have already set an active + * configuration on the device. It is up to your application to ensure the + * correct configuration is selected before you attempt to claim interfaces + * and perform other operations. + * + * If you call this function on a device already configured with the selected + * configuration, then this function will act as a lightweight device reset: + * it will issue a SET_CONFIGURATION request using the current configuration, + * causing most USB-related device state to be reset (altsetting reset to zero, + * endpoint halts cleared, toggles reset). + * + * You cannot change/reset configuration if your application has claimed + * interfaces - you should free them with libusb_release_interface() first. + * You cannot change/reset configuration if other applications or drivers have + * claimed interfaces. + * + * A configuration value of -1 will put the device in unconfigured state. + * The USB specifications state that a configuration value of 0 does this, + * however buggy devices exist which actually have a configuration 0. + * + * You should always use this function rather than formulating your own + * SET_CONFIGURATION control request. This is because the underlying operating + * system needs to know when such changes happen. + * + * This is a blocking function. + * + * \param dev a device handle + * \param configuration the bConfigurationValue of the configuration you + * wish to activate, or -1 if you wish to put the device in unconfigured state + * \returns 0 on success + * \returns LIBUSB_ERROR_NOT_FOUND if the requested configuration does not exist + * \returns LIBUSB_ERROR_BUSY if interfaces are currently claimed + * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected + * \returns another LIBUSB_ERROR code on other failure + */ +int API_EXPORTED libusb_set_configuration(libusb_device_handle *dev, + int configuration) +{ + usbi_dbg("configuration %d", configuration); + return usbi_backend->set_configuration(dev, configuration); +} + +/** \ingroup dev + * Claim an interface on a given device handle. You must claim the interface + * you wish to use before you can perform I/O on any of its endpoints. + * + * It is legal to attempt to claim an already-claimed interface, in which + * case libusb just returns 0 without doing anything. + * + * Claiming of interfaces is a purely logical operation; it does not cause + * any requests to be sent over the bus. Interface claiming is used to + * instruct the underlying operating system that your application wishes + * to take ownership of the interface. + * + * This is a non-blocking function. + * + * \param dev a device handle + * \param interface_number the bInterfaceNumber of the interface you + * wish to claim + * \returns 0 on success + * \returns LIBUSB_ERROR_NOT_FOUND if the requested interface does not exist + * \returns LIBUSB_ERROR_BUSY if another program or driver has claimed the + * interface + * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected + * \returns a LIBUSB_ERROR code on other failure + */ +int API_EXPORTED libusb_claim_interface(libusb_device_handle *dev, + int interface_number) +{ + int r = 0; + + usbi_dbg("interface %d", interface_number); + if (interface_number >= USB_MAXINTERFACES) + return LIBUSB_ERROR_INVALID_PARAM; + + if (!dev->dev->attached) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_mutex_lock(&dev->lock); + if (dev->claimed_interfaces & (1 << interface_number)) + goto out; + + r = usbi_backend->claim_interface(dev, interface_number); + if (r == 0) + dev->claimed_interfaces |= 1 << interface_number; + +out: + usbi_mutex_unlock(&dev->lock); + return r; +} + +/** \ingroup dev + * Release an interface previously claimed with libusb_claim_interface(). You + * should release all claimed interfaces before closing a device handle. + * + * This is a blocking function. A SET_INTERFACE control request will be sent + * to the device, resetting interface state to the first alternate setting. + * + * \param dev a device handle + * \param interface_number the bInterfaceNumber of the + * previously-claimed interface + * \returns 0 on success + * \returns LIBUSB_ERROR_NOT_FOUND if the interface was not claimed + * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected + * \returns another LIBUSB_ERROR code on other failure + */ +int API_EXPORTED libusb_release_interface(libusb_device_handle *dev, + int interface_number) +{ + int r; + + usbi_dbg("interface %d", interface_number); + if (interface_number >= USB_MAXINTERFACES) + return LIBUSB_ERROR_INVALID_PARAM; + + usbi_mutex_lock(&dev->lock); + if (!(dev->claimed_interfaces & (1 << interface_number))) { + r = LIBUSB_ERROR_NOT_FOUND; + goto out; + } + + r = usbi_backend->release_interface(dev, interface_number); + if (r == 0) + dev->claimed_interfaces &= ~(1 << interface_number); + +out: + usbi_mutex_unlock(&dev->lock); + return r; +} + +/** \ingroup dev + * Activate an alternate setting for an interface. The interface must have + * been previously claimed with libusb_claim_interface(). + * + * You should always use this function rather than formulating your own + * SET_INTERFACE control request. This is because the underlying operating + * system needs to know when such changes happen. + * + * This is a blocking function. + * + * \param dev a device handle + * \param interface_number the bInterfaceNumber of the + * previously-claimed interface + * \param alternate_setting the bAlternateSetting of the alternate + * setting to activate + * \returns 0 on success + * \returns LIBUSB_ERROR_NOT_FOUND if the interface was not claimed, or the + * requested alternate setting does not exist + * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected + * \returns another LIBUSB_ERROR code on other failure + */ +int API_EXPORTED libusb_set_interface_alt_setting(libusb_device_handle *dev, + int interface_number, int alternate_setting) +{ + usbi_dbg("interface %d altsetting %d", + interface_number, alternate_setting); + if (interface_number >= USB_MAXINTERFACES) + return LIBUSB_ERROR_INVALID_PARAM; + + usbi_mutex_lock(&dev->lock); + if (!dev->dev->attached) { + usbi_mutex_unlock(&dev->lock); + return LIBUSB_ERROR_NO_DEVICE; + } + + if (!(dev->claimed_interfaces & (1 << interface_number))) { + usbi_mutex_unlock(&dev->lock); + return LIBUSB_ERROR_NOT_FOUND; + } + usbi_mutex_unlock(&dev->lock); + + return usbi_backend->set_interface_altsetting(dev, interface_number, + alternate_setting); +} + +/** \ingroup dev + * Clear the halt/stall condition for an endpoint. Endpoints with halt status + * are unable to receive or transmit data until the halt condition is stalled. + * + * You should cancel all pending transfers before attempting to clear the halt + * condition. + * + * This is a blocking function. + * + * \param dev a device handle + * \param endpoint the endpoint to clear halt status + * \returns 0 on success + * \returns LIBUSB_ERROR_NOT_FOUND if the endpoint does not exist + * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected + * \returns another LIBUSB_ERROR code on other failure + */ +int API_EXPORTED libusb_clear_halt(libusb_device_handle *dev, + unsigned char endpoint) +{ + usbi_dbg("endpoint %x", endpoint); + if (!dev->dev->attached) + return LIBUSB_ERROR_NO_DEVICE; + + return usbi_backend->clear_halt(dev, endpoint); +} + +/** \ingroup dev + * Perform a USB port reset to reinitialize a device. The system will attempt + * to restore the previous configuration and alternate settings after the + * reset has completed. + * + * If the reset fails, the descriptors change, or the previous state cannot be + * restored, the device will appear to be disconnected and reconnected. This + * means that the device handle is no longer valid (you should close it) and + * rediscover the device. A return code of LIBUSB_ERROR_NOT_FOUND indicates + * when this is the case. + * + * This is a blocking function which usually incurs a noticeable delay. + * + * \param dev a handle of the device to reset + * \returns 0 on success + * \returns LIBUSB_ERROR_NOT_FOUND if re-enumeration is required, or if the + * device has been disconnected + * \returns another LIBUSB_ERROR code on other failure + */ +int API_EXPORTED libusb_reset_device(libusb_device_handle *dev) +{ + usbi_dbg(""); + if (!dev->dev->attached) + return LIBUSB_ERROR_NO_DEVICE; + + return usbi_backend->reset_device(dev); +} + +/** \ingroup dev + * Determine if a kernel driver is active on an interface. If a kernel driver + * is active, you cannot claim the interface, and libusb will be unable to + * perform I/O. + * + * This functionality is not available on Windows. + * + * \param dev a device handle + * \param interface_number the interface to check + * \returns 0 if no kernel driver is active + * \returns 1 if a kernel driver is active + * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected + * \returns LIBUSB_ERROR_NOT_SUPPORTED on platforms where the functionality + * is not available + * \returns another LIBUSB_ERROR code on other failure + * \see libusb_detach_kernel_driver() + */ +int API_EXPORTED libusb_kernel_driver_active(libusb_device_handle *dev, + int interface_number) +{ + usbi_dbg("interface %d", interface_number); + + if (!dev->dev->attached) + return LIBUSB_ERROR_NO_DEVICE; + + if (usbi_backend->kernel_driver_active) + return usbi_backend->kernel_driver_active(dev, interface_number); + else + return LIBUSB_ERROR_NOT_SUPPORTED; +} + +/** \ingroup dev + * Detach a kernel driver from an interface. If successful, you will then be + * able to claim the interface and perform I/O. + * + * This functionality is not available on Darwin or Windows. + * + * \param dev a device handle + * \param interface_number the interface to detach the driver from + * \returns 0 on success + * \returns LIBUSB_ERROR_NOT_FOUND if no kernel driver was active + * \returns LIBUSB_ERROR_INVALID_PARAM if the interface does not exist + * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected + * \returns LIBUSB_ERROR_NOT_SUPPORTED on platforms where the functionality + * is not available + * \returns another LIBUSB_ERROR code on other failure + * \see libusb_kernel_driver_active() + */ +int API_EXPORTED libusb_detach_kernel_driver(libusb_device_handle *dev, + int interface_number) +{ + usbi_dbg("interface %d", interface_number); + + if (!dev->dev->attached) + return LIBUSB_ERROR_NO_DEVICE; + + if (usbi_backend->detach_kernel_driver) + return usbi_backend->detach_kernel_driver(dev, interface_number); + else + return LIBUSB_ERROR_NOT_SUPPORTED; +} + +/** \ingroup dev + * Re-attach an interface's kernel driver, which was previously detached + * using libusb_detach_kernel_driver(). This call is only effective on + * Linux and returns LIBUSB_ERROR_NOT_SUPPORTED on all other platforms. + * + * This functionality is not available on Darwin or Windows. + * + * \param dev a device handle + * \param interface_number the interface to attach the driver from + * \returns 0 on success + * \returns LIBUSB_ERROR_NOT_FOUND if no kernel driver was active + * \returns LIBUSB_ERROR_INVALID_PARAM if the interface does not exist + * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected + * \returns LIBUSB_ERROR_NOT_SUPPORTED on platforms where the functionality + * is not available + * \returns LIBUSB_ERROR_BUSY if the driver cannot be attached because the + * interface is claimed by a program or driver + * \returns another LIBUSB_ERROR code on other failure + * \see libusb_kernel_driver_active() + */ +int API_EXPORTED libusb_attach_kernel_driver(libusb_device_handle *dev, + int interface_number) +{ + usbi_dbg("interface %d", interface_number); + + if (!dev->dev->attached) + return LIBUSB_ERROR_NO_DEVICE; + + if (usbi_backend->attach_kernel_driver) + return usbi_backend->attach_kernel_driver(dev, interface_number); + else + return LIBUSB_ERROR_NOT_SUPPORTED; +} + +/** \ingroup lib + * Set message verbosity. + * - Level 0: no messages ever printed by the library (default) + * - Level 1: error messages are printed to stderr + * - Level 2: warning and error messages are printed to stderr + * - Level 3: informational messages are printed to stdout, warning and error + * messages are printed to stderr + * + * The default level is 0, which means no messages are ever printed. If you + * choose to increase the message verbosity level, ensure that your + * application does not close the stdout/stderr file descriptors. + * + * You are advised to set level 3. libusb is conservative with its message + * logging and most of the time, will only log messages that explain error + * conditions and other oddities. This will help you debug your software. + * + * If the LIBUSB_DEBUG environment variable was set when libusb was + * initialized, this function does nothing: the message verbosity is fixed + * to the value in the environment variable. + * + * If libusb was compiled without any message logging, this function does + * nothing: you'll never get any messages. + * + * If libusb was compiled with verbose debug message logging, this function + * does nothing: you'll always get messages from all levels. + * + * \param ctx the context to operate on, or NULL for the default context + * \param level debug level to set + */ +void API_EXPORTED libusb_set_debug(libusb_context *ctx, int level) +{ + USBI_GET_CONTEXT(ctx); + if (!ctx->debug_fixed) + ctx->debug = level; +} + +/** \ingroup lib + * Initialize libusb. This function must be called before calling any other + * libusb function. + * + * If you do not provide an output location for a context pointer, a default + * context will be created. If there was already a default context, it will + * be reused (and nothing will be initialized/reinitialized). + * + * \param context Optional output location for context pointer. + * Only valid on return code 0. + * \returns 0 on success, or a LIBUSB_ERROR code on failure + * \see contexts + */ +int API_EXPORTED libusb_init(libusb_context **context) +{ + struct libusb_device *dev, *next; + char *dbg = getenv("LIBUSB_DEBUG"); + struct libusb_context *ctx; + static int first_init = 1; + int r = 0; + + usbi_mutex_static_lock(&default_context_lock); + if (!context && usbi_default_context) { + usbi_dbg("reusing default context"); + default_context_refcnt++; + usbi_mutex_static_unlock(&default_context_lock); + return 0; + } + + ctx = malloc(sizeof(*ctx)); + if (!ctx) { + r = LIBUSB_ERROR_NO_MEM; + goto err_unlock; + } + memset(ctx, 0, sizeof(*ctx)); + + if (dbg) { + ctx->debug = atoi(dbg); + if (ctx->debug) + ctx->debug_fixed = 1; + } + + usbi_dbg("libusb-%d.%d.%d%s%s%s", + libusb_version_internal.major, + libusb_version_internal.minor, + libusb_version_internal.micro, + libusb_version_internal.rc, + libusb_version_internal.describe[0] ? " git:" : "", + libusb_version_internal.describe); + + usbi_mutex_init(&ctx->usb_devs_lock, NULL); + usbi_mutex_init(&ctx->open_devs_lock, NULL); + usbi_mutex_init(&ctx->hotplug_cbs_lock, NULL); + list_init(&ctx->usb_devs); + list_init(&ctx->open_devs); + list_init(&ctx->hotplug_cbs); + + if (usbi_backend->init) { + r = usbi_backend->init(ctx); + if (r) + goto err_free_ctx; + } + + r = usbi_io_init(ctx); + if (r < 0) { + if (usbi_backend->exit) + usbi_backend->exit(); + goto err_destroy_mutex; + } + + if (context) { + *context = ctx; + } else if (!usbi_default_context) { + usbi_dbg("created default context"); + usbi_default_context = ctx; + default_context_refcnt++; + } + usbi_mutex_static_unlock(&default_context_lock); + + usbi_mutex_static_lock(&active_contexts_lock); + if (first_init) { + first_init = 0; + list_init (&active_contexts_list); + } + + list_add (&ctx->list, &active_contexts_list); + usbi_mutex_static_unlock(&active_contexts_lock); + + return 0; + +err_destroy_mutex: + usbi_mutex_destroy(&ctx->open_devs_lock); + usbi_mutex_destroy(&ctx->usb_devs_lock); +err_free_ctx: + usbi_mutex_lock(&ctx->usb_devs_lock); + list_for_each_entry_safe(dev, next, &ctx->usb_devs, list, struct libusb_device) { + list_del(&dev->list); + libusb_unref_device(dev); + } + usbi_mutex_unlock(&ctx->usb_devs_lock); + free(ctx); +err_unlock: + usbi_mutex_static_unlock(&default_context_lock); + return r; +} + +/** \ingroup lib + * Deinitialize libusb. Should be called after closing all open devices and + * before your application terminates. + * \param ctx the context to deinitialize, or NULL for the default context + */ +void API_EXPORTED libusb_exit(struct libusb_context *ctx) +{ + struct libusb_device *dev, *next; + + usbi_dbg(""); + USBI_GET_CONTEXT(ctx); + + /* if working with default context, only actually do the deinitialization + * if we're the last user */ + if (ctx == usbi_default_context) { + usbi_mutex_static_lock(&default_context_lock); + if (--default_context_refcnt > 0) { + usbi_dbg("not destroying default context"); + usbi_mutex_static_unlock(&default_context_lock); + return; + } + usbi_dbg("destroying default context"); + usbi_default_context = NULL; + usbi_mutex_static_unlock(&default_context_lock); + } + + usbi_mutex_static_lock(&active_contexts_lock); + list_del (&ctx->list); + usbi_mutex_static_unlock(&active_contexts_lock); + + usbi_hotplug_deregister_all(ctx); + + usbi_mutex_lock(&ctx->usb_devs_lock); + list_for_each_entry_safe(dev, next, &ctx->usb_devs, list, struct libusb_device) { + list_del(&dev->list); + libusb_unref_device(dev); + } + usbi_mutex_unlock(&ctx->usb_devs_lock); + + /* a little sanity check. doesn't bother with open_devs locking because + * unless there is an application bug, nobody will be accessing this. */ + if (!list_empty(&ctx->open_devs)) + usbi_warn(ctx, "application left some devices open"); + + usbi_io_exit(ctx); + if (usbi_backend->exit) + usbi_backend->exit(); + + usbi_mutex_destroy(&ctx->open_devs_lock); + usbi_mutex_destroy(&ctx->usb_devs_lock); + usbi_mutex_destroy(&ctx->hotplug_cbs_lock); + free(ctx); +} + +/** \ingroup misc + * Check at runtime if the loaded library has a given capability. + * + * \param capability the \ref libusb_capability to check for + * \returns 1 if the running library has the capability, 0 otherwise + */ +int API_EXPORTED libusb_has_capability(uint32_t capability) +{ + enum libusb_capability cap = capability; + switch (cap) { + case LIBUSB_CAP_HAS_CAPABILITY: + return 1; + case LIBUSB_CAP_HAS_HOTPLUG: + return !(usbi_backend->get_device_list); + } + return 0; +} + +/* this is defined in libusbi.h if needed */ +#ifdef LIBUSB_GETTIMEOFDAY_WIN32 +/* + * gettimeofday + * Implementation according to: + * The Open Group Base Specifications Issue 6 + * IEEE Std 1003.1, 2004 Edition + */ + +/* + * THIS SOFTWARE IS NOT COPYRIGHTED + * + * This source code is offered for use in the public domain. You may + * use, modify or distribute it freely. + * + * This code is distributed in the hope that it will be useful but + * WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESS OR IMPLIED ARE HEREBY + * DISCLAIMED. This includes but is not limited to warranties of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * Contributed by: + * Danny Smith + */ + +/* Offset between 1/1/1601 and 1/1/1970 in 100 nanosec units */ +#define _W32_FT_OFFSET (116444736000000000) + +int usbi_gettimeofday(struct timeval *tp, void *tzp) + { + union { + unsigned __int64 ns100; /*time since 1 Jan 1601 in 100ns units */ + FILETIME ft; + } _now; + + if(tp) + { + GetSystemTimeAsFileTime (&_now.ft); + tp->tv_usec=(long)((_now.ns100 / 10) % 1000000 ); + tp->tv_sec= (long)((_now.ns100 - _W32_FT_OFFSET) / 10000000); + } + /* Always return 0 as per Open Group Base Specifications Issue 6. + Do not set errno on error. */ + return 0; +} +#endif + +void usbi_log_v(struct libusb_context *ctx, enum usbi_log_level level, + const char *function, const char *format, va_list args) +{ + FILE *stream = stdout; + const char *prefix; + struct timeval now; + static struct timeval first = { 0, 0 }; + +#ifndef ENABLE_DEBUG_LOGGING + USBI_GET_CONTEXT(ctx); + if (!ctx) + return; + if (!ctx->debug) + return; + if (level == LOG_LEVEL_WARNING && ctx->debug < 2) + return; + if (level == LOG_LEVEL_INFO && ctx->debug < 3) + return; +#endif + + usbi_gettimeofday(&now, NULL); + if (!first.tv_sec) { + first.tv_sec = now.tv_sec; + first.tv_usec = now.tv_usec; + } + if (now.tv_usec < first.tv_usec) { + now.tv_sec--; + now.tv_usec += 1000000; + } + now.tv_sec -= first.tv_sec; + now.tv_usec -= first.tv_usec; + + switch (level) { + case LOG_LEVEL_INFO: + prefix = "info"; + break; + case LOG_LEVEL_WARNING: + stream = stderr; + prefix = "warning"; + break; + case LOG_LEVEL_ERROR: + stream = stderr; + prefix = "error"; + break; + case LOG_LEVEL_DEBUG: + stream = stderr; + prefix = "debug"; + break; + default: + stream = stderr; + prefix = "unknown"; + break; + } + + fprintf(stream, "libusb: %d.%06d %s [%s] ", + (int)now.tv_sec, (int)now.tv_usec, prefix, function); + + vfprintf(stream, format, args); + + fprintf(stream, "\n"); +} + +void usbi_log(struct libusb_context *ctx, enum usbi_log_level level, + const char *function, const char *format, ...) +{ + va_list args; + + va_start (args, format); + usbi_log_v(ctx, level, function, format, args); + va_end (args); +} + +/** \ingroup misc + * Returns a constant NULL-terminated string with the ASCII name of a libusb + * error code. The caller must not free() the returned string. + * + * \param error_code The \ref libusb_error code to return the name of. + * \returns The error name, or the string **UNKNOWN** if the value of + * error_code is not a known error code. + */ +DEFAULT_VISIBILITY const char * LIBUSB_CALL libusb_error_name(int error_code) +{ + enum libusb_error error = error_code; + switch (error) { + case LIBUSB_SUCCESS: + return "LIBUSB_SUCCESS"; + case LIBUSB_ERROR_IO: + return "LIBUSB_ERROR_IO"; + case LIBUSB_ERROR_INVALID_PARAM: + return "LIBUSB_ERROR_INVALID_PARAM"; + case LIBUSB_ERROR_ACCESS: + return "LIBUSB_ERROR_ACCESS"; + case LIBUSB_ERROR_NO_DEVICE: + return "LIBUSB_ERROR_NO_DEVICE"; + case LIBUSB_ERROR_NOT_FOUND: + return "LIBUSB_ERROR_NOT_FOUND"; + case LIBUSB_ERROR_BUSY: + return "LIBUSB_ERROR_BUSY"; + case LIBUSB_ERROR_TIMEOUT: + return "LIBUSB_ERROR_TIMEOUT"; + case LIBUSB_ERROR_OVERFLOW: + return "LIBUSB_ERROR_OVERFLOW"; + case LIBUSB_ERROR_PIPE: + return "LIBUSB_ERROR_PIPE"; + case LIBUSB_ERROR_INTERRUPTED: + return "LIBUSB_ERROR_INTERRUPTED"; + case LIBUSB_ERROR_NO_MEM: + return "LIBUSB_ERROR_NO_MEM"; + case LIBUSB_ERROR_NOT_SUPPORTED: + return "LIBUSB_ERROR_NOT_SUPPORTED"; + case LIBUSB_ERROR_OTHER: + return "LIBUSB_ERROR_OTHER"; + } + return "**UNKNOWN**"; +} + +/** \ingroup misc + * Returns a constant string with an English short description of the given + * error code. The caller should never free() the returned pointer since it + * points to a constant string. + * The returned string is encoded in ASCII form and always starts with a capital + * letter and ends without any dot. + * \param errcode the error code whose description is desired + * \returns a short description of the error code in English + */ +API_EXPORTED const char* libusb_strerror(enum libusb_error errcode) +{ + switch (errcode) { + case LIBUSB_SUCCESS: + return "Success"; + case LIBUSB_ERROR_IO: + return "Input/output error"; + case LIBUSB_ERROR_INVALID_PARAM: + return "Invalid parameter"; + case LIBUSB_ERROR_ACCESS: + return "Access denied (insufficient permissions)"; + case LIBUSB_ERROR_NO_DEVICE: + return "No such device (it may have been disconnected)"; + case LIBUSB_ERROR_NOT_FOUND: + return "Entity not found"; + case LIBUSB_ERROR_BUSY: + return "Resource busy"; + case LIBUSB_ERROR_TIMEOUT: + return "Operation timed out"; + case LIBUSB_ERROR_OVERFLOW: + return "Overflow"; + case LIBUSB_ERROR_PIPE: + return "Pipe error"; + case LIBUSB_ERROR_INTERRUPTED: + return "System call interrupted (perhaps due to signal)"; + case LIBUSB_ERROR_NO_MEM: + return "Insufficient memory"; + case LIBUSB_ERROR_NOT_SUPPORTED: + return "Operation not supported or unimplemented on this platform"; + case LIBUSB_ERROR_OTHER: + return "Other error"; + } + + return "Unknown error"; +} + +/** \ingroup misc + * Returns a pointer to const struct libusb_version with the version + * (major, minor, micro, rc, and nano) of the running library. + */ +DEFAULT_VISIBILITY +const struct libusb_version * LIBUSB_CALL libusb_get_version(void) +{ + return &libusb_version_internal; +} diff --git a/compat/libusb-1.0/libusb/descriptor.c b/compat/libusb-1.0/libusb/descriptor.c new file mode 100644 index 0000000..4f81dab --- /dev/null +++ b/compat/libusb-1.0/libusb/descriptor.c @@ -0,0 +1,872 @@ +/* + * USB descriptor handling functions for libusb + * Copyright (C) 2007 Daniel Drake + * Copyright (c) 2001 Johannes Erdfelt + * Copyright (c) 2012-2013 Nathan Hjelm + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include + +#include "libusbi.h" + +#define DESC_HEADER_LENGTH 2 +#define DEVICE_DESC_LENGTH 18 +#define CONFIG_DESC_LENGTH 9 +#define INTERFACE_DESC_LENGTH 9 +#define ENDPOINT_DESC_LENGTH 7 +#define ENDPOINT_AUDIO_DESC_LENGTH 9 + +/** @defgroup desc USB descriptors + * This page details how to examine the various standard USB descriptors + * for detected devices + */ + +/* set host_endian if the w values are already in host endian format, + * as opposed to bus endian. */ +int usbi_parse_descriptor(const unsigned char *source, const char *descriptor, + void *dest, int host_endian) +{ + const unsigned char *sp = source; + unsigned char *dp = dest; + uint16_t w; + const char *cp; + uint32_t d; + + for (cp = descriptor; *cp; cp++) { + switch (*cp) { + case 'b': /* 8-bit byte */ + *dp++ = *sp++; + break; + case 'w': /* 16-bit word, convert from little endian to CPU */ + dp += ((uintptr_t)dp & 1); /* Align to word boundary */ + + if (host_endian) { + memcpy(dp, sp, 2); + } else { + w = (sp[1] << 8) | sp[0]; + *((uint16_t *)dp) = w; + } + sp += 2; + dp += 2; + break; + /* 32-bit word, convert from little endian to CPU */ + case 'd': + /* Align to word boundary */ + dp += ((unsigned long)dp & 1); + + if (host_endian) { + memcpy(dp, sp, 4); + } else { + d = (sp[3] << 24) | (sp[2] << 16) | + (sp[1] << 8) | sp[0]; + *((uint32_t *)dp) = d; + } + sp += 4; + dp += 4; + break; + } + } + + return (int) (sp - source); +} + +static void clear_endpoint(struct libusb_endpoint_descriptor *endpoint) +{ + if (endpoint->extra) + free((unsigned char *) endpoint->extra); +} + +static int parse_endpoint(struct libusb_context *ctx, + struct libusb_endpoint_descriptor *endpoint, unsigned char *buffer, + int size, int host_endian) +{ + struct usb_descriptor_header header; + unsigned char *extra; + unsigned char *begin; + int parsed = 0; + int len; + + usbi_parse_descriptor(buffer, "bb", &header, 0); + + /* Everything should be fine being passed into here, but we sanity */ + /* check JIC */ + if (header.bLength > size) { + usbi_err(ctx, "ran out of descriptors parsing"); + return -1; + } + + if (header.bDescriptorType != LIBUSB_DT_ENDPOINT) { + usbi_err(ctx, "unexpected descriptor %x (expected %x)", + header.bDescriptorType, LIBUSB_DT_ENDPOINT); + return parsed; + } + + if (header.bLength >= ENDPOINT_AUDIO_DESC_LENGTH) + usbi_parse_descriptor(buffer, "bbbbwbbb", endpoint, host_endian); + else if (header.bLength >= ENDPOINT_DESC_LENGTH) + usbi_parse_descriptor(buffer, "bbbbwb", endpoint, host_endian); + + buffer += header.bLength; + size -= header.bLength; + parsed += header.bLength; + + /* Skip over the rest of the Class Specific or Vendor Specific */ + /* descriptors */ + begin = buffer; + while (size >= DESC_HEADER_LENGTH) { + usbi_parse_descriptor(buffer, "bb", &header, 0); + + if (header.bLength < 2) { + usbi_err(ctx, "invalid descriptor length %d", header.bLength); + return -1; + } + + /* If we find another "proper" descriptor then we're done */ + if ((header.bDescriptorType == LIBUSB_DT_ENDPOINT) || + (header.bDescriptorType == LIBUSB_DT_INTERFACE) || + (header.bDescriptorType == LIBUSB_DT_CONFIG) || + (header.bDescriptorType == LIBUSB_DT_DEVICE)) + break; + + usbi_dbg("skipping descriptor %x", header.bDescriptorType); + buffer += header.bLength; + size -= header.bLength; + parsed += header.bLength; + } + + /* Copy any unknown descriptors into a storage area for drivers */ + /* to later parse */ + len = (int)(buffer - begin); + if (!len) { + endpoint->extra = NULL; + endpoint->extra_length = 0; + return parsed; + } + + extra = malloc(len); + endpoint->extra = extra; + if (!extra) { + endpoint->extra_length = 0; + return LIBUSB_ERROR_NO_MEM; + } + + memcpy(extra, begin, len); + endpoint->extra_length = len; + + return parsed; +} + +static void clear_interface(struct libusb_interface *usb_interface) +{ + int i; + int j; + + if (usb_interface->altsetting) { + for (i = 0; i < usb_interface->num_altsetting; i++) { + struct libusb_interface_descriptor *ifp = + (struct libusb_interface_descriptor *) + usb_interface->altsetting + i; + if (ifp->extra) + free((void *) ifp->extra); + if (ifp->endpoint) { + for (j = 0; j < ifp->bNumEndpoints; j++) + clear_endpoint((struct libusb_endpoint_descriptor *) + ifp->endpoint + j); + free((void *) ifp->endpoint); + } + } + free((void *) usb_interface->altsetting); + usb_interface->altsetting = NULL; + } + +} + +static int parse_interface(libusb_context *ctx, + struct libusb_interface *usb_interface, unsigned char *buffer, int size, + int host_endian) +{ + int i; + int len; + int r; + int parsed = 0; + size_t tmp; + struct usb_descriptor_header header; + struct libusb_interface_descriptor *ifp; + unsigned char *begin; + + usb_interface->num_altsetting = 0; + + while (size >= INTERFACE_DESC_LENGTH) { + struct libusb_interface_descriptor *altsetting = + (struct libusb_interface_descriptor *) usb_interface->altsetting; + altsetting = realloc(altsetting, + sizeof(struct libusb_interface_descriptor) * + (usb_interface->num_altsetting + 1)); + if (!altsetting) { + r = LIBUSB_ERROR_NO_MEM; + goto err; + } + usb_interface->altsetting = altsetting; + + ifp = altsetting + usb_interface->num_altsetting; + usb_interface->num_altsetting++; + usbi_parse_descriptor(buffer, "bbbbbbbbb", ifp, 0); + ifp->extra = NULL; + ifp->extra_length = 0; + ifp->endpoint = NULL; + + /* Skip over the interface */ + buffer += ifp->bLength; + parsed += ifp->bLength; + size -= ifp->bLength; + + begin = buffer; + + /* Skip over any interface, class or vendor descriptors */ + while (size >= DESC_HEADER_LENGTH) { + usbi_parse_descriptor(buffer, "bb", &header, 0); + if (header.bLength < 2) { + usbi_err(ctx, "invalid descriptor of length %d", + header.bLength); + r = LIBUSB_ERROR_IO; + goto err; + } + + /* If we find another "proper" descriptor then we're done */ + if ((header.bDescriptorType == LIBUSB_DT_INTERFACE) || + (header.bDescriptorType == LIBUSB_DT_ENDPOINT) || + (header.bDescriptorType == LIBUSB_DT_CONFIG) || + (header.bDescriptorType == LIBUSB_DT_DEVICE) || + (header.bDescriptorType == + LIBUSB_DT_SS_ENDPOINT_COMPANION)) + break; + + buffer += header.bLength; + parsed += header.bLength; + size -= header.bLength; + } + + /* Copy any unknown descriptors into a storage area for */ + /* drivers to later parse */ + len = (int)(buffer - begin); + if (len) { + ifp->extra = malloc(len); + if (!ifp->extra) { + r = LIBUSB_ERROR_NO_MEM; + goto err; + } + memcpy((unsigned char *) ifp->extra, begin, len); + ifp->extra_length = len; + } + + /* Did we hit an unexpected descriptor? */ + if (size >= DESC_HEADER_LENGTH) { + usbi_parse_descriptor(buffer, "bb", &header, 0); + if ((header.bDescriptorType == LIBUSB_DT_CONFIG) || + (header.bDescriptorType == LIBUSB_DT_DEVICE)) { + return parsed; + } + } + + if (ifp->bNumEndpoints > USB_MAXENDPOINTS) { + usbi_err(ctx, "too many endpoints (%d)", ifp->bNumEndpoints); + r = LIBUSB_ERROR_IO; + goto err; + } + + if (ifp->bNumEndpoints > 0) { + struct libusb_endpoint_descriptor *endpoint; + tmp = ifp->bNumEndpoints * sizeof(struct libusb_endpoint_descriptor); + endpoint = malloc(tmp); + ifp->endpoint = endpoint; + if (!endpoint) { + r = LIBUSB_ERROR_NO_MEM; + goto err; + } + + memset(endpoint, 0, tmp); + for (i = 0; i < ifp->bNumEndpoints; i++) { + usbi_parse_descriptor(buffer, "bb", &header, 0); + + if (header.bLength > size) { + usbi_err(ctx, "ran out of descriptors parsing"); + r = LIBUSB_ERROR_IO; + goto err; + } + + r = parse_endpoint(ctx, endpoint + i, buffer, size, + host_endian); + if (r < 0) + goto err; + + buffer += r; + parsed += r; + size -= r; + } + } + + /* We check to see if it's an alternate to this one */ + ifp = (struct libusb_interface_descriptor *) buffer; + if (size < LIBUSB_DT_INTERFACE_SIZE || + ifp->bDescriptorType != LIBUSB_DT_INTERFACE || + !ifp->bAlternateSetting) + return parsed; + } + + return parsed; +err: + clear_interface(usb_interface); + return r; +} + +static void clear_configuration(struct libusb_config_descriptor *config) +{ + if (config->interface) { + int i; + for (i = 0; i < config->bNumInterfaces; i++) + clear_interface((struct libusb_interface *) + config->interface + i); + free((void *) config->interface); + } + if (config->extra) + free((void *) config->extra); +} + +static int parse_configuration(struct libusb_context *ctx, + struct libusb_config_descriptor *config, unsigned char *buffer, + int host_endian) +{ + int i; + int r; + int size; + size_t tmp; + struct usb_descriptor_header header; + struct libusb_interface *usb_interface; + + usbi_parse_descriptor(buffer, "bbwbbbbb", config, host_endian); + size = config->wTotalLength; + + if (config->bNumInterfaces > USB_MAXINTERFACES) { + usbi_err(ctx, "too many interfaces (%d)", config->bNumInterfaces); + return LIBUSB_ERROR_IO; + } + + tmp = config->bNumInterfaces * sizeof(struct libusb_interface); + usb_interface = malloc(tmp); + config->interface = usb_interface; + if (!config->interface) + return LIBUSB_ERROR_NO_MEM; + + memset(usb_interface, 0, tmp); + buffer += config->bLength; + size -= config->bLength; + + config->extra = NULL; + config->extra_length = 0; + + for (i = 0; i < config->bNumInterfaces; i++) { + int len; + unsigned char *begin; + + /* Skip over the rest of the Class Specific or Vendor */ + /* Specific descriptors */ + begin = buffer; + while (size >= DESC_HEADER_LENGTH) { + usbi_parse_descriptor(buffer, "bb", &header, 0); + + if ((header.bLength > size) || + (header.bLength < DESC_HEADER_LENGTH)) { + usbi_err(ctx, "invalid descriptor length of %d", + header.bLength); + r = LIBUSB_ERROR_IO; + goto err; + } + + /* If we find another "proper" descriptor then we're done */ + if ((header.bDescriptorType == LIBUSB_DT_ENDPOINT) || + (header.bDescriptorType == LIBUSB_DT_INTERFACE) || + (header.bDescriptorType == LIBUSB_DT_CONFIG) || + (header.bDescriptorType == LIBUSB_DT_DEVICE) || + (header.bDescriptorType == + LIBUSB_DT_SS_ENDPOINT_COMPANION)) + break; + + usbi_dbg("skipping descriptor 0x%x\n", header.bDescriptorType); + buffer += header.bLength; + size -= header.bLength; + } + + /* Copy any unknown descriptors into a storage area for */ + /* drivers to later parse */ + len = (int)(buffer - begin); + if (len) { + /* FIXME: We should realloc and append here */ + if (!config->extra_length) { + config->extra = malloc(len); + if (!config->extra) { + r = LIBUSB_ERROR_NO_MEM; + goto err; + } + + memcpy((unsigned char *) config->extra, begin, len); + config->extra_length = len; + } + } + + r = parse_interface(ctx, usb_interface + i, buffer, size, host_endian); + if (r < 0) + goto err; + + buffer += r; + size -= r; + } + + return size; + +err: + clear_configuration(config); + return r; +} + +int usbi_device_cache_descriptor(libusb_device *dev) +{ + int r, host_endian; + + r = usbi_backend->get_device_descriptor(dev, (unsigned char *) &dev->device_descriptor, + &host_endian); + if (r < 0) + return r; + + if (!host_endian) { + dev->device_descriptor.bcdUSB = libusb_le16_to_cpu(dev->device_descriptor.bcdUSB); + dev->device_descriptor.idVendor = libusb_le16_to_cpu(dev->device_descriptor.idVendor); + dev->device_descriptor.idProduct = libusb_le16_to_cpu(dev->device_descriptor.idProduct); + dev->device_descriptor.bcdDevice = libusb_le16_to_cpu(dev->device_descriptor.bcdDevice); + } + + return LIBUSB_SUCCESS; +} + +/** \ingroup desc + * Get the USB device descriptor for a given device. + * + * This is a non-blocking function; the device descriptor is cached in memory. + * + * \param dev the device + * \param desc output location for the descriptor data + * \returns 0 on success or a LIBUSB_ERROR code on failure + */ +int API_EXPORTED libusb_get_device_descriptor(libusb_device *dev, + struct libusb_device_descriptor *desc) +{ + usbi_dbg(""); + memcpy((unsigned char *) desc, (unsigned char *) &dev->device_descriptor, + sizeof (dev->device_descriptor)); + return 0; +} + +/** \ingroup desc + * Get the USB configuration descriptor for the currently active configuration. + * This is a non-blocking function which does not involve any requests being + * sent to the device. + * + * \param dev a device + * \param config output location for the USB configuration descriptor. Only + * valid if 0 was returned. Must be freed with libusb_free_config_descriptor() + * after use. + * \returns 0 on success + * \returns LIBUSB_ERROR_NOT_FOUND if the device is in unconfigured state + * \returns another LIBUSB_ERROR code on error + * \see libusb_get_config_descriptor + */ +int API_EXPORTED libusb_get_active_config_descriptor(libusb_device *dev, + struct libusb_config_descriptor **config) +{ + struct libusb_config_descriptor *_config = malloc(sizeof(*_config)); + unsigned char tmp[8]; + unsigned char *buf = NULL; + int host_endian = 0; + int r; + + usbi_dbg(""); + if (!_config) + return LIBUSB_ERROR_NO_MEM; + + r = usbi_backend->get_active_config_descriptor(dev, tmp, sizeof(tmp), + &host_endian); + if (r < 0) + goto err; + + usbi_parse_descriptor(tmp, "bbw", _config, host_endian); + buf = malloc(_config->wTotalLength); + if (!buf) { + r = LIBUSB_ERROR_NO_MEM; + goto err; + } + + r = usbi_backend->get_active_config_descriptor(dev, buf, + _config->wTotalLength, &host_endian); + if (r < 0) + goto err; + + r = parse_configuration(dev->ctx, _config, buf, host_endian); + if (r < 0) { + usbi_err(dev->ctx, "parse_configuration failed with error %d", r); + goto err; + } else if (r > 0) { + usbi_warn(dev->ctx, "descriptor data still left"); + } + + free(buf); + *config = _config; + return 0; + +err: + free(_config); + if (buf) + free(buf); + return r; +} + +/** \ingroup desc + * Get a USB configuration descriptor based on its index. + * This is a non-blocking function which does not involve any requests being + * sent to the device. + * + * \param dev a device + * \param config_index the index of the configuration you wish to retrieve + * \param config output location for the USB configuration descriptor. Only + * valid if 0 was returned. Must be freed with libusb_free_config_descriptor() + * after use. + * \returns 0 on success + * \returns LIBUSB_ERROR_NOT_FOUND if the configuration does not exist + * \returns another LIBUSB_ERROR code on error + * \see libusb_get_active_config_descriptor() + * \see libusb_get_config_descriptor_by_value() + */ +int API_EXPORTED libusb_get_config_descriptor(libusb_device *dev, + uint8_t config_index, struct libusb_config_descriptor **config) +{ + struct libusb_config_descriptor *_config; + unsigned char tmp[8]; + unsigned char *buf = NULL; + int host_endian = 0; + int r; + + usbi_dbg("index %d", config_index); + if (config_index >= dev->num_configurations) + return LIBUSB_ERROR_NOT_FOUND; + + _config = malloc(sizeof(*_config)); + if (!_config) + return LIBUSB_ERROR_NO_MEM; + + r = usbi_backend->get_config_descriptor(dev, config_index, tmp, + sizeof(tmp), &host_endian); + if (r < 0) + goto err; + + usbi_parse_descriptor(tmp, "bbw", _config, host_endian); + buf = malloc(_config->wTotalLength); + if (!buf) { + r = LIBUSB_ERROR_NO_MEM; + goto err; + } + + host_endian = 0; + r = usbi_backend->get_config_descriptor(dev, config_index, buf, + _config->wTotalLength, &host_endian); + if (r < 0) + goto err; + + r = parse_configuration(dev->ctx, _config, buf, host_endian); + if (r < 0) { + usbi_err(dev->ctx, "parse_configuration failed with error %d", r); + goto err; + } else if (r > 0) { + usbi_warn(dev->ctx, "descriptor data still left"); + } + + free(buf); + *config = _config; + return 0; + +err: + free(_config); + if (buf) + free(buf); + return r; +} + +/* iterate through all configurations, returning the index of the configuration + * matching a specific bConfigurationValue in the idx output parameter, or -1 + * if the config was not found. + * returns 0 or a LIBUSB_ERROR code + */ +int usbi_get_config_index_by_value(struct libusb_device *dev, + uint8_t bConfigurationValue, int *idx) +{ + uint8_t i; + + usbi_dbg("value %d", bConfigurationValue); + for (i = 0; i < dev->num_configurations; i++) { + unsigned char tmp[6]; + int host_endian; + int r = usbi_backend->get_config_descriptor(dev, i, tmp, sizeof(tmp), + &host_endian); + if (r < 0) + return r; + if (tmp[5] == bConfigurationValue) { + *idx = i; + return 0; + } + } + + *idx = -1; + return 0; +} + +/** \ingroup desc + * Get a USB configuration descriptor with a specific bConfigurationValue. + * This is a non-blocking function which does not involve any requests being + * sent to the device. + * + * \param dev a device + * \param bConfigurationValue the bConfigurationValue of the configuration you + * wish to retrieve + * \param config output location for the USB configuration descriptor. Only + * valid if 0 was returned. Must be freed with libusb_free_config_descriptor() + * after use. + * \returns 0 on success + * \returns LIBUSB_ERROR_NOT_FOUND if the configuration does not exist + * \returns another LIBUSB_ERROR code on error + * \see libusb_get_active_config_descriptor() + * \see libusb_get_config_descriptor() + */ +int API_EXPORTED libusb_get_config_descriptor_by_value(libusb_device *dev, + uint8_t bConfigurationValue, struct libusb_config_descriptor **config) +{ + int idx; + int r = usbi_get_config_index_by_value(dev, bConfigurationValue, &idx); + if (r < 0) + return r; + else if (idx == -1) + return LIBUSB_ERROR_NOT_FOUND; + else + return libusb_get_config_descriptor(dev, (uint8_t) idx, config); +} + +/** \ingroup desc + * Free a configuration descriptor obtained from + * libusb_get_active_config_descriptor() or libusb_get_config_descriptor(). + * It is safe to call this function with a NULL config parameter, in which + * case the function simply returns. + * + * \param config the configuration descriptor to free + */ +void API_EXPORTED libusb_free_config_descriptor( + struct libusb_config_descriptor *config) +{ + if (!config) + return; + + clear_configuration(config); + free(config); +} + +/** \ingroup desc + * Retrieve a string descriptor in C style ASCII. + * + * Wrapper around libusb_get_string_descriptor(). Uses the first language + * supported by the device. + * + * \param dev a device handle + * \param desc_index the index of the descriptor to retrieve + * \param data output buffer for ASCII string descriptor + * \param length size of data buffer + * \returns number of bytes returned in data, or LIBUSB_ERROR code on failure + */ +int API_EXPORTED libusb_get_string_descriptor_ascii(libusb_device_handle *dev, + uint8_t desc_index, unsigned char *data, int length) +{ + unsigned char tbuf[255]; /* Some devices choke on size > 255 */ + int r, si, di; + uint16_t langid; + + /* Asking for the zero'th index is special - it returns a string + * descriptor that contains all the language IDs supported by the + * device. Typically there aren't many - often only one. Language + * IDs are 16 bit numbers, and they start at the third byte in the + * descriptor. There's also no point in trying to read descriptor 0 + * with this function. See USB 2.0 specification section 9.6.7 for + * more information. + */ + + if (desc_index == 0) + return LIBUSB_ERROR_INVALID_PARAM; + + r = libusb_get_string_descriptor(dev, 0, 0, tbuf, sizeof(tbuf)); + if (r < 0) + return r; + + if (r < 4) + return LIBUSB_ERROR_IO; + + langid = tbuf[2] | (tbuf[3] << 8); + + r = libusb_get_string_descriptor(dev, desc_index, langid, tbuf, + sizeof(tbuf)); + if (r < 0) + return r; + + if (tbuf[1] != LIBUSB_DT_STRING) + return LIBUSB_ERROR_IO; + + if (tbuf[0] > r) + return LIBUSB_ERROR_IO; + + for (di = 0, si = 2; si < tbuf[0]; si += 2) { + if (di >= (length - 1)) + break; + + if (tbuf[si + 1]) /* high byte */ + data[di++] = '?'; + else + data[di++] = tbuf[si]; + } + + data[di] = 0; + return di; +} + +int API_EXPORTED libusb_parse_ss_endpoint_comp(const void *buf, int len, + struct libusb_ss_endpoint_companion_descriptor **ep_comp) +{ + struct libusb_ss_endpoint_companion_descriptor *ep_comp_desc; + struct usb_descriptor_header header; + + usbi_parse_descriptor(buf, "bb", &header, 0); + + /* Everything should be fine being passed into here, but we sanity */ + /* check JIC */ + if (header.bLength > len) { + usbi_err(NULL, "ran out of descriptors parsing"); + return LIBUSB_ERROR_NO_MEM; + } + + if (header.bDescriptorType != LIBUSB_DT_SS_ENDPOINT_COMPANION) { + usbi_err(NULL, "unexpected descriptor %x (expected %x)", + header.bDescriptorType, LIBUSB_DT_SS_ENDPOINT_COMPANION); + return LIBUSB_ERROR_INVALID_PARAM; + } + + ep_comp_desc = calloc(1, sizeof (*ep_comp_desc)); + if (!ep_comp_desc) { + return LIBUSB_ERROR_NO_MEM; + } + + if (header.bLength >= LIBUSB_DT_SS_ENDPOINT_COMPANION_SIZE) + usbi_parse_descriptor(buf, "bbbbw", ep_comp_desc, 0); + + *ep_comp = ep_comp_desc; + + return LIBUSB_SUCCESS; +} + +void API_EXPORTED libusb_free_ss_endpoint_comp(struct libusb_ss_endpoint_companion_descriptor *ep_comp) +{ + assert(ep_comp); + free(ep_comp); +} + +int API_EXPORTED libusb_parse_bos_descriptor(const void *buf, int len, + struct libusb_bos_descriptor **bos) +{ + const unsigned char *buffer = (const unsigned char *) buf; + struct libusb_bos_descriptor *bos_desc; + int i; + + len = len; + bos_desc = calloc (1, sizeof (*bos_desc)); + if (!bos_desc) { + return LIBUSB_ERROR_NO_MEM; + } + + usbi_parse_descriptor(buffer, "bbwb", bos_desc, 0); + buffer += LIBUSB_DT_BOS_SIZE; + + /* Get the device capability descriptors */ + for (i = 0; i < bos_desc->bNumDeviceCaps; ++i) { + if (buffer[2] == LIBUSB_USB_CAP_TYPE_EXT) { + if (!bos_desc->usb_2_0_ext_cap) { + bos_desc->usb_2_0_ext_cap = + (struct libusb_usb_2_0_device_capability_descriptor *) + malloc(sizeof(*bos_desc->usb_2_0_ext_cap)); + usbi_parse_descriptor(buffer, "bbbd", + bos_desc->usb_2_0_ext_cap, 0); + } else + usbi_warn(NULL, + "usb_2_0_ext_cap was already allocated"); + + /* move to the next device capability descriptor */ + buffer += LIBUSB_USB_2_0_EXTENSION_DEVICE_CAPABILITY_SIZE; + } else if (buffer[2] == LIBUSB_SS_USB_CAP_TYPE) { + if (!bos_desc->ss_usb_cap) { + bos_desc->ss_usb_cap = + (struct libusb_ss_usb_device_capability_descriptor *) + malloc(sizeof(*bos_desc->ss_usb_cap)); + usbi_parse_descriptor(buffer, "bbbbwbbw", + bos_desc->ss_usb_cap, 0); + } else + usbi_warn(NULL, + "ss_usb_cap was already allocated"); + + /* move to the next device capability descriptor */ + buffer += LIBUSB_SS_USB_DEVICE_CAPABILITY_SIZE; + } else { + usbi_info(NULL, "wireless/container_id capability " + "descriptor"); + + /* move to the next device capability descriptor */ + buffer += buffer[0]; + } + } + + *bos = bos_desc; + + return LIBUSB_SUCCESS; +} + +void API_EXPORTED libusb_free_bos_descriptor(struct libusb_bos_descriptor *bos) +{ + assert(bos); + + if (bos->usb_2_0_ext_cap) { + free(bos->usb_2_0_ext_cap); + } + + if (bos->ss_usb_cap) { + free(bos->ss_usb_cap); + } + + free(bos); +} diff --git a/compat/libusb-1.0/libusb/hotplug.c b/compat/libusb-1.0/libusb/hotplug.c new file mode 100644 index 0000000..d00d0b9 --- /dev/null +++ b/compat/libusb-1.0/libusb/hotplug.c @@ -0,0 +1,298 @@ +/* -*- Mode: C; indent-tabs-mode:nil ; c-basic-offset:8 -*- */ +/* + * Hotplug functions for libusb + * Copyright (C) 2012-2013 Nathan Hjelm + * Copyright (C) 2012-2013 Peter Stuge + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#include +#include +#include +#include +#include +#include + +#include "libusbi.h" +#include "hotplug.h" + +/** + * @defgroup hotplug Device hotplug event notification + * This page details how to use the libusb hotplug interface. + * + * \page hotplug Device hotplug event notification + * + * \section intro Introduction + * + * Releases of libusb 1.0 newer than 1.X have added support for hotplug + * events. This interface allows you to request notification for the + * arrival and departure of matching USB devices. + * + * To receive hotplug notification you register a callback by calling + * libusb_hotplug_register_callback(). This function will optionally return + * a handle that can be passed to libusb_hotplug_deregister_callback(). + * + * A callback function must return an int (0 or 1) indicating whether the callback is + * expecting additional events. Returning 0 will rearm the callback and 1 will cause + * the callback to be deregistered. + * + * Callbacks for a particulat context are automatically deregistered by libusb_exit(). + * + * As of 1.X there are two supported hotplug events: + * - LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED: A device has arrived and is ready to use + * - LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT: A device has left and is no longer available + * + * A hotplug event can listen for either or both of these events. + * + * Note: If you receive notification that a device has left and you have any + * a libusb_device_handles for the device it is up to you to call libusb_close() + * on each handle to free up any remaining resources associated with the device. + * Once a device has left any libusb_device_handle associated with the device + * are invalid and will remain so even if the device comes back. + * + * When handling a LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED event it is considered + * safe to call any libusb function that takes a libusb_device. On the other hand, + * when handling a LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT event the only safe function + * is libusb_get_device_descriptor(). + * + * The following code provides an example of the usage of the hotplug interface: +\code +static int count = 0; + +int hotplug_callback(struct libusb_context *ctx, struct libusb_device *dev, + libusb_hotplug_event event, void *user_data) { + static libusb_device_handle *handle = NULL; + struct libusb_device_descriptor desc; + int rc; + + (void)libusb_get_device_descriptor(dev, &desc); + + if (LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED == event) { + rc = libusb_open(dev, &handle); + if (LIBUSB_SUCCESS != rc) { + printf("Could not open USB device\n"); + } + } else if (LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT == event) { + if (handle) { + libusb_close(handle); + handle = NULL; + } + } else { + printf("Unhandled event %d\n", event); + } + count++; + + return 0; +} + +int main (void) { + libusb_hotplug_callback_handle handle; + int rc; + + libusb_init(NULL); + + rc = libusb_hotplug_register_callback(NULL, LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | + LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, 0, 0x045a, 0x5005, + LIBUSB_HOTPLUG_MATCH_ANY, hotplug_callback, NULL, + &handle); + if (LIBUSB_SUCCESS != rc) { + printf("Error creating a hotplug callback\n"); + libusb_exit(NULL); + return EXIT_FAILURE; + } + + while (count < 2) { + usleep(10000); + } + + libusb_hotplug_deregister_callback(handle); + libusb_exit(NULL); + + return 0; +} +\endcode + */ + +static int usbi_hotplug_match_cb (struct libusb_device *dev, libusb_hotplug_event event, + struct libusb_hotplug_callback *hotplug_cb) { + struct libusb_context *ctx = dev->ctx; + + /* Handle lazy deregistration of callback */ + if (hotplug_cb->needs_free) { + /* Free callback */ + return 1; + } + + if (!(hotplug_cb->events & event)) { + return 0; + } + + if (LIBUSB_HOTPLUG_MATCH_ANY != hotplug_cb->vendor_id && + hotplug_cb->vendor_id != dev->device_descriptor.idVendor) { + return 0; + } + + if (LIBUSB_HOTPLUG_MATCH_ANY != hotplug_cb->product_id && + hotplug_cb->product_id != dev->device_descriptor.idProduct) { + return 0; + } + + if (LIBUSB_HOTPLUG_MATCH_ANY != hotplug_cb->dev_class && + hotplug_cb->dev_class != dev->device_descriptor.bDeviceClass) { + return 0; + } + + return hotplug_cb->cb (ctx == usbi_default_context ? NULL : ctx, + dev, event, hotplug_cb->user_data); +} + +void usbi_hotplug_match(struct libusb_device *dev, libusb_hotplug_event event) { + struct libusb_hotplug_callback *hotplug_cb, *next; + struct libusb_context *ctx = dev->ctx; + + usbi_mutex_lock(&ctx->hotplug_cbs_lock); + + list_for_each_entry_safe(hotplug_cb, next, &ctx->hotplug_cbs, list, struct libusb_hotplug_callback) { + usbi_mutex_unlock(&ctx->hotplug_cbs_lock); + int ret = usbi_hotplug_match_cb (dev, event, hotplug_cb); + usbi_mutex_lock(&ctx->hotplug_cbs_lock); + + if (ret) { + list_del(&hotplug_cb->list); + free(hotplug_cb); + } + } + + usbi_mutex_unlock(&ctx->hotplug_cbs_lock); + + /* loop through and disconnect all open handles for this device */ + if (LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT == event) { + struct libusb_device_handle *handle; + + usbi_mutex_lock(&ctx->open_devs_lock); + list_for_each_entry(handle, &ctx->open_devs, list, struct libusb_device_handle) { + if (dev == handle->dev) { + usbi_handle_disconnect (handle); + } + } + usbi_mutex_unlock(&ctx->open_devs_lock); + } +} + +int API_EXPORTED libusb_hotplug_register_callback(libusb_context *ctx, + libusb_hotplug_event events, + libusb_hotplug_flag flags, + int vendor_id, int product_id, + int dev_class, + libusb_hotplug_callback_fn cb_fn, + void *user_data, libusb_hotplug_callback_handle *handle) { + libusb_hotplug_callback *new_callback; + static int handle_id = 1; + + /* check for hotplug support */ + if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) { + return LIBUSB_ERROR_NOT_SUPPORTED; + } + + /* check for sane values */ + if ((LIBUSB_HOTPLUG_MATCH_ANY != vendor_id && (~0xffff & vendor_id)) || + (LIBUSB_HOTPLUG_MATCH_ANY != product_id && (~0xffff & product_id)) || + (LIBUSB_HOTPLUG_MATCH_ANY != dev_class && (~0xff & dev_class)) || + !cb_fn) { + return LIBUSB_ERROR_INVALID_PARAM; + } + + USBI_GET_CONTEXT(ctx); + + new_callback = (libusb_hotplug_callback *)calloc(1, sizeof (*new_callback)); + if (!new_callback) { + return LIBUSB_ERROR_NO_MEM; + } + + new_callback->ctx = ctx; + new_callback->vendor_id = vendor_id; + new_callback->product_id = product_id; + new_callback->dev_class = dev_class; + new_callback->flags = flags; + new_callback->events = events; + new_callback->cb = cb_fn; + new_callback->user_data = user_data; + new_callback->needs_free = 0; + + usbi_mutex_lock(&ctx->hotplug_cbs_lock); + + /* protect the handle by the context hotplug lock. it doesn't matter if the same handle is used for different + contexts only that the handle is unique for this context */ + new_callback->handle = handle_id++; + + list_add(&new_callback->list, &ctx->hotplug_cbs); + + if (flags & LIBUSB_HOTPLUG_ENUMERATE) { + struct libusb_device *dev; + + usbi_mutex_lock(&ctx->usb_devs_lock); + + list_for_each_entry(dev, &ctx->usb_devs, list, struct libusb_device) { + (void) usbi_hotplug_match_cb (dev, LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED, new_callback); + } + + usbi_mutex_unlock(&ctx->usb_devs_lock); + } + + usbi_mutex_unlock(&ctx->hotplug_cbs_lock); + + if (handle) { + *handle = new_callback->handle; + } + + return LIBUSB_SUCCESS; +} + +void API_EXPORTED libusb_hotplug_deregister_callback (struct libusb_context *ctx, libusb_hotplug_callback_handle handle) { + struct libusb_hotplug_callback *hotplug_cb; + + /* check for hotplug support */ + if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) { + return; + } + + USBI_GET_CONTEXT(ctx); + + usbi_mutex_lock(&ctx->hotplug_cbs_lock); + list_for_each_entry(hotplug_cb, &ctx->hotplug_cbs, list, + struct libusb_hotplug_callback) { + if (handle == hotplug_cb->handle) { + /* Mark this callback for deregistration */ + hotplug_cb->needs_free = 1; + } + } + usbi_mutex_unlock(&ctx->hotplug_cbs_lock); +} + +void usbi_hotplug_deregister_all(struct libusb_context *ctx) { + struct libusb_hotplug_callback *hotplug_cb, *next; + + usbi_mutex_lock(&ctx->hotplug_cbs_lock); + list_for_each_entry_safe(hotplug_cb, next, &ctx->hotplug_cbs, list, + struct libusb_hotplug_callback) { + list_del(&hotplug_cb->list); + free(hotplug_cb); + } + + usbi_mutex_unlock(&ctx->hotplug_cbs_lock); +} diff --git a/compat/libusb-1.0/libusb/hotplug.h b/compat/libusb-1.0/libusb/hotplug.h new file mode 100644 index 0000000..64d4c74 --- /dev/null +++ b/compat/libusb-1.0/libusb/hotplug.h @@ -0,0 +1,77 @@ +/* -*- Mode: C; indent-tabs-mode:nil ; c-basic-offset:8 -*- */ +/* + * Hotplug support for libusb 1.0 + * Copyright (C) 2012 Nathan Hjelm + * Copyright (C) 2012 Peter Stuge + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined(USBI_HOTPLUG_H) +#define USBI_HOTPLUG_H + +/** \ingroup hotplug + * The hotplug callback structure. The user populates this structure with + * libusb_hotplug_prepare_callback() and then calls libusb_hotplug_register_callback() + * to receive notification of hotplug events. + */ +struct libusb_hotplug_callback { + /** Context this callback is associated with */ + struct libusb_context *ctx; + + /** Vendor ID to match or LIBUSB_HOTPLUG_MATCH_ANY */ + int vendor_id; + + /** Product ID to match or LIBUSB_HOTPLUG_MATCH_ANY */ + int product_id; + + /** Device class to match or LIBUSB_HOTPLUG_MATCH_ANY */ + int dev_class; + + /** Hotplug callback flags */ + libusb_hotplug_flag flags; + + /** Event(s) that will trigger this callback */ + libusb_hotplug_event events; + + /** Callback function to invoke for matching event/device */ + libusb_hotplug_callback_fn cb; + + /** Handle for this callback (used to match on deregister) */ + libusb_hotplug_callback_handle handle; + + /** User data that will be passed to the callback function */ + void *user_data; + + /** Callback is marked for deletion */ + int needs_free; + + /** List this callback is registered in (ctx->hotplug_cbs) */ + struct list_head list; +}; + +typedef struct libusb_hotplug_callback libusb_hotplug_callback; + +struct libusb_hotplug_message { + libusb_hotplug_event event; + struct libusb_device *device; +}; + +typedef struct libusb_hotplug_message libusb_hotplug_message; + +void usbi_hotplug_deregister_all(struct libusb_context *ctx); +void usbi_hotplug_match(struct libusb_device *dev, libusb_hotplug_event event); + +#endif diff --git a/compat/libusb-1.0/libusb/io.c b/compat/libusb-1.0/libusb/io.c new file mode 100644 index 0000000..55b17f1 --- /dev/null +++ b/compat/libusb-1.0/libusb/io.c @@ -0,0 +1,2503 @@ +/* + * I/O functions for libusb + * Copyright (C) 2007-2009 Daniel Drake + * Copyright (c) 2001 Johannes Erdfelt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_SYS_TIME_H +#include +#endif + +#ifdef USBI_TIMERFD_AVAILABLE +#include +#endif + +#include "libusbi.h" +#include "hotplug.h" + +/** + * \page io Synchronous and asynchronous device I/O + * + * \section intro Introduction + * + * If you're using libusb in your application, you're probably wanting to + * perform I/O with devices - you want to perform USB data transfers. + * + * libusb offers two separate interfaces for device I/O. This page aims to + * introduce the two in order to help you decide which one is more suitable + * for your application. You can also choose to use both interfaces in your + * application by considering each transfer on a case-by-case basis. + * + * Once you have read through the following discussion, you should consult the + * detailed API documentation pages for the details: + * - \ref syncio + * - \ref asyncio + * + * \section theory Transfers at a logical level + * + * At a logical level, USB transfers typically happen in two parts. For + * example, when reading data from a endpoint: + * -# A request for data is sent to the device + * -# Some time later, the incoming data is received by the host + * + * or when writing data to an endpoint: + * + * -# The data is sent to the device + * -# Some time later, the host receives acknowledgement from the device that + * the data has been transferred. + * + * There may be an indefinite delay between the two steps. Consider a + * fictional USB input device with a button that the user can press. In order + * to determine when the button is pressed, you would likely submit a request + * to read data on a bulk or interrupt endpoint and wait for data to arrive. + * Data will arrive when the button is pressed by the user, which is + * potentially hours later. + * + * libusb offers both a synchronous and an asynchronous interface to performing + * USB transfers. The main difference is that the synchronous interface + * combines both steps indicated above into a single function call, whereas + * the asynchronous interface separates them. + * + * \section sync The synchronous interface + * + * The synchronous I/O interface allows you to perform a USB transfer with + * a single function call. When the function call returns, the transfer has + * completed and you can parse the results. + * + * If you have used the libusb-0.1 before, this I/O style will seem familar to + * you. libusb-0.1 only offered a synchronous interface. + * + * In our input device example, to read button presses you might write code + * in the following style: +\code +unsigned char data[4]; +int actual_length; +int r = libusb_bulk_transfer(handle, LIBUSB_ENDPOINT_IN, data, sizeof(data), &actual_length, 0); +if (r == 0 && actual_length == sizeof(data)) { + // results of the transaction can now be found in the data buffer + // parse them here and report button press +} else { + error(); +} +\endcode + * + * The main advantage of this model is simplicity: you did everything with + * a single simple function call. + * + * However, this interface has its limitations. Your application will sleep + * inside libusb_bulk_transfer() until the transaction has completed. If it + * takes the user 3 hours to press the button, your application will be + * sleeping for that long. Execution will be tied up inside the library - + * the entire thread will be useless for that duration. + * + * Another issue is that by tieing up the thread with that single transaction + * there is no possibility of performing I/O with multiple endpoints and/or + * multiple devices simultaneously, unless you resort to creating one thread + * per transaction. + * + * Additionally, there is no opportunity to cancel the transfer after the + * request has been submitted. + * + * For details on how to use the synchronous API, see the + * \ref syncio "synchronous I/O API documentation" pages. + * + * \section async The asynchronous interface + * + * Asynchronous I/O is the most significant new feature in libusb-1.0. + * Although it is a more complex interface, it solves all the issues detailed + * above. + * + * Instead of providing which functions that block until the I/O has complete, + * libusb's asynchronous interface presents non-blocking functions which + * begin a transfer and then return immediately. Your application passes a + * callback function pointer to this non-blocking function, which libusb will + * call with the results of the transaction when it has completed. + * + * Transfers which have been submitted through the non-blocking functions + * can be cancelled with a separate function call. + * + * The non-blocking nature of this interface allows you to be simultaneously + * performing I/O to multiple endpoints on multiple devices, without having + * to use threads. + * + * This added flexibility does come with some complications though: + * - In the interest of being a lightweight library, libusb does not create + * threads and can only operate when your application is calling into it. Your + * application must call into libusb from it's main loop when events are ready + * to be handled, or you must use some other scheme to allow libusb to + * undertake whatever work needs to be done. + * - libusb also needs to be called into at certain fixed points in time in + * order to accurately handle transfer timeouts. + * - Memory handling becomes more complex. You cannot use stack memory unless + * the function with that stack is guaranteed not to return until the transfer + * callback has finished executing. + * - You generally lose some linearity from your code flow because submitting + * the transfer request is done in a separate function from where the transfer + * results are handled. This becomes particularly obvious when you want to + * submit a second transfer based on the results of an earlier transfer. + * + * Internally, libusb's synchronous interface is expressed in terms of function + * calls to the asynchronous interface. + * + * For details on how to use the asynchronous API, see the + * \ref asyncio "asynchronous I/O API" documentation pages. + */ + + +/** + * \page packetoverflow Packets and overflows + * + * \section packets Packet abstraction + * + * The USB specifications describe how data is transmitted in packets, with + * constraints on packet size defined by endpoint descriptors. The host must + * not send data payloads larger than the endpoint's maximum packet size. + * + * libusb and the underlying OS abstract out the packet concept, allowing you + * to request transfers of any size. Internally, the request will be divided + * up into correctly-sized packets. You do not have to be concerned with + * packet sizes, but there is one exception when considering overflows. + * + * \section overflow Bulk/interrupt transfer overflows + * + * When requesting data on a bulk endpoint, libusb requires you to supply a + * buffer and the maximum number of bytes of data that libusb can put in that + * buffer. However, the size of the buffer is not communicated to the device - + * the device is just asked to send any amount of data. + * + * There is no problem if the device sends an amount of data that is less than + * or equal to the buffer size. libusb reports this condition to you through + * the \ref libusb_transfer::actual_length "libusb_transfer.actual_length" + * field. + * + * Problems may occur if the device attempts to send more data than can fit in + * the buffer. libusb reports LIBUSB_TRANSFER_OVERFLOW for this condition but + * other behaviour is largely undefined: actual_length may or may not be + * accurate, the chunk of data that can fit in the buffer (before overflow) + * may or may not have been transferred. + * + * Overflows are nasty, but can be avoided. Even though you were told to + * ignore packets above, think about the lower level details: each transfer is + * split into packets (typically small, with a maximum size of 512 bytes). + * Overflows can only happen if the final packet in an incoming data transfer + * is smaller than the actual packet that the device wants to transfer. + * Therefore, you will never see an overflow if your transfer buffer size is a + * multiple of the endpoint's packet size: the final packet will either + * fill up completely or will be only partially filled. + */ + +/** + * @defgroup asyncio Asynchronous device I/O + * + * This page details libusb's asynchronous (non-blocking) API for USB device + * I/O. This interface is very powerful but is also quite complex - you will + * need to read this page carefully to understand the necessary considerations + * and issues surrounding use of this interface. Simplistic applications + * may wish to consider the \ref syncio "synchronous I/O API" instead. + * + * The asynchronous interface is built around the idea of separating transfer + * submission and handling of transfer completion (the synchronous model + * combines both of these into one). There may be a long delay between + * submission and completion, however the asynchronous submission function + * is non-blocking so will return control to your application during that + * potentially long delay. + * + * \section asyncabstraction Transfer abstraction + * + * For the asynchronous I/O, libusb implements the concept of a generic + * transfer entity for all types of I/O (control, bulk, interrupt, + * isochronous). The generic transfer object must be treated slightly + * differently depending on which type of I/O you are performing with it. + * + * This is represented by the public libusb_transfer structure type. + * + * \section asynctrf Asynchronous transfers + * + * We can view asynchronous I/O as a 5 step process: + * -# Allocation: allocate a libusb_transfer + * -# Filling: populate the libusb_transfer instance with information + * about the transfer you wish to perform + * -# Submission: ask libusb to submit the transfer + * -# Completion handling: examine transfer results in the + * libusb_transfer structure + * -# Deallocation: clean up resources + * + * + * \subsection asyncalloc Allocation + * + * This step involves allocating memory for a USB transfer. This is the + * generic transfer object mentioned above. At this stage, the transfer + * is "blank" with no details about what type of I/O it will be used for. + * + * Allocation is done with the libusb_alloc_transfer() function. You must use + * this function rather than allocating your own transfers. + * + * \subsection asyncfill Filling + * + * This step is where you take a previously allocated transfer and fill it + * with information to determine the message type and direction, data buffer, + * callback function, etc. + * + * You can either fill the required fields yourself or you can use the + * helper functions: libusb_fill_control_transfer(), libusb_fill_bulk_transfer() + * and libusb_fill_interrupt_transfer(). + * + * \subsection asyncsubmit Submission + * + * When you have allocated a transfer and filled it, you can submit it using + * libusb_submit_transfer(). This function returns immediately but can be + * regarded as firing off the I/O request in the background. + * + * \subsection asynccomplete Completion handling + * + * After a transfer has been submitted, one of four things can happen to it: + * + * - The transfer completes (i.e. some data was transferred) + * - The transfer has a timeout and the timeout expires before all data is + * transferred + * - The transfer fails due to an error + * - The transfer is cancelled + * + * Each of these will cause the user-specified transfer callback function to + * be invoked. It is up to the callback function to determine which of the + * above actually happened and to act accordingly. + * + * The user-specified callback is passed a pointer to the libusb_transfer + * structure which was used to setup and submit the transfer. At completion + * time, libusb has populated this structure with results of the transfer: + * success or failure reason, number of bytes of data transferred, etc. See + * the libusb_transfer structure documentation for more information. + * + * \subsection Deallocation + * + * When a transfer has completed (i.e. the callback function has been invoked), + * you are advised to free the transfer (unless you wish to resubmit it, see + * below). Transfers are deallocated with libusb_free_transfer(). + * + * It is undefined behaviour to free a transfer which has not completed. + * + * \section asyncresubmit Resubmission + * + * You may be wondering why allocation, filling, and submission are all + * separated above where they could reasonably be combined into a single + * operation. + * + * The reason for separation is to allow you to resubmit transfers without + * having to allocate new ones every time. This is especially useful for + * common situations dealing with interrupt endpoints - you allocate one + * transfer, fill and submit it, and when it returns with results you just + * resubmit it for the next interrupt. + * + * \section asynccancel Cancellation + * + * Another advantage of using the asynchronous interface is that you have + * the ability to cancel transfers which have not yet completed. This is + * done by calling the libusb_cancel_transfer() function. + * + * libusb_cancel_transfer() is asynchronous/non-blocking in itself. When the + * cancellation actually completes, the transfer's callback function will + * be invoked, and the callback function should check the transfer status to + * determine that it was cancelled. + * + * Freeing the transfer after it has been cancelled but before cancellation + * has completed will result in undefined behaviour. + * + * When a transfer is cancelled, some of the data may have been transferred. + * libusb will communicate this to you in the transfer callback. Do not assume + * that no data was transferred. + * + * \section bulk_overflows Overflows on device-to-host bulk/interrupt endpoints + * + * If your device does not have predictable transfer sizes (or it misbehaves), + * your application may submit a request for data on an IN endpoint which is + * smaller than the data that the device wishes to send. In some circumstances + * this will cause an overflow, which is a nasty condition to deal with. See + * the \ref packetoverflow page for discussion. + * + * \section asyncctrl Considerations for control transfers + * + * The libusb_transfer structure is generic and hence does not + * include specific fields for the control-specific setup packet structure. + * + * In order to perform a control transfer, you must place the 8-byte setup + * packet at the start of the data buffer. To simplify this, you could + * cast the buffer pointer to type struct libusb_control_setup, or you can + * use the helper function libusb_fill_control_setup(). + * + * The wLength field placed in the setup packet must be the length you would + * expect to be sent in the setup packet: the length of the payload that + * follows (or the expected maximum number of bytes to receive). However, + * the length field of the libusb_transfer object must be the length of + * the data buffer - i.e. it should be wLength plus the size of + * the setup packet (LIBUSB_CONTROL_SETUP_SIZE). + * + * If you use the helper functions, this is simplified for you: + * -# Allocate a buffer of size LIBUSB_CONTROL_SETUP_SIZE plus the size of the + * data you are sending/requesting. + * -# Call libusb_fill_control_setup() on the data buffer, using the transfer + * request size as the wLength value (i.e. do not include the extra space you + * allocated for the control setup). + * -# If this is a host-to-device transfer, place the data to be transferred + * in the data buffer, starting at offset LIBUSB_CONTROL_SETUP_SIZE. + * -# Call libusb_fill_control_transfer() to associate the data buffer with + * the transfer (and to set the remaining details such as callback and timeout). + * - Note that there is no parameter to set the length field of the transfer. + * The length is automatically inferred from the wLength field of the setup + * packet. + * -# Submit the transfer. + * + * The multi-byte control setup fields (wValue, wIndex and wLength) must + * be given in little-endian byte order (the endianness of the USB bus). + * Endianness conversion is transparently handled by + * libusb_fill_control_setup() which is documented to accept host-endian + * values. + * + * Further considerations are needed when handling transfer completion in + * your callback function: + * - As you might expect, the setup packet will still be sitting at the start + * of the data buffer. + * - If this was a device-to-host transfer, the received data will be sitting + * at offset LIBUSB_CONTROL_SETUP_SIZE into the buffer. + * - The actual_length field of the transfer structure is relative to the + * wLength of the setup packet, rather than the size of the data buffer. So, + * if your wLength was 4, your transfer's length was 12, then you + * should expect an actual_length of 4 to indicate that the data was + * transferred in entirity. + * + * To simplify parsing of setup packets and obtaining the data from the + * correct offset, you may wish to use the libusb_control_transfer_get_data() + * and libusb_control_transfer_get_setup() functions within your transfer + * callback. + * + * Even though control endpoints do not halt, a completed control transfer + * may have a LIBUSB_TRANSFER_STALL status code. This indicates the control + * request was not supported. + * + * \section asyncintr Considerations for interrupt transfers + * + * All interrupt transfers are performed using the polling interval presented + * by the bInterval value of the endpoint descriptor. + * + * \section asynciso Considerations for isochronous transfers + * + * Isochronous transfers are more complicated than transfers to + * non-isochronous endpoints. + * + * To perform I/O to an isochronous endpoint, allocate the transfer by calling + * libusb_alloc_transfer() with an appropriate number of isochronous packets. + * + * During filling, set \ref libusb_transfer::type "type" to + * \ref libusb_transfer_type::LIBUSB_TRANSFER_TYPE_ISOCHRONOUS + * "LIBUSB_TRANSFER_TYPE_ISOCHRONOUS", and set + * \ref libusb_transfer::num_iso_packets "num_iso_packets" to a value less than + * or equal to the number of packets you requested during allocation. + * libusb_alloc_transfer() does not set either of these fields for you, given + * that you might not even use the transfer on an isochronous endpoint. + * + * Next, populate the length field for the first num_iso_packets entries in + * the \ref libusb_transfer::iso_packet_desc "iso_packet_desc" array. Section + * 5.6.3 of the USB2 specifications describe how the maximum isochronous + * packet length is determined by the wMaxPacketSize field in the endpoint + * descriptor. + * Two functions can help you here: + * + * - libusb_get_max_iso_packet_size() is an easy way to determine the max + * packet size for an isochronous endpoint. Note that the maximum packet + * size is actually the maximum number of bytes that can be transmitted in + * a single microframe, therefore this function multiplies the maximum number + * of bytes per transaction by the number of transaction opportunities per + * microframe. + * - libusb_set_iso_packet_lengths() assigns the same length to all packets + * within a transfer, which is usually what you want. + * + * For outgoing transfers, you'll obviously fill the buffer and populate the + * packet descriptors in hope that all the data gets transferred. For incoming + * transfers, you must ensure the buffer has sufficient capacity for + * the situation where all packets transfer the full amount of requested data. + * + * Completion handling requires some extra consideration. The + * \ref libusb_transfer::actual_length "actual_length" field of the transfer + * is meaningless and should not be examined; instead you must refer to the + * \ref libusb_iso_packet_descriptor::actual_length "actual_length" field of + * each individual packet. + * + * The \ref libusb_transfer::status "status" field of the transfer is also a + * little misleading: + * - If the packets were submitted and the isochronous data microframes + * completed normally, status will have value + * \ref libusb_transfer_status::LIBUSB_TRANSFER_COMPLETED + * "LIBUSB_TRANSFER_COMPLETED". Note that bus errors and software-incurred + * delays are not counted as transfer errors; the transfer.status field may + * indicate COMPLETED even if some or all of the packets failed. Refer to + * the \ref libusb_iso_packet_descriptor::status "status" field of each + * individual packet to determine packet failures. + * - The status field will have value + * \ref libusb_transfer_status::LIBUSB_TRANSFER_ERROR + * "LIBUSB_TRANSFER_ERROR" only when serious errors were encountered. + * - Other transfer status codes occur with normal behaviour. + * + * The data for each packet will be found at an offset into the buffer that + * can be calculated as if each prior packet completed in full. The + * libusb_get_iso_packet_buffer() and libusb_get_iso_packet_buffer_simple() + * functions may help you here. + * + * \section asyncmem Memory caveats + * + * In most circumstances, it is not safe to use stack memory for transfer + * buffers. This is because the function that fired off the asynchronous + * transfer may return before libusb has finished using the buffer, and when + * the function returns it's stack gets destroyed. This is true for both + * host-to-device and device-to-host transfers. + * + * The only case in which it is safe to use stack memory is where you can + * guarantee that the function owning the stack space for the buffer does not + * return until after the transfer's callback function has completed. In every + * other case, you need to use heap memory instead. + * + * \section asyncflags Fine control + * + * Through using this asynchronous interface, you may find yourself repeating + * a few simple operations many times. You can apply a bitwise OR of certain + * flags to a transfer to simplify certain things: + * - \ref libusb_transfer_flags::LIBUSB_TRANSFER_SHORT_NOT_OK + * "LIBUSB_TRANSFER_SHORT_NOT_OK" results in transfers which transferred + * less than the requested amount of data being marked with status + * \ref libusb_transfer_status::LIBUSB_TRANSFER_ERROR "LIBUSB_TRANSFER_ERROR" + * (they would normally be regarded as COMPLETED) + * - \ref libusb_transfer_flags::LIBUSB_TRANSFER_FREE_BUFFER + * "LIBUSB_TRANSFER_FREE_BUFFER" allows you to ask libusb to free the transfer + * buffer when freeing the transfer. + * - \ref libusb_transfer_flags::LIBUSB_TRANSFER_FREE_TRANSFER + * "LIBUSB_TRANSFER_FREE_TRANSFER" causes libusb to automatically free the + * transfer after the transfer callback returns. + * + * \section asyncevent Event handling + * + * In accordance of the aim of being a lightweight library, libusb does not + * create threads internally. This means that libusb code does not execute + * at any time other than when your application is calling a libusb function. + * However, an asynchronous model requires that libusb perform work at various + * points in time - namely processing the results of previously-submitted + * transfers and invoking the user-supplied callback function. + * + * This gives rise to the libusb_handle_events() function which your + * application must call into when libusb has work do to. This gives libusb + * the opportunity to reap pending transfers, invoke callbacks, etc. + * + * The first issue to discuss here is how your application can figure out + * when libusb has work to do. In fact, there are two naive options which + * do not actually require your application to know this: + * -# Periodically call libusb_handle_events() in non-blocking mode at fixed + * short intervals from your main loop + * -# Repeatedly call libusb_handle_events() in blocking mode from a dedicated + * thread. + * + * The first option is plainly not very nice, and will cause unnecessary + * CPU wakeups leading to increased power usage and decreased battery life. + * The second option is not very nice either, but may be the nicest option + * available to you if the "proper" approach can not be applied to your + * application (read on...). + * + * The recommended option is to integrate libusb with your application main + * event loop. libusb exposes a set of file descriptors which allow you to do + * this. Your main loop is probably already calling poll() or select() or a + * variant on a set of file descriptors for other event sources (e.g. keyboard + * button presses, mouse movements, network sockets, etc). You then add + * libusb's file descriptors to your poll()/select() calls, and when activity + * is detected on such descriptors you know it is time to call + * libusb_handle_events(). + * + * There is one final event handling complication. libusb supports + * asynchronous transfers which time out after a specified time period, and + * this requires that libusb is called into at or after the timeout so that + * the timeout can be handled. So, in addition to considering libusb's file + * descriptors in your main event loop, you must also consider that libusb + * sometimes needs to be called into at fixed points in time even when there + * is no file descriptor activity. + * + * For the details on retrieving the set of file descriptors and determining + * the next timeout, see the \ref poll "polling and timing" API documentation. + */ + +/** + * @defgroup poll Polling and timing + * + * This page documents libusb's functions for polling events and timing. + * These functions are only necessary for users of the + * \ref asyncio "asynchronous API". If you are only using the simpler + * \ref syncio "synchronous API" then you do not need to ever call these + * functions. + * + * The justification for the functionality described here has already been + * discussed in the \ref asyncevent "event handling" section of the + * asynchronous API documentation. In summary, libusb does not create internal + * threads for event processing and hence relies on your application calling + * into libusb at certain points in time so that pending events can be handled. + * In order to know precisely when libusb needs to be called into, libusb + * offers you a set of pollable file descriptors and information about when + * the next timeout expires. + * + * If you are using the asynchronous I/O API, you must take one of the two + * following options, otherwise your I/O will not complete. + * + * \section pollsimple The simple option + * + * If your application revolves solely around libusb and does not need to + * handle other event sources, you can have a program structure as follows: +\code +// initialize libusb +// find and open device +// maybe fire off some initial async I/O + +while (user_has_not_requested_exit) + libusb_handle_events(ctx); + +// clean up and exit +\endcode + * + * With such a simple main loop, you do not have to worry about managing + * sets of file descriptors or handling timeouts. libusb_handle_events() will + * handle those details internally. + * + * \section pollmain The more advanced option + * + * \note This functionality is currently only available on Unix-like platforms. + * On Windows, libusb_get_pollfds() simply returns NULL. Exposing event sources + * on Windows will require some further thought and design. + * + * In more advanced applications, you will already have a main loop which + * is monitoring other event sources: network sockets, X11 events, mouse + * movements, etc. Through exposing a set of file descriptors, libusb is + * designed to cleanly integrate into such main loops. + * + * In addition to polling file descriptors for the other event sources, you + * take a set of file descriptors from libusb and monitor those too. When you + * detect activity on libusb's file descriptors, you call + * libusb_handle_events_timeout() in non-blocking mode. + * + * What's more, libusb may also need to handle events at specific moments in + * time. No file descriptor activity is generated at these times, so your + * own application needs to be continually aware of when the next one of these + * moments occurs (through calling libusb_get_next_timeout()), and then it + * needs to call libusb_handle_events_timeout() in non-blocking mode when + * these moments occur. This means that you need to adjust your + * poll()/select() timeout accordingly. + * + * libusb provides you with a set of file descriptors to poll and expects you + * to poll all of them, treating them as a single entity. The meaning of each + * file descriptor in the set is an internal implementation detail, + * platform-dependent and may vary from release to release. Don't try and + * interpret the meaning of the file descriptors, just do as libusb indicates, + * polling all of them at once. + * + * In pseudo-code, you want something that looks like: +\code +// initialise libusb + +libusb_get_pollfds(ctx) +while (user has not requested application exit) { + libusb_get_next_timeout(ctx); + poll(on libusb file descriptors plus any other event sources of interest, + using a timeout no larger than the value libusb just suggested) + if (poll() indicated activity on libusb file descriptors) + libusb_handle_events_timeout(ctx, &zero_tv); + if (time has elapsed to or beyond the libusb timeout) + libusb_handle_events_timeout(ctx, &zero_tv); + // handle events from other sources here +} + +// clean up and exit +\endcode + * + * \subsection polltime Notes on time-based events + * + * The above complication with having to track time and call into libusb at + * specific moments is a bit of a headache. For maximum compatibility, you do + * need to write your main loop as above, but you may decide that you can + * restrict the supported platforms of your application and get away with + * a more simplistic scheme. + * + * These time-based event complications are \b not required on the following + * platforms: + * - Darwin + * - Linux, provided that the following version requirements are satisfied: + * - Linux v2.6.27 or newer, compiled with timerfd support + * - glibc v2.9 or newer + * - libusb v1.0.5 or newer + * + * Under these configurations, libusb_get_next_timeout() will \em always return + * 0, so your main loop can be simplified to: +\code +// initialise libusb + +libusb_get_pollfds(ctx) +while (user has not requested application exit) { + poll(on libusb file descriptors plus any other event sources of interest, + using any timeout that you like) + if (poll() indicated activity on libusb file descriptors) + libusb_handle_events_timeout(ctx, &zero_tv); + // handle events from other sources here +} + +// clean up and exit +\endcode + * + * Do remember that if you simplify your main loop to the above, you will + * lose compatibility with some platforms (including legacy Linux platforms, + * and any future platforms supported by libusb which may have time-based + * event requirements). The resultant problems will likely appear as + * strange bugs in your application. + * + * You can use the libusb_pollfds_handle_timeouts() function to do a runtime + * check to see if it is safe to ignore the time-based event complications. + * If your application has taken the shortcut of ignoring libusb's next timeout + * in your main loop, then you are advised to check the return value of + * libusb_pollfds_handle_timeouts() during application startup, and to abort + * if the platform does suffer from these timing complications. + * + * \subsection fdsetchange Changes in the file descriptor set + * + * The set of file descriptors that libusb uses as event sources may change + * during the life of your application. Rather than having to repeatedly + * call libusb_get_pollfds(), you can set up notification functions for when + * the file descriptor set changes using libusb_set_pollfd_notifiers(). + * + * \subsection mtissues Multi-threaded considerations + * + * Unfortunately, the situation is complicated further when multiple threads + * come into play. If two threads are monitoring the same file descriptors, + * the fact that only one thread will be woken up when an event occurs causes + * some headaches. + * + * The events lock, event waiters lock, and libusb_handle_events_locked() + * entities are added to solve these problems. You do not need to be concerned + * with these entities otherwise. + * + * See the extra documentation: \ref mtasync + */ + +/** \page mtasync Multi-threaded applications and asynchronous I/O + * + * libusb is a thread-safe library, but extra considerations must be applied + * to applications which interact with libusb from multiple threads. + * + * The underlying issue that must be addressed is that all libusb I/O + * revolves around monitoring file descriptors through the poll()/select() + * system calls. This is directly exposed at the + * \ref asyncio "asynchronous interface" but it is important to note that the + * \ref syncio "synchronous interface" is implemented on top of the + * asynchonrous interface, therefore the same considerations apply. + * + * The issue is that if two or more threads are concurrently calling poll() + * or select() on libusb's file descriptors then only one of those threads + * will be woken up when an event arrives. The others will be completely + * oblivious that anything has happened. + * + * Consider the following pseudo-code, which submits an asynchronous transfer + * then waits for its completion. This style is one way you could implement a + * synchronous interface on top of the asynchronous interface (and libusb + * does something similar, albeit more advanced due to the complications + * explained on this page). + * +\code +void cb(struct libusb_transfer *transfer) +{ + int *completed = transfer->user_data; + *completed = 1; +} + +void myfunc() { + struct libusb_transfer *transfer; + unsigned char buffer[LIBUSB_CONTROL_SETUP_SIZE]; + int completed = 0; + + transfer = libusb_alloc_transfer(0); + libusb_fill_control_setup(buffer, + LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_OUT, 0x04, 0x01, 0, 0); + libusb_fill_control_transfer(transfer, dev, buffer, cb, &completed, 1000); + libusb_submit_transfer(transfer); + + while (!completed) { + poll(libusb file descriptors, 120*1000); + if (poll indicates activity) + libusb_handle_events_timeout(ctx, &zero_tv); + } + printf("completed!"); + // other code here +} +\endcode + * + * Here we are serializing completion of an asynchronous event + * against a condition - the condition being completion of a specific transfer. + * The poll() loop has a long timeout to minimize CPU usage during situations + * when nothing is happening (it could reasonably be unlimited). + * + * If this is the only thread that is polling libusb's file descriptors, there + * is no problem: there is no danger that another thread will swallow up the + * event that we are interested in. On the other hand, if there is another + * thread polling the same descriptors, there is a chance that it will receive + * the event that we were interested in. In this situation, myfunc() + * will only realise that the transfer has completed on the next iteration of + * the loop, up to 120 seconds later. Clearly a two-minute delay is + * undesirable, and don't even think about using short timeouts to circumvent + * this issue! + * + * The solution here is to ensure that no two threads are ever polling the + * file descriptors at the same time. A naive implementation of this would + * impact the capabilities of the library, so libusb offers the scheme + * documented below to ensure no loss of functionality. + * + * Before we go any further, it is worth mentioning that all libusb-wrapped + * event handling procedures fully adhere to the scheme documented below. + * This includes libusb_handle_events() and its variants, and all the + * synchronous I/O functions - libusb hides this headache from you. + * + * \section Using libusb_handle_events() from multiple threads + * + * Even when only using libusb_handle_events() and synchronous I/O functions, + * you can still have a race condition. You might be tempted to solve the + * above with libusb_handle_events() like so: + * +\code + libusb_submit_transfer(transfer); + + while (!completed) { + libusb_handle_events(ctx); + } + printf("completed!"); +\endcode + * + * This however has a race between the checking of completed and + * libusb_handle_events() acquiring the events lock, so another thread + * could have completed the transfer, resulting in this thread hanging + * until either a timeout or another event occurs. See also commit + * 6696512aade99bb15d6792af90ae329af270eba6 which fixes this in the + * synchronous API implementation of libusb. + * + * Fixing this race requires checking the variable completed only after + * taking the event lock, which defeats the concept of just calling + * libusb_handle_events() without worrying about locking. This is why + * libusb-1.0.9 introduces the new libusb_handle_events_timeout_completed() + * and libusb_handle_events_completed() functions, which handles doing the + * completion check for you after they have acquired the lock: + * +\code + libusb_submit_transfer(transfer); + + while (!completed) { + libusb_handle_events_completed(ctx, &completed); + } + printf("completed!"); +\endcode + * + * This nicely fixes the race in our example. Note that if all you want to + * do is submit a single transfer and wait for its completion, then using + * one of the synchronous I/O functions is much easier. + * + * \section eventlock The events lock + * + * The problem is when we consider the fact that libusb exposes file + * descriptors to allow for you to integrate asynchronous USB I/O into + * existing main loops, effectively allowing you to do some work behind + * libusb's back. If you do take libusb's file descriptors and pass them to + * poll()/select() yourself, you need to be aware of the associated issues. + * + * The first concept to be introduced is the events lock. The events lock + * is used to serialize threads that want to handle events, such that only + * one thread is handling events at any one time. + * + * You must take the events lock before polling libusb file descriptors, + * using libusb_lock_events(). You must release the lock as soon as you have + * aborted your poll()/select() loop, using libusb_unlock_events(). + * + * \section threadwait Letting other threads do the work for you + * + * Although the events lock is a critical part of the solution, it is not + * enough on it's own. You might wonder if the following is sufficient... +\code + libusb_lock_events(ctx); + while (!completed) { + poll(libusb file descriptors, 120*1000); + if (poll indicates activity) + libusb_handle_events_timeout(ctx, &zero_tv); + } + libusb_unlock_events(ctx); +\endcode + * ...and the answer is that it is not. This is because the transfer in the + * code shown above may take a long time (say 30 seconds) to complete, and + * the lock is not released until the transfer is completed. + * + * Another thread with similar code that wants to do event handling may be + * working with a transfer that completes after a few milliseconds. Despite + * having such a quick completion time, the other thread cannot check that + * status of its transfer until the code above has finished (30 seconds later) + * due to contention on the lock. + * + * To solve this, libusb offers you a mechanism to determine when another + * thread is handling events. It also offers a mechanism to block your thread + * until the event handling thread has completed an event (and this mechanism + * does not involve polling of file descriptors). + * + * After determining that another thread is currently handling events, you + * obtain the event waiters lock using libusb_lock_event_waiters(). + * You then re-check that some other thread is still handling events, and if + * so, you call libusb_wait_for_event(). + * + * libusb_wait_for_event() puts your application to sleep until an event + * occurs, or until a thread releases the events lock. When either of these + * things happen, your thread is woken up, and should re-check the condition + * it was waiting on. It should also re-check that another thread is handling + * events, and if not, it should start handling events itself. + * + * This looks like the following, as pseudo-code: +\code +retry: +if (libusb_try_lock_events(ctx) == 0) { + // we obtained the event lock: do our own event handling + while (!completed) { + if (!libusb_event_handling_ok(ctx)) { + libusb_unlock_events(ctx); + goto retry; + } + poll(libusb file descriptors, 120*1000); + if (poll indicates activity) + libusb_handle_events_locked(ctx, 0); + } + libusb_unlock_events(ctx); +} else { + // another thread is doing event handling. wait for it to signal us that + // an event has completed + libusb_lock_event_waiters(ctx); + + while (!completed) { + // now that we have the event waiters lock, double check that another + // thread is still handling events for us. (it may have ceased handling + // events in the time it took us to reach this point) + if (!libusb_event_handler_active(ctx)) { + // whoever was handling events is no longer doing so, try again + libusb_unlock_event_waiters(ctx); + goto retry; + } + + libusb_wait_for_event(ctx, NULL); + } + libusb_unlock_event_waiters(ctx); +} +printf("completed!\n"); +\endcode + * + * A naive look at the above code may suggest that this can only support + * one event waiter (hence a total of 2 competing threads, the other doing + * event handling), because the event waiter seems to have taken the event + * waiters lock while waiting for an event. However, the system does support + * multiple event waiters, because libusb_wait_for_event() actually drops + * the lock while waiting, and reaquires it before continuing. + * + * We have now implemented code which can dynamically handle situations where + * nobody is handling events (so we should do it ourselves), and it can also + * handle situations where another thread is doing event handling (so we can + * piggyback onto them). It is also equipped to handle a combination of + * the two, for example, another thread is doing event handling, but for + * whatever reason it stops doing so before our condition is met, so we take + * over the event handling. + * + * Four functions were introduced in the above pseudo-code. Their importance + * should be apparent from the code shown above. + * -# libusb_try_lock_events() is a non-blocking function which attempts + * to acquire the events lock but returns a failure code if it is contended. + * -# libusb_event_handling_ok() checks that libusb is still happy for your + * thread to be performing event handling. Sometimes, libusb needs to + * interrupt the event handler, and this is how you can check if you have + * been interrupted. If this function returns 0, the correct behaviour is + * for you to give up the event handling lock, and then to repeat the cycle. + * The following libusb_try_lock_events() will fail, so you will become an + * events waiter. For more information on this, read \ref fullstory below. + * -# libusb_handle_events_locked() is a variant of + * libusb_handle_events_timeout() that you can call while holding the + * events lock. libusb_handle_events_timeout() itself implements similar + * logic to the above, so be sure not to call it when you are + * "working behind libusb's back", as is the case here. + * -# libusb_event_handler_active() determines if someone is currently + * holding the events lock + * + * You might be wondering why there is no function to wake up all threads + * blocked on libusb_wait_for_event(). This is because libusb can do this + * internally: it will wake up all such threads when someone calls + * libusb_unlock_events() or when a transfer completes (at the point after its + * callback has returned). + * + * \subsection fullstory The full story + * + * The above explanation should be enough to get you going, but if you're + * really thinking through the issues then you may be left with some more + * questions regarding libusb's internals. If you're curious, read on, and if + * not, skip to the next section to avoid confusing yourself! + * + * The immediate question that may spring to mind is: what if one thread + * modifies the set of file descriptors that need to be polled while another + * thread is doing event handling? + * + * There are 2 situations in which this may happen. + * -# libusb_open() will add another file descriptor to the poll set, + * therefore it is desirable to interrupt the event handler so that it + * restarts, picking up the new descriptor. + * -# libusb_close() will remove a file descriptor from the poll set. There + * are all kinds of race conditions that could arise here, so it is + * important that nobody is doing event handling at this time. + * + * libusb handles these issues internally, so application developers do not + * have to stop their event handlers while opening/closing devices. Here's how + * it works, focusing on the libusb_close() situation first: + * + * -# During initialization, libusb opens an internal pipe, and it adds the read + * end of this pipe to the set of file descriptors to be polled. + * -# During libusb_close(), libusb writes some dummy data on this control pipe. + * This immediately interrupts the event handler. libusb also records + * internally that it is trying to interrupt event handlers for this + * high-priority event. + * -# At this point, some of the functions described above start behaving + * differently: + * - libusb_event_handling_ok() starts returning 1, indicating that it is NOT + * OK for event handling to continue. + * - libusb_try_lock_events() starts returning 1, indicating that another + * thread holds the event handling lock, even if the lock is uncontended. + * - libusb_event_handler_active() starts returning 1, indicating that + * another thread is doing event handling, even if that is not true. + * -# The above changes in behaviour result in the event handler stopping and + * giving up the events lock very quickly, giving the high-priority + * libusb_close() operation a "free ride" to acquire the events lock. All + * threads that are competing to do event handling become event waiters. + * -# With the events lock held inside libusb_close(), libusb can safely remove + * a file descriptor from the poll set, in the safety of knowledge that + * nobody is polling those descriptors or trying to access the poll set. + * -# After obtaining the events lock, the close operation completes very + * quickly (usually a matter of milliseconds) and then immediately releases + * the events lock. + * -# At the same time, the behaviour of libusb_event_handling_ok() and friends + * reverts to the original, documented behaviour. + * -# The release of the events lock causes the threads that are waiting for + * events to be woken up and to start competing to become event handlers + * again. One of them will succeed; it will then re-obtain the list of poll + * descriptors, and USB I/O will then continue as normal. + * + * libusb_open() is similar, and is actually a more simplistic case. Upon a + * call to libusb_open(): + * + * -# The device is opened and a file descriptor is added to the poll set. + * -# libusb sends some dummy data on the control pipe, and records that it + * is trying to modify the poll descriptor set. + * -# The event handler is interrupted, and the same behaviour change as for + * libusb_close() takes effect, causing all event handling threads to become + * event waiters. + * -# The libusb_open() implementation takes its free ride to the events lock. + * -# Happy that it has successfully paused the events handler, libusb_open() + * releases the events lock. + * -# The event waiter threads are all woken up and compete to become event + * handlers again. The one that succeeds will obtain the list of poll + * descriptors again, which will include the addition of the new device. + * + * \subsection concl Closing remarks + * + * The above may seem a little complicated, but hopefully I have made it clear + * why such complications are necessary. Also, do not forget that this only + * applies to applications that take libusb's file descriptors and integrate + * them into their own polling loops. + * + * You may decide that it is OK for your multi-threaded application to ignore + * some of the rules and locks detailed above, because you don't think that + * two threads can ever be polling the descriptors at the same time. If that + * is the case, then that's good news for you because you don't have to worry. + * But be careful here; remember that the synchronous I/O functions do event + * handling internally. If you have one thread doing event handling in a loop + * (without implementing the rules and locking semantics documented above) + * and another trying to send a synchronous USB transfer, you will end up with + * two threads monitoring the same descriptors, and the above-described + * undesirable behaviour occuring. The solution is for your polling thread to + * play by the rules; the synchronous I/O functions do so, and this will result + * in them getting along in perfect harmony. + * + * If you do have a dedicated thread doing event handling, it is perfectly + * legal for it to take the event handling lock for long periods of time. Any + * synchronous I/O functions you call from other threads will transparently + * fall back to the "event waiters" mechanism detailed above. The only + * consideration that your event handling thread must apply is the one related + * to libusb_event_handling_ok(): you must call this before every poll(), and + * give up the events lock if instructed. + */ + +int usbi_io_init(struct libusb_context *ctx) +{ + int r; + + usbi_mutex_init(&ctx->flying_transfers_lock, NULL); + usbi_mutex_init(&ctx->pollfds_lock, NULL); + usbi_mutex_init(&ctx->pollfd_modify_lock, NULL); + usbi_mutex_init_recursive(&ctx->events_lock, NULL); + usbi_mutex_init(&ctx->event_waiters_lock, NULL); + usbi_cond_init(&ctx->event_waiters_cond, NULL); + list_init(&ctx->flying_transfers); + list_init(&ctx->pollfds); + + /* FIXME should use an eventfd on kernels that support it */ + r = usbi_pipe(ctx->ctrl_pipe); + if (r < 0) { + r = LIBUSB_ERROR_OTHER; + goto err; + } + + r = usbi_add_pollfd(ctx, ctx->ctrl_pipe[0], POLLIN); + if (r < 0) + goto err_close_pipe; + + /* create hotplug pipe */ + r = usbi_pipe(ctx->hotplug_pipe); + if (r < 0) { + r = LIBUSB_ERROR_OTHER; + goto err; + } + +#ifndef OS_WINDOWS + fcntl (ctx->hotplug_pipe[1], F_SETFD, O_NONBLOCK); +#endif + r = usbi_add_pollfd(ctx, ctx->hotplug_pipe[0], POLLIN); + if (r < 0) + goto err_close_hp_pipe; + +#ifdef USBI_TIMERFD_AVAILABLE + ctx->timerfd = timerfd_create(usbi_backend->get_timerfd_clockid(), + TFD_NONBLOCK); + if (ctx->timerfd >= 0) { + usbi_dbg("using timerfd for timeouts"); + r = usbi_add_pollfd(ctx, ctx->timerfd, POLLIN); + if (r < 0) { + usbi_remove_pollfd(ctx, ctx->ctrl_pipe[0]); + close(ctx->timerfd); + goto err_close_hp_pipe; + } + } else { + usbi_dbg("timerfd not available (code %d error %d)", ctx->timerfd, errno); + ctx->timerfd = -1; + } +#endif + + return 0; + +err_close_hp_pipe: + usbi_close(ctx->hotplug_pipe[0]); + usbi_close(ctx->hotplug_pipe[1]); +err_close_pipe: + usbi_close(ctx->ctrl_pipe[0]); + usbi_close(ctx->ctrl_pipe[1]); +err: + usbi_mutex_destroy(&ctx->flying_transfers_lock); + usbi_mutex_destroy(&ctx->pollfds_lock); + usbi_mutex_destroy(&ctx->pollfd_modify_lock); + usbi_mutex_destroy(&ctx->events_lock); + usbi_mutex_destroy(&ctx->event_waiters_lock); + usbi_cond_destroy(&ctx->event_waiters_cond); + return r; +} + +void usbi_io_exit(struct libusb_context *ctx) +{ + usbi_remove_pollfd(ctx, ctx->ctrl_pipe[0]); + usbi_close(ctx->ctrl_pipe[0]); + usbi_close(ctx->ctrl_pipe[1]); + usbi_remove_pollfd(ctx, ctx->hotplug_pipe[0]); + usbi_close(ctx->hotplug_pipe[0]); + usbi_close(ctx->hotplug_pipe[1]); +#ifdef USBI_TIMERFD_AVAILABLE + if (usbi_using_timerfd(ctx)) { + usbi_remove_pollfd(ctx, ctx->timerfd); + close(ctx->timerfd); + } +#endif + usbi_mutex_destroy(&ctx->flying_transfers_lock); + usbi_mutex_destroy(&ctx->pollfds_lock); + usbi_mutex_destroy(&ctx->pollfd_modify_lock); + usbi_mutex_destroy(&ctx->events_lock); + usbi_mutex_destroy(&ctx->event_waiters_lock); + usbi_cond_destroy(&ctx->event_waiters_cond); +} + +static int calculate_timeout(struct usbi_transfer *transfer) +{ + int r; + struct timespec current_time; + unsigned int timeout = + USBI_TRANSFER_TO_LIBUSB_TRANSFER(transfer)->timeout; + + if (!timeout) + return 0; + + r = usbi_backend->clock_gettime(USBI_CLOCK_MONOTONIC, ¤t_time); + if (r < 0) { + usbi_err(ITRANSFER_CTX(transfer), + "failed to read monotonic clock, errno=%d", errno); + return r; + } + + current_time.tv_sec += timeout / 1000; + current_time.tv_nsec += (timeout % 1000) * 1000000; + + while (current_time.tv_nsec >= 1000000000) { + current_time.tv_nsec -= 1000000000; + current_time.tv_sec++; + } + + TIMESPEC_TO_TIMEVAL(&transfer->timeout, ¤t_time); + return 0; +} + +/* add a transfer to the (timeout-sorted) active transfers list. + * returns 1 if the transfer has a timeout and it is the timeout next to + * expire */ +static int add_to_flying_list(struct usbi_transfer *transfer) +{ + struct usbi_transfer *cur; + struct timeval *timeout = &transfer->timeout; + struct libusb_context *ctx = ITRANSFER_CTX(transfer); + int r = 0; + int first = 1; + + usbi_mutex_lock(&ctx->flying_transfers_lock); + + /* if we have no other flying transfers, start the list with this one */ + if (list_empty(&ctx->flying_transfers)) { + list_add(&transfer->list, &ctx->flying_transfers); + if (timerisset(timeout)) + r = 1; + goto out; + } + + /* if we have infinite timeout, append to end of list */ + if (!timerisset(timeout)) { + list_add_tail(&transfer->list, &ctx->flying_transfers); + goto out; + } + + /* otherwise, find appropriate place in list */ + list_for_each_entry(cur, &ctx->flying_transfers, list, struct usbi_transfer) { + /* find first timeout that occurs after the transfer in question */ + struct timeval *cur_tv = &cur->timeout; + + if (!timerisset(cur_tv) || (cur_tv->tv_sec > timeout->tv_sec) || + (cur_tv->tv_sec == timeout->tv_sec && + cur_tv->tv_usec > timeout->tv_usec)) { + list_add_tail(&transfer->list, &cur->list); + r = first; + goto out; + } + first = 0; + } + + /* otherwise we need to be inserted at the end */ + list_add_tail(&transfer->list, &ctx->flying_transfers); +out: + usbi_mutex_unlock(&ctx->flying_transfers_lock); + return r; +} + +/** \ingroup asyncio + * Allocate a libusb transfer with a specified number of isochronous packet + * descriptors. The returned transfer is pre-initialized for you. When the new + * transfer is no longer needed, it should be freed with + * libusb_free_transfer(). + * + * Transfers intended for non-isochronous endpoints (e.g. control, bulk, + * interrupt) should specify an iso_packets count of zero. + * + * For transfers intended for isochronous endpoints, specify an appropriate + * number of packet descriptors to be allocated as part of the transfer. + * The returned transfer is not specially initialized for isochronous I/O; + * you are still required to set the + * \ref libusb_transfer::num_iso_packets "num_iso_packets" and + * \ref libusb_transfer::type "type" fields accordingly. + * + * It is safe to allocate a transfer with some isochronous packets and then + * use it on a non-isochronous endpoint. If you do this, ensure that at time + * of submission, num_iso_packets is 0 and that type is set appropriately. + * + * \param iso_packets number of isochronous packet descriptors to allocate + * \returns a newly allocated transfer, or NULL on error + */ +DEFAULT_VISIBILITY +struct libusb_transfer * LIBUSB_CALL libusb_alloc_transfer( + int iso_packets) +{ + size_t os_alloc_size = usbi_backend->transfer_priv_size + + (usbi_backend->add_iso_packet_size * iso_packets); + size_t alloc_size = sizeof(struct usbi_transfer) + + sizeof(struct libusb_transfer) + + (sizeof(struct libusb_iso_packet_descriptor) * iso_packets) + + os_alloc_size; + struct usbi_transfer *itransfer = malloc(alloc_size); + if (!itransfer) + return NULL; + + memset(itransfer, 0, alloc_size); + itransfer->num_iso_packets = iso_packets; + usbi_mutex_init(&itransfer->lock, NULL); + return USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); +} + +/** \ingroup asyncio + * Free a transfer structure. This should be called for all transfers + * allocated with libusb_alloc_transfer(). + * + * If the \ref libusb_transfer_flags::LIBUSB_TRANSFER_FREE_BUFFER + * "LIBUSB_TRANSFER_FREE_BUFFER" flag is set and the transfer buffer is + * non-NULL, this function will also free the transfer buffer using the + * standard system memory allocator (e.g. free()). + * + * It is legal to call this function with a NULL transfer. In this case, + * the function will simply return safely. + * + * It is not legal to free an active transfer (one which has been submitted + * and has not yet completed). + * + * \param transfer the transfer to free + */ +void API_EXPORTED libusb_free_transfer(struct libusb_transfer *transfer) +{ + struct usbi_transfer *itransfer; + if (!transfer) + return; + + if (transfer->flags & LIBUSB_TRANSFER_FREE_BUFFER && transfer->buffer) + free(transfer->buffer); + + itransfer = LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer); + usbi_mutex_destroy(&itransfer->lock); + free(itransfer); +} + +/** \ingroup asyncio + * Submit a transfer. This function will fire off the USB transfer and then + * return immediately. + * + * \param transfer the transfer to submit + * \returns 0 on success + * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected + * \returns LIBUSB_ERROR_BUSY if the transfer has already been submitted. + * \returns LIBUSB_ERROR_NOT_SUPPORTED if the transfer flags are not supported + * by the operating system. + * \returns another LIBUSB_ERROR code on other failure + */ +int API_EXPORTED libusb_submit_transfer(struct libusb_transfer *transfer) +{ + struct libusb_context *ctx = TRANSFER_CTX(transfer); + struct usbi_transfer *itransfer = + LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer); + int r; + int first; + int updated_fds; + + usbi_mutex_lock(&itransfer->lock); + itransfer->transferred = 0; + itransfer->flags = 0; + r = calculate_timeout(itransfer); + if (r < 0) { + r = LIBUSB_ERROR_OTHER; + goto out; + } + + first = add_to_flying_list(itransfer); + r = usbi_backend->submit_transfer(itransfer); + if (r) { + usbi_mutex_lock(&ctx->flying_transfers_lock); + list_del(&itransfer->list); + usbi_mutex_unlock(&ctx->flying_transfers_lock); + } +#ifdef USBI_TIMERFD_AVAILABLE + else if (first && usbi_using_timerfd(ctx)) { + /* if this transfer has the lowest timeout of all active transfers, + * rearm the timerfd with this transfer's timeout */ + const struct itimerspec it = { {0, 0}, + { itransfer->timeout.tv_sec, itransfer->timeout.tv_usec * 1000 } }; + usbi_dbg("arm timerfd for timeout in %dms (first in line)", transfer->timeout); + r = timerfd_settime(ctx->timerfd, TFD_TIMER_ABSTIME, &it, NULL); + if (r < 0) + r = LIBUSB_ERROR_OTHER; + } +#else + (void)first; +#endif + +out: + updated_fds = (itransfer->flags & USBI_TRANSFER_UPDATED_FDS); + usbi_mutex_unlock(&itransfer->lock); + if (updated_fds) + usbi_fd_notification(ctx); + return r; +} + +/** \ingroup asyncio + * Asynchronously cancel a previously submitted transfer. + * This function returns immediately, but this does not indicate cancellation + * is complete. Your callback function will be invoked at some later time + * with a transfer status of + * \ref libusb_transfer_status::LIBUSB_TRANSFER_CANCELLED + * "LIBUSB_TRANSFER_CANCELLED." + * + * \param transfer the transfer to cancel + * \returns 0 on success + * \returns LIBUSB_ERROR_NOT_FOUND if the transfer is already complete or + * cancelled. + * \returns a LIBUSB_ERROR code on failure + */ +int API_EXPORTED libusb_cancel_transfer(struct libusb_transfer *transfer) +{ + struct usbi_transfer *itransfer = + LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer); + int r; + + usbi_dbg(""); + usbi_mutex_lock(&itransfer->lock); + r = usbi_backend->cancel_transfer(itransfer); + if (r < 0) { + if (r != LIBUSB_ERROR_NOT_FOUND) + usbi_err(TRANSFER_CTX(transfer), + "cancel transfer failed error %d", r); + else + usbi_dbg("cancel transfer failed error %d", r); + + if (r == LIBUSB_ERROR_NO_DEVICE) + itransfer->flags |= USBI_TRANSFER_DEVICE_DISAPPEARED; + } + + itransfer->flags |= USBI_TRANSFER_CANCELLING; + + usbi_mutex_unlock(&itransfer->lock); + return r; +} + +#ifdef USBI_TIMERFD_AVAILABLE +static int disarm_timerfd(struct libusb_context *ctx) +{ + const struct itimerspec disarm_timer = { { 0, 0 }, { 0, 0 } }; + int r; + + usbi_dbg(""); + r = timerfd_settime(ctx->timerfd, 0, &disarm_timer, NULL); + if (r < 0) + return LIBUSB_ERROR_OTHER; + else + return 0; +} + +/* iterates through the flying transfers, and rearms the timerfd based on the + * next upcoming timeout. + * must be called with flying_list locked. + * returns 0 if there was no timeout to arm, 1 if the next timeout was armed, + * or a LIBUSB_ERROR code on failure. + */ +static int arm_timerfd_for_next_timeout(struct libusb_context *ctx) +{ + struct usbi_transfer *transfer; + + list_for_each_entry(transfer, &ctx->flying_transfers, list, struct usbi_transfer) { + struct timeval *cur_tv = &transfer->timeout; + + /* if we've reached transfers of infinite timeout, then we have no + * arming to do */ + if (!timerisset(cur_tv)) + return 0; + + /* act on first transfer that is not already cancelled */ + if (!(transfer->flags & USBI_TRANSFER_TIMED_OUT)) { + int r; + const struct itimerspec it = { {0, 0}, + { cur_tv->tv_sec, cur_tv->tv_usec * 1000 } }; + usbi_dbg("next timeout originally %dms", USBI_TRANSFER_TO_LIBUSB_TRANSFER(transfer)->timeout); + r = timerfd_settime(ctx->timerfd, TFD_TIMER_ABSTIME, &it, NULL); + if (r < 0) + return LIBUSB_ERROR_OTHER; + return 1; + } + } + + return 0; +} +#else +static int disarm_timerfd(struct libusb_context *ctx) +{ + (void)ctx; + return 0; +} +static int arm_timerfd_for_next_timeout(struct libusb_context *ctx) +{ + (void)ctx; + return 0; +} +#endif + +/* Handle completion of a transfer (completion might be an error condition). + * This will invoke the user-supplied callback function, which may end up + * freeing the transfer. Therefore you cannot use the transfer structure + * after calling this function, and you should free all backend-specific + * data before calling it. + * Do not call this function with the usbi_transfer lock held. User-specified + * callback functions may attempt to directly resubmit the transfer, which + * will attempt to take the lock. */ +int usbi_handle_transfer_completion(struct usbi_transfer *itransfer, + enum libusb_transfer_status status) +{ + struct libusb_transfer *transfer = + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct libusb_context *ctx = TRANSFER_CTX(transfer); + uint8_t flags; + int r = 0; + + /* FIXME: could be more intelligent with the timerfd here. we don't need + * to disarm the timerfd if there was no timer running, and we only need + * to rearm the timerfd if the transfer that expired was the one with + * the shortest timeout. */ + + usbi_mutex_lock(&ctx->flying_transfers_lock); + /* FIXME: Sanity check for some race where this entry has already been + * removed! */ + if ((&itransfer->list)->next) + list_del(&itransfer->list); + if (usbi_using_timerfd(ctx)) { + r = arm_timerfd_for_next_timeout(ctx); + if (0 == r) + r = disarm_timerfd(ctx); + } + usbi_mutex_unlock(&ctx->flying_transfers_lock); + if (r < 0) + return r; + + if (status == LIBUSB_TRANSFER_COMPLETED + && transfer->flags & LIBUSB_TRANSFER_SHORT_NOT_OK) { + int rqlen = transfer->length; + if (transfer->type == LIBUSB_TRANSFER_TYPE_CONTROL) + rqlen -= LIBUSB_CONTROL_SETUP_SIZE; + if (rqlen != itransfer->transferred) { + usbi_dbg("interpreting short transfer as error"); + status = LIBUSB_TRANSFER_ERROR; + } + } + + flags = transfer->flags; + transfer->status = status; + transfer->actual_length = itransfer->transferred; + usbi_dbg("transfer %p has callback %p", transfer, transfer->callback); + if (transfer->callback) + transfer->callback(transfer); + /* transfer might have been freed by the above call, do not use from + * this point. */ + if (flags & LIBUSB_TRANSFER_FREE_TRANSFER) + libusb_free_transfer(transfer); + usbi_mutex_lock(&ctx->event_waiters_lock); + usbi_cond_broadcast(&ctx->event_waiters_cond); + usbi_mutex_unlock(&ctx->event_waiters_lock); + return 0; +} + +/* Similar to usbi_handle_transfer_completion() but exclusively for transfers + * that were asynchronously cancelled. The same concerns w.r.t. freeing of + * transfers exist here. + * Do not call this function with the usbi_transfer lock held. User-specified + * callback functions may attempt to directly resubmit the transfer, which + * will attempt to take the lock. */ +int usbi_handle_transfer_cancellation(struct usbi_transfer *transfer) +{ + /* if the URB was cancelled due to timeout, report timeout to the user */ + if (transfer->flags & USBI_TRANSFER_TIMED_OUT) { + usbi_dbg("detected timeout cancellation"); + return usbi_handle_transfer_completion(transfer, LIBUSB_TRANSFER_TIMED_OUT); + } + + /* otherwise its a normal async cancel */ + return usbi_handle_transfer_completion(transfer, LIBUSB_TRANSFER_CANCELLED); +} + +/** \ingroup poll + * Attempt to acquire the event handling lock. This lock is used to ensure that + * only one thread is monitoring libusb event sources at any one time. + * + * You only need to use this lock if you are developing an application + * which calls poll() or select() on libusb's file descriptors directly. + * If you stick to libusb's event handling loop functions (e.g. + * libusb_handle_events()) then you do not need to be concerned with this + * locking. + * + * While holding this lock, you are trusted to actually be handling events. + * If you are no longer handling events, you must call libusb_unlock_events() + * as soon as possible. + * + * \param ctx the context to operate on, or NULL for the default context + * \returns 0 if the lock was obtained successfully + * \returns 1 if the lock was not obtained (i.e. another thread holds the lock) + * \see \ref mtasync + */ +int API_EXPORTED libusb_try_lock_events(libusb_context *ctx) +{ + int r; + USBI_GET_CONTEXT(ctx); + + /* is someone else waiting to modify poll fds? if so, don't let this thread + * start event handling */ + usbi_mutex_lock(&ctx->pollfd_modify_lock); + r = ctx->pollfd_modify; + usbi_mutex_unlock(&ctx->pollfd_modify_lock); + if (r) { + usbi_dbg("someone else is modifying poll fds"); + return 1; + } + + r = usbi_mutex_trylock(&ctx->events_lock); + if (r) + return 1; + + ctx->event_handler_active = 1; + return 0; +} + +/** \ingroup poll + * Acquire the event handling lock, blocking until successful acquisition if + * it is contended. This lock is used to ensure that only one thread is + * monitoring libusb event sources at any one time. + * + * You only need to use this lock if you are developing an application + * which calls poll() or select() on libusb's file descriptors directly. + * If you stick to libusb's event handling loop functions (e.g. + * libusb_handle_events()) then you do not need to be concerned with this + * locking. + * + * While holding this lock, you are trusted to actually be handling events. + * If you are no longer handling events, you must call libusb_unlock_events() + * as soon as possible. + * + * \param ctx the context to operate on, or NULL for the default context + * \see \ref mtasync + */ +void API_EXPORTED libusb_lock_events(libusb_context *ctx) +{ + USBI_GET_CONTEXT(ctx); + usbi_mutex_lock(&ctx->events_lock); + ctx->event_handler_active = 1; +} + +/** \ingroup poll + * Release the lock previously acquired with libusb_try_lock_events() or + * libusb_lock_events(). Releasing this lock will wake up any threads blocked + * on libusb_wait_for_event(). + * + * \param ctx the context to operate on, or NULL for the default context + * \see \ref mtasync + */ +void API_EXPORTED libusb_unlock_events(libusb_context *ctx) +{ + USBI_GET_CONTEXT(ctx); + ctx->event_handler_active = 0; + usbi_mutex_unlock(&ctx->events_lock); + + /* FIXME: perhaps we should be a bit more efficient by not broadcasting + * the availability of the events lock when we are modifying pollfds + * (check ctx->pollfd_modify)? */ + usbi_mutex_lock(&ctx->event_waiters_lock); + usbi_cond_broadcast(&ctx->event_waiters_cond); + usbi_mutex_unlock(&ctx->event_waiters_lock); +} + +/** \ingroup poll + * Determine if it is still OK for this thread to be doing event handling. + * + * Sometimes, libusb needs to temporarily pause all event handlers, and this + * is the function you should use before polling file descriptors to see if + * this is the case. + * + * If this function instructs your thread to give up the events lock, you + * should just continue the usual logic that is documented in \ref mtasync. + * On the next iteration, your thread will fail to obtain the events lock, + * and will hence become an event waiter. + * + * This function should be called while the events lock is held: you don't + * need to worry about the results of this function if your thread is not + * the current event handler. + * + * \param ctx the context to operate on, or NULL for the default context + * \returns 1 if event handling can start or continue + * \returns 0 if this thread must give up the events lock + * \see \ref fullstory "Multi-threaded I/O: the full story" + */ +int API_EXPORTED libusb_event_handling_ok(libusb_context *ctx) +{ + int r; + USBI_GET_CONTEXT(ctx); + + /* is someone else waiting to modify poll fds? if so, don't let this thread + * continue event handling */ + usbi_mutex_lock(&ctx->pollfd_modify_lock); + r = ctx->pollfd_modify; + usbi_mutex_unlock(&ctx->pollfd_modify_lock); + if (r) { + usbi_dbg("someone else is modifying poll fds"); + return 0; + } + + return 1; +} + + +/** \ingroup poll + * Determine if an active thread is handling events (i.e. if anyone is holding + * the event handling lock). + * + * \param ctx the context to operate on, or NULL for the default context + * \returns 1 if a thread is handling events + * \returns 0 if there are no threads currently handling events + * \see \ref mtasync + */ +int API_EXPORTED libusb_event_handler_active(libusb_context *ctx) +{ + int r; + USBI_GET_CONTEXT(ctx); + + /* is someone else waiting to modify poll fds? if so, don't let this thread + * start event handling -- indicate that event handling is happening */ + usbi_mutex_lock(&ctx->pollfd_modify_lock); + r = ctx->pollfd_modify; + usbi_mutex_unlock(&ctx->pollfd_modify_lock); + if (r) { + usbi_dbg("someone else is modifying poll fds"); + return 1; + } + + return ctx->event_handler_active; +} + +/** \ingroup poll + * Acquire the event waiters lock. This lock is designed to be obtained under + * the situation where you want to be aware when events are completed, but + * some other thread is event handling so calling libusb_handle_events() is not + * allowed. + * + * You then obtain this lock, re-check that another thread is still handling + * events, then call libusb_wait_for_event(). + * + * You only need to use this lock if you are developing an application + * which calls poll() or select() on libusb's file descriptors directly, + * and may potentially be handling events from 2 threads simultaenously. + * If you stick to libusb's event handling loop functions (e.g. + * libusb_handle_events()) then you do not need to be concerned with this + * locking. + * + * \param ctx the context to operate on, or NULL for the default context + * \see \ref mtasync + */ +void API_EXPORTED libusb_lock_event_waiters(libusb_context *ctx) +{ + USBI_GET_CONTEXT(ctx); + usbi_mutex_lock(&ctx->event_waiters_lock); +} + +/** \ingroup poll + * Release the event waiters lock. + * \param ctx the context to operate on, or NULL for the default context + * \see \ref mtasync + */ +void API_EXPORTED libusb_unlock_event_waiters(libusb_context *ctx) +{ + USBI_GET_CONTEXT(ctx); + usbi_mutex_unlock(&ctx->event_waiters_lock); +} + +/** \ingroup poll + * Wait for another thread to signal completion of an event. Must be called + * with the event waiters lock held, see libusb_lock_event_waiters(). + * + * This function will block until any of the following conditions are met: + * -# The timeout expires + * -# A transfer completes + * -# A thread releases the event handling lock through libusb_unlock_events() + * + * Condition 1 is obvious. Condition 2 unblocks your thread after + * the callback for the transfer has completed. Condition 3 is important + * because it means that the thread that was previously handling events is no + * longer doing so, so if any events are to complete, another thread needs to + * step up and start event handling. + * + * This function releases the event waiters lock before putting your thread + * to sleep, and reacquires the lock as it is being woken up. + * + * \param ctx the context to operate on, or NULL for the default context + * \param tv maximum timeout for this blocking function. A NULL value + * indicates unlimited timeout. + * \returns 0 after a transfer completes or another thread stops event handling + * \returns 1 if the timeout expired + * \see \ref mtasync + */ +int API_EXPORTED libusb_wait_for_event(libusb_context *ctx, struct timeval *tv) +{ + struct timespec timeout; + int r; + + USBI_GET_CONTEXT(ctx); + if (tv == NULL) { + usbi_cond_wait(&ctx->event_waiters_cond, &ctx->event_waiters_lock); + return 0; + } + + r = usbi_backend->clock_gettime(USBI_CLOCK_REALTIME, &timeout); + if (r < 0) { + usbi_err(ctx, "failed to read realtime clock, error %d", errno); + return LIBUSB_ERROR_OTHER; + } + + timeout.tv_sec += tv->tv_sec; + timeout.tv_nsec += tv->tv_usec * 1000; + while (timeout.tv_nsec >= 1000000000) { + timeout.tv_nsec -= 1000000000; + timeout.tv_sec++; + } + + r = usbi_cond_timedwait(&ctx->event_waiters_cond, + &ctx->event_waiters_lock, &timeout); + return (r == ETIMEDOUT); +} + +static void handle_timeout(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + int r; + + itransfer->flags |= USBI_TRANSFER_TIMED_OUT; + r = libusb_cancel_transfer(transfer); + if (r < 0) + usbi_warn(TRANSFER_CTX(transfer), + "async cancel failed %d errno=%d", r, errno); +} + +static int handle_timeouts_locked(struct libusb_context *ctx) +{ + int r; + struct timespec systime_ts; + struct timeval systime; + struct usbi_transfer *transfer; + + if (list_empty(&ctx->flying_transfers)) + return 0; + + /* get current time */ + r = usbi_backend->clock_gettime(USBI_CLOCK_MONOTONIC, &systime_ts); + if (r < 0) + return r; + + TIMESPEC_TO_TIMEVAL(&systime, &systime_ts); + + /* iterate through flying transfers list, finding all transfers that + * have expired timeouts */ + list_for_each_entry(transfer, &ctx->flying_transfers, list, struct usbi_transfer) { + struct timeval *cur_tv = &transfer->timeout; + + /* if we've reached transfers of infinite timeout, we're all done */ + if (!timerisset(cur_tv)) + return 0; + + /* ignore timeouts we've already handled */ + if (transfer->flags & (USBI_TRANSFER_TIMED_OUT | USBI_TRANSFER_OS_HANDLES_TIMEOUT)) + continue; + + /* if transfer has non-expired timeout, nothing more to do */ + if ((cur_tv->tv_sec > systime.tv_sec) || + (cur_tv->tv_sec == systime.tv_sec && + cur_tv->tv_usec > systime.tv_usec)) + return 0; + + /* otherwise, we've got an expired timeout to handle */ + handle_timeout(transfer); + } + return 0; +} + +static int handle_timeouts(struct libusb_context *ctx) +{ + int r; + USBI_GET_CONTEXT(ctx); + usbi_mutex_lock(&ctx->flying_transfers_lock); + r = handle_timeouts_locked(ctx); + usbi_mutex_unlock(&ctx->flying_transfers_lock); + return r; +} + +#ifdef USBI_TIMERFD_AVAILABLE +static int handle_timerfd_trigger(struct libusb_context *ctx) +{ + int r; + + r = disarm_timerfd(ctx); + if (r < 0) + return r; + + usbi_mutex_lock(&ctx->flying_transfers_lock); + + /* process the timeout that just happened */ + r = handle_timeouts_locked(ctx); + if (r < 0) + goto out; + + /* arm for next timeout*/ + r = arm_timerfd_for_next_timeout(ctx); + +out: + usbi_mutex_unlock(&ctx->flying_transfers_lock); + return r; +} +#endif + +/* do the actual event handling. assumes that no other thread is concurrently + * doing the same thing. */ +static int handle_events(struct libusb_context *ctx, struct timeval *tv) +{ + int r; + struct usbi_pollfd *ipollfd; + POLL_NFDS_TYPE nfds = 0; + struct pollfd *fds; + int i = -1; + int timeout_ms; + + usbi_mutex_lock(&ctx->pollfds_lock); + list_for_each_entry(ipollfd, &ctx->pollfds, list, struct usbi_pollfd) + nfds++; + + /* TODO: malloc when number of fd's changes, not on every poll */ + fds = malloc(sizeof(*fds) * nfds); + if (!fds) { + usbi_mutex_unlock(&ctx->pollfds_lock); + return LIBUSB_ERROR_NO_MEM; + } + + list_for_each_entry(ipollfd, &ctx->pollfds, list, struct usbi_pollfd) { + struct libusb_pollfd *pollfd = &ipollfd->pollfd; + int fd = pollfd->fd; + i++; + fds[i].fd = fd; + fds[i].events = pollfd->events; + fds[i].revents = 0; + } + usbi_mutex_unlock(&ctx->pollfds_lock); + + timeout_ms = (tv->tv_sec * 1000) + (tv->tv_usec / 1000); + + /* round up to next millisecond */ + if (tv->tv_usec % 1000) + timeout_ms++; + + usbi_dbg("poll() %d fds with timeout in %dms", nfds, timeout_ms); + r = usbi_poll(fds, nfds, timeout_ms); + usbi_dbg("poll() returned %d", r); + if (r == 0) { + free(fds); + return handle_timeouts(ctx); + } else if (r == -1 && errno == EINTR) { + free(fds); + return LIBUSB_ERROR_INTERRUPTED; + } else if (r < 0) { + free(fds); + usbi_err(ctx, "poll failed %d err=%d\n", r, errno); + return LIBUSB_ERROR_IO; + } + + /* fd[0] is always the ctrl pipe */ + if (fds[0].revents) { + /* another thread wanted to interrupt event handling, and it succeeded! + * handle any other events that cropped up at the same time, and + * simply return */ + usbi_dbg("caught a fish on the control pipe"); + + if (r == 1) { + r = 0; + goto handled; + } else { + /* prevent OS backend from trying to handle events on ctrl pipe */ + fds[0].revents = 0; + r--; + } + } + + /* fd[1] is always the hotplug pipe */ + if (libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG) && fds[1].revents) { + libusb_hotplug_message message; + unsigned int ret; + + /* read the message from the hotplug thread */ + ret = read(ctx->hotplug_pipe[0], &message, sizeof (message)); + if (ret < sizeof(message)) { + ret = LIBUSB_ERROR_OTHER; + goto handled; + } + + usbi_hotplug_match(message.device, message.event); + + /* the device left. dereference the device */ + if (LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT == message.event) + libusb_unref_device(message.device); + + fds[1].revents = 0; + if (1 == r--) + goto handled; + } /* else there shouldn't be anything on this pipe */ + +#ifdef USBI_TIMERFD_AVAILABLE + /* on timerfd configurations, fds[2] is the timerfd */ + if (usbi_using_timerfd(ctx) && fds[2].revents) { + /* timerfd indicates that a timeout has expired */ + int ret; + usbi_dbg("timerfd triggered"); + + ret = handle_timerfd_trigger(ctx); + if (ret < 0) { + /* return error code */ + r = ret; + goto handled; + } else if (r == 1) { + /* no more active file descriptors, nothing more to do */ + r = 0; + goto handled; + } else { + /* more events pending... + * prevent OS backend from trying to handle events on timerfd */ + fds[2].revents = 0; + r--; + } + } +#endif + + r = usbi_backend->handle_events(ctx, fds, nfds, r); + if (r) + usbi_err(ctx, "backend handle_events failed with error %d", r); + +handled: + free(fds); + return r; +} + +/* returns the smallest of: + * 1. timeout of next URB + * 2. user-supplied timeout + * returns 1 if there is an already-expired timeout, otherwise returns 0 + * and populates out + */ +static int get_next_timeout(libusb_context *ctx, struct timeval *tv, + struct timeval *out) +{ + struct timeval timeout; + int r = libusb_get_next_timeout(ctx, &timeout); + if (r) { + /* timeout already expired? */ + if (!timerisset(&timeout)) + return 1; + + /* choose the smallest of next URB timeout or user specified timeout */ + if (timercmp(&timeout, tv, <)) + *out = timeout; + else + *out = *tv; + } else { + *out = *tv; + } + return 0; +} + +/** \ingroup poll + * Handle any pending events. + * + * libusb determines "pending events" by checking if any timeouts have expired + * and by checking the set of file descriptors for activity. + * + * If a zero timeval is passed, this function will handle any already-pending + * events and then immediately return in non-blocking style. + * + * If a non-zero timeval is passed and no events are currently pending, this + * function will block waiting for events to handle up until the specified + * timeout. If an event arrives or a signal is raised, this function will + * return early. + * + * If the parameter completed is not NULL then after obtaining the event + * handling lock this function will return immediately if the integer + * pointed to is not 0. This allows for race free waiting for the completion + * of a specific transfer. + * + * \param ctx the context to operate on, or NULL for the default context + * \param tv the maximum time to block waiting for events, or an all zero + * timeval struct for non-blocking mode + * \param completed pointer to completion integer to check, or NULL + * \returns 0 on success, or a LIBUSB_ERROR code on failure + * \see \ref mtasync + */ +int API_EXPORTED libusb_handle_events_timeout_completed(libusb_context *ctx, + struct timeval *tv, int *completed) +{ + int r; + struct timeval poll_timeout; + + USBI_GET_CONTEXT(ctx); + r = get_next_timeout(ctx, tv, &poll_timeout); + if (r) { + /* timeout already expired */ + return handle_timeouts(ctx); + } + +retry: + if (libusb_try_lock_events(ctx) == 0) { + if (completed == NULL || !*completed) { + /* we obtained the event lock: do our own event handling */ + usbi_dbg("doing our own event handling"); + r = handle_events(ctx, &poll_timeout); + } + libusb_unlock_events(ctx); + return r; + } + + /* another thread is doing event handling. wait for thread events that + * notify event completion. */ + libusb_lock_event_waiters(ctx); + + if (completed && *completed) + goto already_done; + + if (!libusb_event_handler_active(ctx)) { + /* we hit a race: whoever was event handling earlier finished in the + * time it took us to reach this point. try the cycle again. */ + libusb_unlock_event_waiters(ctx); + usbi_dbg("event handler was active but went away, retrying"); + goto retry; + } + + usbi_dbg("another thread is doing event handling"); + r = libusb_wait_for_event(ctx, &poll_timeout); + +already_done: + libusb_unlock_event_waiters(ctx); + + if (r < 0) + return r; + else if (r == 1) + return handle_timeouts(ctx); + else + return 0; +} + +/** \ingroup poll + * Handle any pending events + * + * Like libusb_handle_events_timeout_completed(), but without the completed + * parameter, calling this function is equivalent to calling + * libusb_handle_events_timeout_completed() with a NULL completed parameter. + * + * This function is kept primarily for backwards compatibility. + * All new code should call libusb_handle_events_completed() or + * libusb_handle_events_timeout_completed() to avoid race conditions. + * + * \param ctx the context to operate on, or NULL for the default context + * \param tv the maximum time to block waiting for events, or an all zero + * timeval struct for non-blocking mode + * \returns 0 on success, or a LIBUSB_ERROR code on failure + */ +int API_EXPORTED libusb_handle_events_timeout(libusb_context *ctx, + struct timeval *tv) +{ + return libusb_handle_events_timeout_completed(ctx, tv, NULL); +} + +/** \ingroup poll + * Handle any pending events in blocking mode. There is currently a timeout + * hardcoded at 60 seconds but we plan to make it unlimited in future. For + * finer control over whether this function is blocking or non-blocking, or + * for control over the timeout, use libusb_handle_events_timeout_completed() + * instead. + * + * This function is kept primarily for backwards compatibility. + * All new code should call libusb_handle_events_completed() or + * libusb_handle_events_timeout_completed() to avoid race conditions. + * + * \param ctx the context to operate on, or NULL for the default context + * \returns 0 on success, or a LIBUSB_ERROR code on failure + */ +int API_EXPORTED libusb_handle_events(libusb_context *ctx) +{ + struct timeval tv; + tv.tv_sec = 60; + tv.tv_usec = 0; + return libusb_handle_events_timeout_completed(ctx, &tv, NULL); +} + +/** \ingroup poll + * Handle any pending events in blocking mode. + * + * Like libusb_handle_events(), with the addition of a completed parameter + * to allow for race free waiting for the completion of a specific transfer. + * + * See libusb_handle_events_timeout_completed() for details on the completed + * parameter. + * + * \param ctx the context to operate on, or NULL for the default context + * \param completed pointer to completion integer to check, or NULL + * \returns 0 on success, or a LIBUSB_ERROR code on failure + * \see \ref mtasync + */ +int API_EXPORTED libusb_handle_events_completed(libusb_context *ctx, + int *completed) +{ + struct timeval tv; + tv.tv_sec = 60; + tv.tv_usec = 0; + return libusb_handle_events_timeout_completed(ctx, &tv, completed); +} + +/** \ingroup poll + * Handle any pending events by polling file descriptors, without checking if + * any other threads are already doing so. Must be called with the event lock + * held, see libusb_lock_events(). + * + * This function is designed to be called under the situation where you have + * taken the event lock and are calling poll()/select() directly on libusb's + * file descriptors (as opposed to using libusb_handle_events() or similar). + * You detect events on libusb's descriptors, so you then call this function + * with a zero timeout value (while still holding the event lock). + * + * \param ctx the context to operate on, or NULL for the default context + * \param tv the maximum time to block waiting for events, or zero for + * non-blocking mode + * \returns 0 on success, or a LIBUSB_ERROR code on failure + * \see \ref mtasync + */ +int API_EXPORTED libusb_handle_events_locked(libusb_context *ctx, + struct timeval *tv) +{ + int r; + struct timeval poll_timeout; + + USBI_GET_CONTEXT(ctx); + r = get_next_timeout(ctx, tv, &poll_timeout); + if (r) { + /* timeout already expired */ + return handle_timeouts(ctx); + } + + return handle_events(ctx, &poll_timeout); +} + +/** \ingroup poll + * Determines whether your application must apply special timing considerations + * when monitoring libusb's file descriptors. + * + * This function is only useful for applications which retrieve and poll + * libusb's file descriptors in their own main loop (\ref pollmain). + * + * Ordinarily, libusb's event handler needs to be called into at specific + * moments in time (in addition to times when there is activity on the file + * descriptor set). The usual approach is to use libusb_get_next_timeout() + * to learn about when the next timeout occurs, and to adjust your + * poll()/select() timeout accordingly so that you can make a call into the + * library at that time. + * + * Some platforms supported by libusb do not come with this baggage - any + * events relevant to timing will be represented by activity on the file + * descriptor set, and libusb_get_next_timeout() will always return 0. + * This function allows you to detect whether you are running on such a + * platform. + * + * Since v1.0.5. + * + * \param ctx the context to operate on, or NULL for the default context + * \returns 0 if you must call into libusb at times determined by + * libusb_get_next_timeout(), or 1 if all timeout events are handled internally + * or through regular activity on the file descriptors. + * \see \ref pollmain "Polling libusb file descriptors for event handling" + */ +int API_EXPORTED libusb_pollfds_handle_timeouts(libusb_context *ctx) +{ +#if defined(USBI_TIMERFD_AVAILABLE) + USBI_GET_CONTEXT(ctx); + return usbi_using_timerfd(ctx); +#else + (void)ctx; + return 0; +#endif +} + +/** \ingroup poll + * Determine the next internal timeout that libusb needs to handle. You only + * need to use this function if you are calling poll() or select() or similar + * on libusb's file descriptors yourself - you do not need to use it if you + * are calling libusb_handle_events() or a variant directly. + * + * You should call this function in your main loop in order to determine how + * long to wait for select() or poll() to return results. libusb needs to be + * called into at this timeout, so you should use it as an upper bound on + * your select() or poll() call. + * + * When the timeout has expired, call into libusb_handle_events_timeout() + * (perhaps in non-blocking mode) so that libusb can handle the timeout. + * + * This function may return 1 (success) and an all-zero timeval. If this is + * the case, it indicates that libusb has a timeout that has already expired + * so you should call libusb_handle_events_timeout() or similar immediately. + * A return code of 0 indicates that there are no pending timeouts. + * + * On some platforms, this function will always returns 0 (no pending + * timeouts). See \ref polltime. + * + * \param ctx the context to operate on, or NULL for the default context + * \param tv output location for a relative time against the current + * clock in which libusb must be called into in order to process timeout events + * \returns 0 if there are no pending timeouts, 1 if a timeout was returned, + * or LIBUSB_ERROR_OTHER on failure + */ +int API_EXPORTED libusb_get_next_timeout(libusb_context *ctx, + struct timeval *tv) +{ + struct usbi_transfer *transfer; + struct timespec cur_ts; + struct timeval cur_tv; + struct timeval *next_timeout; + int r; + int found = 0; + + USBI_GET_CONTEXT(ctx); + if (usbi_using_timerfd(ctx)) + return 0; + + usbi_mutex_lock(&ctx->flying_transfers_lock); + if (list_empty(&ctx->flying_transfers)) { + usbi_mutex_unlock(&ctx->flying_transfers_lock); + usbi_dbg("no URBs, no timeout!"); + return 0; + } + + /* find next transfer which hasn't already been processed as timed out */ + list_for_each_entry(transfer, &ctx->flying_transfers, list, struct usbi_transfer) { + if (transfer->flags & (USBI_TRANSFER_TIMED_OUT | USBI_TRANSFER_OS_HANDLES_TIMEOUT)) + continue; + + /* no timeout for this transfer? */ + if (!timerisset(&transfer->timeout)) + continue; + + found = 1; + break; + } + usbi_mutex_unlock(&ctx->flying_transfers_lock); + + if (!found) { + usbi_dbg("no URB with timeout or all handled by OS; no timeout!"); + return 0; + } + + next_timeout = &transfer->timeout; + + r = usbi_backend->clock_gettime(USBI_CLOCK_MONOTONIC, &cur_ts); + if (r < 0) { + usbi_err(ctx, "failed to read monotonic clock, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } + TIMESPEC_TO_TIMEVAL(&cur_tv, &cur_ts); + + if (!timercmp(&cur_tv, next_timeout, <)) { + usbi_dbg("first timeout already expired"); + timerclear(tv); + } else { + timersub(next_timeout, &cur_tv, tv); + usbi_dbg("next timeout in %d.%06ds", tv->tv_sec, tv->tv_usec); + } + + return 1; +} + +/** \ingroup poll + * Register notification functions for file descriptor additions/removals. + * These functions will be invoked for every new or removed file descriptor + * that libusb uses as an event source. + * + * To remove notifiers, pass NULL values for the function pointers. + * + * Note that file descriptors may have been added even before you register + * these notifiers (e.g. at libusb_init() time). + * + * Additionally, note that the removal notifier may be called during + * libusb_exit() (e.g. when it is closing file descriptors that were opened + * and added to the poll set at libusb_init() time). If you don't want this, + * remove the notifiers immediately before calling libusb_exit(). + * + * \param ctx the context to operate on, or NULL for the default context + * \param added_cb pointer to function for addition notifications + * \param removed_cb pointer to function for removal notifications + * \param user_data User data to be passed back to callbacks (useful for + * passing context information) + */ +void API_EXPORTED libusb_set_pollfd_notifiers(libusb_context *ctx, + libusb_pollfd_added_cb added_cb, libusb_pollfd_removed_cb removed_cb, + void *user_data) +{ + USBI_GET_CONTEXT(ctx); + ctx->fd_added_cb = added_cb; + ctx->fd_removed_cb = removed_cb; + ctx->fd_cb_user_data = user_data; +} + +/* Add a file descriptor to the list of file descriptors to be monitored. + * events should be specified as a bitmask of events passed to poll(), e.g. + * POLLIN and/or POLLOUT. */ +int usbi_add_pollfd(struct libusb_context *ctx, int fd, short events) +{ + struct usbi_pollfd *ipollfd = malloc(sizeof(*ipollfd)); + if (!ipollfd) + return LIBUSB_ERROR_NO_MEM; + + usbi_dbg("add fd %d events %d", fd, events); + ipollfd->pollfd.fd = fd; + ipollfd->pollfd.events = events; + usbi_mutex_lock(&ctx->pollfds_lock); + list_add_tail(&ipollfd->list, &ctx->pollfds); + usbi_mutex_unlock(&ctx->pollfds_lock); + + if (ctx->fd_added_cb) + ctx->fd_added_cb(fd, events, ctx->fd_cb_user_data); + return 0; +} + +/* Remove a file descriptor from the list of file descriptors to be polled. */ +void usbi_remove_pollfd(struct libusb_context *ctx, int fd) +{ + struct usbi_pollfd *ipollfd; + int found = 0; + + usbi_dbg("remove fd %d", fd); + usbi_mutex_lock(&ctx->pollfds_lock); + list_for_each_entry(ipollfd, &ctx->pollfds, list, struct usbi_pollfd) + if (ipollfd->pollfd.fd == fd) { + found = 1; + break; + } + + if (!found) { + usbi_dbg("couldn't find fd %d to remove", fd); + usbi_mutex_unlock(&ctx->pollfds_lock); + return; + } + + list_del(&ipollfd->list); + usbi_mutex_unlock(&ctx->pollfds_lock); + free(ipollfd); + if (ctx->fd_removed_cb) + ctx->fd_removed_cb(fd, ctx->fd_cb_user_data); +} + +/** \ingroup poll + * Retrieve a list of file descriptors that should be polled by your main loop + * as libusb event sources. + * + * The returned list is NULL-terminated and should be freed with free() when + * done. The actual list contents must not be touched. + * + * As file descriptors are a Unix-specific concept, this function is not + * available on Windows and will always return NULL. + * + * \param ctx the context to operate on, or NULL for the default context + * \returns a NULL-terminated list of libusb_pollfd structures + * \returns NULL on error + * \returns NULL on platforms where the functionality is not available + */ +DEFAULT_VISIBILITY +const struct libusb_pollfd ** LIBUSB_CALL libusb_get_pollfds( + libusb_context *ctx) +{ +#ifndef OS_WINDOWS + struct libusb_pollfd **ret = NULL; + struct usbi_pollfd *ipollfd; + size_t i = 0; + size_t cnt = 0; + USBI_GET_CONTEXT(ctx); + + usbi_mutex_lock(&ctx->pollfds_lock); + list_for_each_entry(ipollfd, &ctx->pollfds, list, struct usbi_pollfd) + cnt++; + + ret = calloc(cnt + 1, sizeof(struct libusb_pollfd *)); + if (!ret) + goto out; + + list_for_each_entry(ipollfd, &ctx->pollfds, list, struct usbi_pollfd) + ret[i++] = (struct libusb_pollfd *) ipollfd; + ret[cnt] = NULL; + +out: + usbi_mutex_unlock(&ctx->pollfds_lock); + return (const struct libusb_pollfd **) ret; +#else + usbi_err(ctx, "external polling of libusb's internal descriptors "\ + "is not yet supported on Windows platforms"); + return NULL; +#endif +} + +/* Backends call this from handle_events to report disconnection of a device. + * The transfers get cancelled appropriately. + */ +void usbi_handle_disconnect(struct libusb_device_handle *handle) +{ + struct usbi_transfer *cur; + struct usbi_transfer *to_cancel; + + usbi_dbg("device %d.%d", + handle->dev->bus_number, handle->dev->device_address); + + /* terminate all pending transfers with the LIBUSB_TRANSFER_NO_DEVICE + * status code. + * + * this is a bit tricky because: + * 1. we can't do transfer completion while holding flying_transfers_lock + * 2. the transfers list can change underneath us - if we were to build a + * list of transfers to complete (while holding look), the situation + * might be different by the time we come to free them + * + * so we resort to a loop-based approach as below + * FIXME: is this still potentially racy? + */ + + while (1) { + usbi_mutex_lock(&HANDLE_CTX(handle)->flying_transfers_lock); + to_cancel = NULL; + list_for_each_entry(cur, &HANDLE_CTX(handle)->flying_transfers, list, struct usbi_transfer) + if (USBI_TRANSFER_TO_LIBUSB_TRANSFER(cur)->dev_handle == handle) { + to_cancel = cur; + break; + } + usbi_mutex_unlock(&HANDLE_CTX(handle)->flying_transfers_lock); + + if (!to_cancel) + break; + + usbi_backend->clear_transfer_priv(to_cancel); + usbi_handle_transfer_completion(to_cancel, LIBUSB_TRANSFER_NO_DEVICE); + } + +} diff --git a/compat/libusb-1.0/libusb/libusb-1.0.def b/compat/libusb-1.0/libusb/libusb-1.0.def new file mode 100644 index 0000000..1d6a5d2 --- /dev/null +++ b/compat/libusb-1.0/libusb/libusb-1.0.def @@ -0,0 +1,120 @@ +LIBRARY +EXPORTS + libusb_alloc_transfer + libusb_alloc_transfer@4 = libusb_alloc_transfer + libusb_attach_kernel_driver + libusb_attach_kernel_driver@8 = libusb_attach_kernel_driver + libusb_bulk_transfer + libusb_bulk_transfer@24 = libusb_bulk_transfer + libusb_cancel_transfer + libusb_cancel_transfer@4 = libusb_cancel_transfer + libusb_claim_interface + libusb_claim_interface@8 = libusb_claim_interface + libusb_clear_halt + libusb_clear_halt@8 = libusb_clear_halt + libusb_close + libusb_close@4 = libusb_close + libusb_control_transfer + libusb_control_transfer@32 = libusb_control_transfer + libusb_detach_kernel_driver + libusb_detach_kernel_driver@8 = libusb_detach_kernel_driver + libusb_error_name + libusb_error_name@4 = libusb_error_name + libusb_event_handler_active + libusb_event_handler_active@4 = libusb_event_handler_active + libusb_event_handling_ok + libusb_event_handling_ok@4 = libusb_event_handling_ok + libusb_exit + libusb_exit@4 = libusb_exit + libusb_free_config_descriptor + libusb_free_config_descriptor@4 = libusb_free_config_descriptor + libusb_free_device_list + libusb_free_device_list@8 = libusb_free_device_list + libusb_free_transfer + libusb_free_transfer@4 = libusb_free_transfer + libusb_get_active_config_descriptor + libusb_get_active_config_descriptor@8 = libusb_get_active_config_descriptor + libusb_get_bus_number + libusb_get_bus_number@4 = libusb_get_bus_number + libusb_get_config_descriptor + libusb_get_config_descriptor@12 = libusb_get_config_descriptor + libusb_get_config_descriptor_by_value + libusb_get_config_descriptor_by_value@12 = libusb_get_config_descriptor_by_value + libusb_get_configuration + libusb_get_configuration@8 = libusb_get_configuration + libusb_get_device + libusb_get_device@4 = libusb_get_device + libusb_get_device_address + libusb_get_device_address@4 = libusb_get_device_address + libusb_get_device_descriptor + libusb_get_device_descriptor@8 = libusb_get_device_descriptor + libusb_get_device_list + libusb_get_device_list@8 = libusb_get_device_list + libusb_get_device_speed + libusb_get_device_speed@4 = libusb_get_device_speed + libusb_get_max_iso_packet_size + libusb_get_max_iso_packet_size@8 = libusb_get_max_iso_packet_size + libusb_get_max_packet_size + libusb_get_max_packet_size@8 = libusb_get_max_packet_size + libusb_get_next_timeout + libusb_get_next_timeout@8 = libusb_get_next_timeout + libusb_get_pollfds + libusb_get_pollfds@4 = libusb_get_pollfds + libusb_get_string_descriptor_ascii + libusb_get_string_descriptor_ascii@16 = libusb_get_string_descriptor_ascii + libusb_get_version + libusb_get_version@0 = libusb_get_version + libusb_handle_events + libusb_handle_events@4 = libusb_handle_events + libusb_handle_events_completed + libusb_handle_events_completed@8 = libusb_handle_events_completed + libusb_handle_events_locked + libusb_handle_events_locked@8 = libusb_handle_events_locked + libusb_handle_events_timeout + libusb_handle_events_timeout@8 = libusb_handle_events_timeout + libusb_handle_events_timeout_completed + libusb_handle_events_timeout_completed@12 = libusb_handle_events_timeout_completed + libusb_has_capability + libusb_has_capability@4 = libusb_has_capability + libusb_init + libusb_init@4 = libusb_init + libusb_interrupt_transfer + libusb_interrupt_transfer@24 = libusb_interrupt_transfer + libusb_kernel_driver_active + libusb_kernel_driver_active@8 = libusb_kernel_driver_active + libusb_lock_event_waiters + libusb_lock_event_waiters@4 = libusb_lock_event_waiters + libusb_lock_events + libusb_lock_events@4 = libusb_lock_events + libusb_open + libusb_open@8 = libusb_open + libusb_open_device_with_vid_pid + libusb_open_device_with_vid_pid@12 = libusb_open_device_with_vid_pid + libusb_pollfds_handle_timeouts + libusb_pollfds_handle_timeouts@4 = libusb_pollfds_handle_timeouts + libusb_ref_device + libusb_ref_device@4 = libusb_ref_device + libusb_release_interface + libusb_release_interface@8 = libusb_release_interface + libusb_reset_device + libusb_reset_device@4 = libusb_reset_device + libusb_set_configuration + libusb_set_configuration@8 = libusb_set_configuration + libusb_set_debug + libusb_set_debug@8 = libusb_set_debug + libusb_set_interface_alt_setting + libusb_set_interface_alt_setting@12 = libusb_set_interface_alt_setting + libusb_set_pollfd_notifiers + libusb_set_pollfd_notifiers@16 = libusb_set_pollfd_notifiers + libusb_submit_transfer + libusb_submit_transfer@4 = libusb_submit_transfer + libusb_try_lock_events + libusb_try_lock_events@4 = libusb_try_lock_events + libusb_unlock_event_waiters + libusb_unlock_event_waiters@4 = libusb_unlock_event_waiters + libusb_unlock_events + libusb_unlock_events@4 = libusb_unlock_events + libusb_unref_device + libusb_unref_device@4 = libusb_unref_device + libusb_wait_for_event + libusb_wait_for_event@8 = libusb_wait_for_event diff --git a/compat/libusb-1.0/libusb/libusb-1.0.rc b/compat/libusb-1.0/libusb/libusb-1.0.rc new file mode 100644 index 0000000..a59a430 --- /dev/null +++ b/compat/libusb-1.0/libusb/libusb-1.0.rc @@ -0,0 +1,56 @@ +/* + * For Windows: input this file to the Resoure Compiler to produce a binary + * .res file. This is then embedded in the resultant library (like any other + * compilation object). + * The information can then be queried using standard APIs and can also be + * viewed with utilities such as Windows Explorer. + */ +#include "winresrc.h" + +#include "version.h" +#ifndef LIBUSB_VERSIONSTRING +#define LU_STR(s) #s +#define LU_XSTR(s) LU_STR(s) +#if LIBUSB_NANO > 0 +#define LIBUSB_VERSIONSTRING LU_XSTR(LIBUSB_MAJOR) "." LU_XSTR(LIBUSB_MINOR) "." LU_XSTR(LIBUSB_MICRO) "." LU_XSTR(LIBUSB_NANO) LIBUSB_RC "\0" +#else +#define LIBUSB_VERSIONSTRING LU_XSTR(LIBUSB_MAJOR) "." LU_XSTR(LIBUSB_MINOR) "." LU_XSTR(LIBUSB_MICRO) LIBUSB_RC "\0" +#endif +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION LIBUSB_MAJOR,LIBUSB_MINOR,LIBUSB_MICRO,LIBUSB_NANO + PRODUCTVERSION LIBUSB_MAJOR,LIBUSB_MINOR,LIBUSB_MICRO,LIBUSB_NANO + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "Comments", "\0" + VALUE "CompanyName", "libusb.org\0" + VALUE "FileDescription", "C library for writing portable USB drivers in userspace\0" + VALUE "FileVersion", LIBUSB_VERSIONSTRING + VALUE "InternalName", "libusb\0" + VALUE "LegalCopyright", "See individual source files, GNU LGPL v2.1 or later.\0" + VALUE "LegalTrademarks", "http://www.gnu.org/licenses/lgpl-2.1.html\0" + VALUE "OriginalFilename", "libusb-1.0.dll\0" + VALUE "PrivateBuild", "\0" + VALUE "ProductName", "libusb-1.0\0" + VALUE "ProductVersion", LIBUSB_VERSIONSTRING + VALUE "SpecialBuild", "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/compat/libusb-1.0/libusb/libusb.h b/compat/libusb-1.0/libusb/libusb.h new file mode 100644 index 0000000..f3a8f38 --- /dev/null +++ b/compat/libusb-1.0/libusb/libusb.h @@ -0,0 +1,1779 @@ +/* + * Public libusb header file + * Copyright (C) 2007-2008 Daniel Drake + * Copyright (c) 2001 Johannes Erdfelt + * Copyright (C) 2012-2013 Nathan Hjelm + * Copyright (C) 2012 Peter Stuge + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef LIBUSB_H +#define LIBUSB_H + +#ifdef _MSC_VER +/* on MS environments, the inline keyword is available in C++ only */ +#define inline __inline +/* ssize_t is also not available (copy/paste from MinGW) */ +#ifndef _SSIZE_T_DEFINED +#define _SSIZE_T_DEFINED +#undef ssize_t +#ifdef _WIN64 + typedef __int64 ssize_t; +#else + typedef int ssize_t; +#endif /* _WIN64 */ +#endif /* _SSIZE_T_DEFINED */ +#endif /* _MSC_VER */ + +/* stdint.h is also not usually available on MS */ +#if defined(_MSC_VER) && (_MSC_VER < 1600) && (!defined(_STDINT)) && (!defined(_STDINT_H)) +typedef unsigned __int8 uint8_t; +typedef unsigned __int16 uint16_t; +typedef unsigned __int32 uint32_t; +#else +#include +#endif + +#include +#include +#include + +#if defined(__linux) || defined(__APPLE__) || defined(__CYGWIN__) +#include +#endif + +/* 'interface' might be defined as a macro on Windows, so we need to + * undefine it so as not to break the current libusb API, because + * libusb_config_descriptor has an 'interface' member + * As this can be problematic if you include windows.h after libusb.h + * in your sources, we force windows.h to be included first. */ +#if defined(_WIN32) || defined(__CYGWIN__) +#include +#if defined(interface) +#undef interface +#endif +#endif + +/** \def LIBUSB_CALL + * \ingroup misc + * libusb's Windows calling convention. + * + * Under Windows, the selection of available compilers and configurations + * means that, unlike other platforms, there is not one true calling + * convention (calling convention: the manner in which parameters are + * passed to funcions in the generated assembly code). + * + * Matching the Windows API itself, libusb uses the WINAPI convention (which + * translates to the stdcall convention) and guarantees that the + * library is compiled in this way. The public header file also includes + * appropriate annotations so that your own software will use the right + * convention, even if another convention is being used by default within + * your codebase. + * + * The one consideration that you must apply in your software is to mark + * all functions which you use as libusb callbacks with this LIBUSB_CALL + * annotation, so that they too get compiled for the correct calling + * convention. + * + * On non-Windows operating systems, this macro is defined as nothing. This + * means that you can apply it to your code without worrying about + * cross-platform compatibility. + */ +/* LIBUSB_CALL must be defined on both definition and declaration of libusb + * functions. You'd think that declaration would be enough, but cygwin will + * complain about conflicting types unless both are marked this way. + * The placement of this macro is important too; it must appear after the + * return type, before the function name. See internal documentation for + * API_EXPORTED. + */ +#if defined(_WIN32) || defined(__CYGWIN__) +#define LIBUSB_CALL WINAPI +#else +#define LIBUSB_CALL +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** \def libusb_cpu_to_le16 + * \ingroup misc + * Convert a 16-bit value from host-endian to little-endian format. On + * little endian systems, this function does nothing. On big endian systems, + * the bytes are swapped. + * \param x the host-endian value to convert + * \returns the value in little-endian byte order + */ +static inline uint16_t libusb_cpu_to_le16(const uint16_t x) +{ + union { + uint8_t b8[2]; + uint16_t b16; + } _tmp; + _tmp.b8[1] = x >> 8; + _tmp.b8[0] = x & 0xff; + return _tmp.b16; +} + +/** \def libusb_le16_to_cpu + * \ingroup misc + * Convert a 16-bit value from little-endian to host-endian format. On + * little endian systems, this function does nothing. On big endian systems, + * the bytes are swapped. + * \param x the little-endian value to convert + * \returns the value in host-endian byte order + */ +#define libusb_le16_to_cpu libusb_cpu_to_le16 + +/* standard USB stuff */ + +/** \ingroup desc + * Device and/or Interface Class codes */ +enum libusb_class_code { + /** In the context of a \ref libusb_device_descriptor "device descriptor", + * this bDeviceClass value indicates that each interface specifies its + * own class information and all interfaces operate independently. + */ + LIBUSB_CLASS_PER_INTERFACE = 0, + + /** Audio class */ + LIBUSB_CLASS_AUDIO = 1, + + /** Communications class */ + LIBUSB_CLASS_COMM = 2, + + /** Human Interface Device class */ + LIBUSB_CLASS_HID = 3, + + /** Physical */ + LIBUSB_CLASS_PHYSICAL = 5, + + /** Printer class */ + LIBUSB_CLASS_PRINTER = 7, + + /** Image class */ + LIBUSB_CLASS_PTP = 6, /* legacy name from libusb-0.1 usb.h */ + LIBUSB_CLASS_IMAGE = 6, + + /** Mass storage class */ + LIBUSB_CLASS_MASS_STORAGE = 8, + + /** Hub class */ + LIBUSB_CLASS_HUB = 9, + + /** Data class */ + LIBUSB_CLASS_DATA = 10, + + /** Smart Card */ + LIBUSB_CLASS_SMART_CARD = 0x0b, + + /** Content Security */ + LIBUSB_CLASS_CONTENT_SECURITY = 0x0d, + + /** Video */ + LIBUSB_CLASS_VIDEO = 0x0e, + + /** Personal Healthcare */ + LIBUSB_CLASS_PERSONAL_HEALTHCARE = 0x0f, + + /** Diagnostic Device */ + LIBUSB_CLASS_DIAGNOSTIC_DEVICE = 0xdc, + + /** Wireless class */ + LIBUSB_CLASS_WIRELESS = 0xe0, + + /** Application class */ + LIBUSB_CLASS_APPLICATION = 0xfe, + + /** Class is vendor-specific */ + LIBUSB_CLASS_VENDOR_SPEC = 0xff +}; + +/** \ingroup desc + * Descriptor types as defined by the USB specification. */ +enum libusb_descriptor_type { + /** Device descriptor. See libusb_device_descriptor. */ + LIBUSB_DT_DEVICE = 0x01, + + /** Configuration descriptor. See libusb_config_descriptor. */ + LIBUSB_DT_CONFIG = 0x02, + + /** String descriptor */ + LIBUSB_DT_STRING = 0x03, + + /** Interface descriptor. See libusb_interface_descriptor. */ + LIBUSB_DT_INTERFACE = 0x04, + + /** Endpoint descriptor. See libusb_endpoint_descriptor. */ + LIBUSB_DT_ENDPOINT = 0x05, + + /** HID descriptor */ + LIBUSB_DT_HID = 0x21, + + /** HID report descriptor */ + LIBUSB_DT_REPORT = 0x22, + + /** Physical descriptor */ + LIBUSB_DT_PHYSICAL = 0x23, + + /** Hub descriptor */ + LIBUSB_DT_HUB = 0x29, + + /** BOS descriptor */ + LIBUSB_DT_BOS = 0x0f, + + /** Device Capability descriptor */ + LIBUSB_DT_DEVICE_CAPABILITY = 0x10, + + /** SuperSpeed Endpoint Companion descriptor */ + LIBUSB_DT_SS_ENDPOINT_COMPANION = 0x30 +}; + +/* Descriptor sizes per descriptor type */ +#define LIBUSB_DT_DEVICE_SIZE 18 +#define LIBUSB_DT_CONFIG_SIZE 9 +#define LIBUSB_DT_INTERFACE_SIZE 9 +#define LIBUSB_DT_ENDPOINT_SIZE 7 +#define LIBUSB_DT_ENDPOINT_AUDIO_SIZE 9 /* Audio extension */ +#define LIBUSB_DT_HUB_NONVAR_SIZE 7 +#define LIBUSB_DT_SS_ENDPOINT_COMPANION_SIZE 6 +#define LIBUSB_DT_BOS_SIZE 5 +#define LIBUSB_USB_2_0_EXTENSION_DEVICE_CAPABILITY_SIZE 7 +#define LIBUSB_SS_USB_DEVICE_CAPABILITY_SIZE 10 +#define LIBUSB_DT_BOS_MAX_SIZE ((LIBUSB_DT_BOS_SIZE) + \ + (LIBUSB_USB_2_0_EXTENSION_DEVICE_CAPABILITY_SIZE) + \ + (LIBUSB_SS_USB_DEVICE_CAPABILITY_SIZE)) + +#define LIBUSB_ENDPOINT_ADDRESS_MASK 0x0f /* in bEndpointAddress */ +#define LIBUSB_ENDPOINT_DIR_MASK 0x80 + +/** \ingroup desc + * Endpoint direction. Values for bit 7 of the + * \ref libusb_endpoint_descriptor::bEndpointAddress "endpoint address" scheme. + */ +enum libusb_endpoint_direction { + /** In: device-to-host */ + LIBUSB_ENDPOINT_IN = 0x80, + + /** Out: host-to-device */ + LIBUSB_ENDPOINT_OUT = 0x00 +}; + +#define LIBUSB_TRANSFER_TYPE_MASK 0x03 /* in bmAttributes */ + +/** \ingroup desc + * Endpoint transfer type. Values for bits 0:1 of the + * \ref libusb_endpoint_descriptor::bmAttributes "endpoint attributes" field. + */ +enum libusb_transfer_type { + /** Control endpoint */ + LIBUSB_TRANSFER_TYPE_CONTROL = 0, + + /** Isochronous endpoint */ + LIBUSB_TRANSFER_TYPE_ISOCHRONOUS = 1, + + /** Bulk endpoint */ + LIBUSB_TRANSFER_TYPE_BULK = 2, + + /** Interrupt endpoint */ + LIBUSB_TRANSFER_TYPE_INTERRUPT = 3 +}; + +/** \ingroup misc + * Standard requests, as defined in table 9-3 of the USB2 specifications */ +enum libusb_standard_request { + /** Request status of the specific recipient */ + LIBUSB_REQUEST_GET_STATUS = 0x00, + + /** Clear or disable a specific feature */ + LIBUSB_REQUEST_CLEAR_FEATURE = 0x01, + + /* 0x02 is reserved */ + + /** Set or enable a specific feature */ + LIBUSB_REQUEST_SET_FEATURE = 0x03, + + /* 0x04 is reserved */ + + /** Set device address for all future accesses */ + LIBUSB_REQUEST_SET_ADDRESS = 0x05, + + /** Get the specified descriptor */ + LIBUSB_REQUEST_GET_DESCRIPTOR = 0x06, + + /** Used to update existing descriptors or add new descriptors */ + LIBUSB_REQUEST_SET_DESCRIPTOR = 0x07, + + /** Get the current device configuration value */ + LIBUSB_REQUEST_GET_CONFIGURATION = 0x08, + + /** Set device configuration */ + LIBUSB_REQUEST_SET_CONFIGURATION = 0x09, + + /** Return the selected alternate setting for the specified interface */ + LIBUSB_REQUEST_GET_INTERFACE = 0x0A, + + /** Select an alternate interface for the specified interface */ + LIBUSB_REQUEST_SET_INTERFACE = 0x0B, + + /** Set then report an endpoint's synchronization frame */ + LIBUSB_REQUEST_SYNCH_FRAME = 0x0C, +}; + +/** \ingroup misc + * Request type bits of the + * \ref libusb_control_setup::bmRequestType "bmRequestType" field in control + * transfers. */ +enum libusb_request_type { + /** Standard */ + LIBUSB_REQUEST_TYPE_STANDARD = (0x00 << 5), + + /** Class */ + LIBUSB_REQUEST_TYPE_CLASS = (0x01 << 5), + + /** Vendor */ + LIBUSB_REQUEST_TYPE_VENDOR = (0x02 << 5), + + /** Reserved */ + LIBUSB_REQUEST_TYPE_RESERVED = (0x03 << 5) +}; + +/** \ingroup misc + * Recipient bits of the + * \ref libusb_control_setup::bmRequestType "bmRequestType" field in control + * transfers. Values 4 through 31 are reserved. */ +enum libusb_request_recipient { + /** Device */ + LIBUSB_RECIPIENT_DEVICE = 0x00, + + /** Interface */ + LIBUSB_RECIPIENT_INTERFACE = 0x01, + + /** Endpoint */ + LIBUSB_RECIPIENT_ENDPOINT = 0x02, + + /** Other */ + LIBUSB_RECIPIENT_OTHER = 0x03, +}; + +#define LIBUSB_ISO_SYNC_TYPE_MASK 0x0C + +/** \ingroup desc + * Synchronization type for isochronous endpoints. Values for bits 2:3 of the + * \ref libusb_endpoint_descriptor::bmAttributes "bmAttributes" field in + * libusb_endpoint_descriptor. + */ +enum libusb_iso_sync_type { + /** No synchronization */ + LIBUSB_ISO_SYNC_TYPE_NONE = 0, + + /** Asynchronous */ + LIBUSB_ISO_SYNC_TYPE_ASYNC = 1, + + /** Adaptive */ + LIBUSB_ISO_SYNC_TYPE_ADAPTIVE = 2, + + /** Synchronous */ + LIBUSB_ISO_SYNC_TYPE_SYNC = 3 +}; + +#define LIBUSB_ISO_USAGE_TYPE_MASK 0x30 + +/** \ingroup desc + * Usage type for isochronous endpoints. Values for bits 4:5 of the + * \ref libusb_endpoint_descriptor::bmAttributes "bmAttributes" field in + * libusb_endpoint_descriptor. + */ +enum libusb_iso_usage_type { + /** Data endpoint */ + LIBUSB_ISO_USAGE_TYPE_DATA = 0, + + /** Feedback endpoint */ + LIBUSB_ISO_USAGE_TYPE_FEEDBACK = 1, + + /** Implicit feedback Data endpoint */ + LIBUSB_ISO_USAGE_TYPE_IMPLICIT = 2, +}; + +/** \ingroup desc + * A structure representing the standard USB device descriptor. This + * descriptor is documented in section 9.6.1 of the USB 2.0 specification. + * All multiple-byte fields are represented in host-endian format. + */ +struct libusb_device_descriptor { + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_DEVICE LIBUSB_DT_DEVICE in this + * context. */ + uint8_t bDescriptorType; + + /** USB specification release number in binary-coded decimal. A value of + * 0x0200 indicates USB 2.0, 0x0110 indicates USB 1.1, etc. */ + uint16_t bcdUSB; + + /** USB-IF class code for the device. See \ref libusb_class_code. */ + uint8_t bDeviceClass; + + /** USB-IF subclass code for the device, qualified by the bDeviceClass + * value */ + uint8_t bDeviceSubClass; + + /** USB-IF protocol code for the device, qualified by the bDeviceClass and + * bDeviceSubClass values */ + uint8_t bDeviceProtocol; + + /** Maximum packet size for endpoint 0 */ + uint8_t bMaxPacketSize0; + + /** USB-IF vendor ID */ + uint16_t idVendor; + + /** USB-IF product ID */ + uint16_t idProduct; + + /** Device release number in binary-coded decimal */ + uint16_t bcdDevice; + + /** Index of string descriptor describing manufacturer */ + uint8_t iManufacturer; + + /** Index of string descriptor describing product */ + uint8_t iProduct; + + /** Index of string descriptor containing device serial number */ + uint8_t iSerialNumber; + + /** Number of possible configurations */ + uint8_t bNumConfigurations; +}; + +/** \ingroup desc + * A structure representing the superspeed endpoint companion + * descriptor. This descriptor is documented in section 9.6.7 of + * the USB 3.0 specification. All ultiple-byte fields are represented in + * host-endian format. + */ +struct libusb_ss_endpoint_companion_descriptor { + + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_SS_ENDPOINT_COMPANION in + * this context. */ + uint8_t bDescriptorType; + + + /** The maximum number of packets the endpoint can send or + * recieve as part of a burst. */ + uint8_t bMaxBurst; + + /** In bulk EP: bits 4:0 represents the maximum number of + * streams the EP supports. In isochronous EP: bits 1:0 + * represents the Mult - a zero based value that determines + * the maximum number of packets within a service interval */ + uint8_t bmAttributes; + + /** The total number of bytes this EP will transfer every + * service interval. valid only for periodic EPs. */ + uint16_t wBytesPerInterval; +}; + +/** \ingroup desc + * A structure representing the standard USB endpoint descriptor. This + * descriptor is documented in section 9.6.3 of the USB 2.0 specification. + * All multiple-byte fields are represented in host-endian format. + */ +struct libusb_endpoint_descriptor { + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_ENDPOINT LIBUSB_DT_ENDPOINT in + * this context. */ + uint8_t bDescriptorType; + + /** The address of the endpoint described by this descriptor. Bits 0:3 are + * the endpoint number. Bits 4:6 are reserved. Bit 7 indicates direction, + * see \ref libusb_endpoint_direction. + */ + uint8_t bEndpointAddress; + + /** Attributes which apply to the endpoint when it is configured using + * the bConfigurationValue. Bits 0:1 determine the transfer type and + * correspond to \ref libusb_transfer_type. Bits 2:3 are only used for + * isochronous endpoints and correspond to \ref libusb_iso_sync_type. + * Bits 4:5 are also only used for isochronous endpoints and correspond to + * \ref libusb_iso_usage_type. Bits 6:7 are reserved. + */ + uint8_t bmAttributes; + + /** Maximum packet size this endpoint is capable of sending/receiving. */ + uint16_t wMaxPacketSize; + + /** Interval for polling endpoint for data transfers. */ + uint8_t bInterval; + + /** For audio devices only: the rate at which synchronization feedback + * is provided. */ + uint8_t bRefresh; + + /** For audio devices only: the address if the synch endpoint */ + uint8_t bSynchAddress; + + /** Extra descriptors. If libusb encounters unknown endpoint descriptors, + * it will store them here, should you wish to parse them. */ + const unsigned char *extra; + + /** Length of the extra descriptors, in bytes. */ + int extra_length; +}; + + +/** \ingroup desc + * A structure representing the standard USB interface descriptor. This + * descriptor is documented in section 9.6.5 of the USB 2.0 specification. + * All multiple-byte fields are represented in host-endian format. + */ +struct libusb_interface_descriptor { + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_INTERFACE LIBUSB_DT_INTERFACE + * in this context. */ + uint8_t bDescriptorType; + + /** Number of this interface */ + uint8_t bInterfaceNumber; + + /** Value used to select this alternate setting for this interface */ + uint8_t bAlternateSetting; + + /** Number of endpoints used by this interface (excluding the control + * endpoint). */ + uint8_t bNumEndpoints; + + /** USB-IF class code for this interface. See \ref libusb_class_code. */ + uint8_t bInterfaceClass; + + /** USB-IF subclass code for this interface, qualified by the + * bInterfaceClass value */ + uint8_t bInterfaceSubClass; + + /** USB-IF protocol code for this interface, qualified by the + * bInterfaceClass and bInterfaceSubClass values */ + uint8_t bInterfaceProtocol; + + /** Index of string descriptor describing this interface */ + uint8_t iInterface; + + /** Array of endpoint descriptors. This length of this array is determined + * by the bNumEndpoints field. */ + const struct libusb_endpoint_descriptor *endpoint; + + /** Extra descriptors. If libusb encounters unknown interface descriptors, + * it will store them here, should you wish to parse them. */ + const unsigned char *extra; + + /** Length of the extra descriptors, in bytes. */ + int extra_length; +}; + +/** \ingroup desc + * A collection of alternate settings for a particular USB interface. + */ +struct libusb_interface { + /** Array of interface descriptors. The length of this array is determined + * by the num_altsetting field. */ + const struct libusb_interface_descriptor *altsetting; + + /** The number of alternate settings that belong to this interface */ + int num_altsetting; +}; + +/** \ingroup desc + * A structure representing the standard USB configuration descriptor. This + * descriptor is documented in section 9.6.3 of the USB 2.0 specification. + * All multiple-byte fields are represented in host-endian format. + */ +struct libusb_config_descriptor { + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_CONFIG LIBUSB_DT_CONFIG + * in this context. */ + uint8_t bDescriptorType; + + /** Total length of data returned for this configuration */ + uint16_t wTotalLength; + + /** Number of interfaces supported by this configuration */ + uint8_t bNumInterfaces; + + /** Identifier value for this configuration */ + uint8_t bConfigurationValue; + + /** Index of string descriptor describing this configuration */ + uint8_t iConfiguration; + + /** Configuration characteristics */ + uint8_t bmAttributes; + + /** Maximum power consumption of the USB device from this bus in this + * configuration when the device is fully opreation. Expressed in units + * of 2 mA. */ + uint8_t MaxPower; + + /** Array of interfaces supported by this configuration. The length of + * this array is determined by the bNumInterfaces field. */ + const struct libusb_interface *interface; + + /** Extra descriptors. If libusb encounters unknown configuration + * descriptors, it will store them here, should you wish to parse them. */ + const unsigned char *extra; + + /** Length of the extra descriptors, in bytes. */ + int extra_length; +}; + +/** \ingroup desc + * A structure representing the BOS descriptor. This + * descriptor is documented in section 9.6.2 of the USB 3.0 + * specification. All multiple-byte fields are represented in + * host-endian format. + */ +struct libusb_bos_descriptor { + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_BOS LIBUSB_DT_BOS + * in this context. */ + uint8_t bDescriptorType; + + /** Length of this descriptor and all of its sub descriptors */ + uint16_t wTotalLength; + + /** The number of separate device capability descriptors in + * the BOS */ + uint8_t bNumDeviceCaps; + + /** USB 2.0 extension capability descriptor */ + struct libusb_usb_2_0_device_capability_descriptor *usb_2_0_ext_cap; + + /** SuperSpeed capabilty descriptor */ + struct libusb_ss_usb_device_capability_descriptor *ss_usb_cap; +}; + +/** \ingroup desc + * A structure representing the device capability descriptor for + * USB 2.0. This descriptor is documented in section 9.6.2.1 of + * the USB 3.0 specification. All mutiple-byte fields are represented + * in host-endian format. + */ +struct libusb_usb_2_0_device_capability_descriptor { + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_DEVICE_CAPABILITY + * LIBUSB_DT_DEVICE_CAPABILITY in this context. */ + uint8_t bDescriptorType; + + /** Capability type. Will have value + * \ref libusb_capability_type::LIBUSB_USB_CAP_TYPE_EXT + * LIBUSB_USB_CAP_TYPE_EXT in this context. */ + uint8_t bDevCapabilityType; + + /** Bitmap encoding of supported device level features. + * A value of one in a bit location indicates a feature is + * supported; a value of zero indicates it is not supported. + * See \ref libusb_capability_attributes. */ + uint32_t bmAttributes; +}; + +/** \ingroup desc + * A structure representing the device capability descriptor for + * USB 3.0. This descriptor is documented in section 9.6.2.2 of + * the USB 3.0 specification. All mutiple-byte fields are represented + * in host-endian format. + */ +struct libusb_ss_usb_device_capability_descriptor { + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_DEVICE_CAPABILITY + * LIBUSB_DT_DEVICE_CAPABILITY in this context. */ + uint8_t bDescriptorType; + + /** Capability type. Will have value + * \ref libusb_capability_type::LIBUSB_SS_USB_CAP_TYPE + * LIBUSB_SS_USB_CAP_TYPE in this context. */ + uint8_t bDevCapabilityType; + + /** Bitmap encoding of supported device level features. + * A value of one in a bit location indicates a feature is + * supported; a value of zero indicates it is not supported. + * See \ref libusb_capability_attributes. */ + uint8_t bmAttributes; + + /** Bitmap encoding of the speed supported by this device when + * operating in SuperSpeed mode. See \ref libusb_supported_speed. */ + uint16_t wSpeedSupported; + + /** The lowest speed at which all the functionality supported + * by the device is available to the user. For example if the + * device supports all its functionality when connected at + * full speed and above then it sets this value to 1. */ + uint8_t bFunctionalitySupport; + + /** U1 Device Exit Latency. */ + uint8_t bU1DevExitLat; + + /** U2 Device Exit Latency. */ + uint16_t bU2DevExitLat; +}; + + +/** \ingroup asyncio + * Setup packet for control transfers. */ +struct libusb_control_setup { + /** Request type. Bits 0:4 determine recipient, see + * \ref libusb_request_recipient. Bits 5:6 determine type, see + * \ref libusb_request_type. Bit 7 determines data transfer direction, see + * \ref libusb_endpoint_direction. + */ + uint8_t bmRequestType; + + /** Request. If the type bits of bmRequestType are equal to + * \ref libusb_request_type::LIBUSB_REQUEST_TYPE_STANDARD + * "LIBUSB_REQUEST_TYPE_STANDARD" then this field refers to + * \ref libusb_standard_request. For other cases, use of this field is + * application-specific. */ + uint8_t bRequest; + + /** Value. Varies according to request */ + uint16_t wValue; + + /** Index. Varies according to request, typically used to pass an index + * or offset */ + uint16_t wIndex; + + /** Number of bytes to transfer */ + uint16_t wLength; +}; + +#define LIBUSB_CONTROL_SETUP_SIZE (sizeof(struct libusb_control_setup)) + +/* libusb */ + +struct libusb_context; +struct libusb_device; +struct libusb_device_handle; +struct libusb_hotplug_callback; + +/** \ingroup lib + * Structure representing the libusb version. + */ +struct libusb_version { + /** Library major version. */ + const uint16_t major; + + /** Library minor version. */ + const uint16_t minor; + + /** Library micro version. */ + const uint16_t micro; + + /** Library nano version. This field is only nonzero on Windows. */ + const uint16_t nano; + + /** Library release candidate suffix string, e.g. "-rc4". */ + const char *rc; + + /** Output of `git describe --tags` at library build time. */ + const char *describe; +}; + +/** \ingroup lib + * Structure representing a libusb session. The concept of individual libusb + * sessions allows for your program to use two libraries (or dynamically + * load two modules) which both independently use libusb. This will prevent + * interference between the individual libusb users - for example + * libusb_set_debug() will not affect the other user of the library, and + * libusb_exit() will not destroy resources that the other user is still + * using. + * + * Sessions are created by libusb_init() and destroyed through libusb_exit(). + * If your application is guaranteed to only ever include a single libusb + * user (i.e. you), you do not have to worry about contexts: pass NULL in + * every function call where a context is required. The default context + * will be used. + * + * For more information, see \ref contexts. + */ +typedef struct libusb_context libusb_context; + +/** \ingroup dev + * Structure representing a USB device detected on the system. This is an + * opaque type for which you are only ever provided with a pointer, usually + * originating from libusb_get_device_list(). + * + * Certain operations can be performed on a device, but in order to do any + * I/O you will have to first obtain a device handle using libusb_open(). + * + * Devices are reference counted with libusb_ref_device() and + * libusb_unref_device(), and are freed when the reference count reaches 0. + * New devices presented by libusb_get_device_list() have a reference count of + * 1, and libusb_free_device_list() can optionally decrease the reference count + * on all devices in the list. libusb_open() adds another reference which is + * later destroyed by libusb_close(). + */ +typedef struct libusb_device libusb_device; + + +/** \ingroup dev + * Structure representing a handle on a USB device. This is an opaque type for + * which you are only ever provided with a pointer, usually originating from + * libusb_open(). + * + * A device handle is used to perform I/O and other operations. When finished + * with a device handle, you should call libusb_close(). + */ +typedef struct libusb_device_handle libusb_device_handle; + +/** \ingroup dev + * Speed codes. Indicates the speed at which the device is operating. + */ +enum libusb_speed { + /** The OS doesn't report or know the device speed. */ + LIBUSB_SPEED_UNKNOWN = 0, + + /** The device is operating at low speed (1.5MBit/s). */ + LIBUSB_SPEED_LOW = 1, + + /** The device is operating at full speed (12MBit/s). */ + LIBUSB_SPEED_FULL = 2, + + /** The device is operating at high speed (480MBit/s). */ + LIBUSB_SPEED_HIGH = 3, + + /** The device is operating at super speed (5000MBit/s). */ + LIBUSB_SPEED_SUPER = 4, +}; + +/** \ingroup dev + * Supported speeds (wSpeedSupported) bitfield. Indicates what + * speeds the device supports. + */ +enum libusb_supported_speed { + /** Low speed operation supported (1.5MBit/s). */ + LIBUSB_LOW_SPEED_OPERATION = 1, + + /** Full speed operation supported (12MBit/s). */ + LIBUSB_FULL_SPEED_OPERATION = 2, + + /** High speed operation supported (480MBit/s). */ + LIBUSB_HIGH_SPEED_OPERATION = 4, + + /** Superspeed operation supported (5000MBit/s). */ + LIBUSB_5GBPS_OPERATION = 8, +}; + +/** \ingroup dev + * Capability attributes + */ +enum libusb_capability_attributes { + /** Supports Link Power Management (LPM) */ + LIBUSB_LPM_SUPPORT = 2, +}; + +/** \ingroup dev + * USB capability types + */ +enum libusb_capability_type { + /** USB 2.0 extension capability type */ + LIBUSB_USB_CAP_TYPE_EXT = 2, + + /** SuperSpeed capability type */ + LIBUSB_SS_USB_CAP_TYPE = 3, +}; + +/** \ingroup misc + * Error codes. Most libusb functions return 0 on success or one of these + * codes on failure. + * You can call \ref libusb_error_name() to retrieve a string representation + * of an error code or \ret libusb_strerror() to get an english description + * of an error code. + */ +enum libusb_error { + /** Success (no error) */ + LIBUSB_SUCCESS = 0, + + /** Input/output error */ + LIBUSB_ERROR_IO = -1, + + /** Invalid parameter */ + LIBUSB_ERROR_INVALID_PARAM = -2, + + /** Access denied (insufficient permissions) */ + LIBUSB_ERROR_ACCESS = -3, + + /** No such device (it may have been disconnected) */ + LIBUSB_ERROR_NO_DEVICE = -4, + + /** Entity not found */ + LIBUSB_ERROR_NOT_FOUND = -5, + + /** Resource busy */ + LIBUSB_ERROR_BUSY = -6, + + /** Operation timed out */ + LIBUSB_ERROR_TIMEOUT = -7, + + /** Overflow */ + LIBUSB_ERROR_OVERFLOW = -8, + + /** Pipe error */ + LIBUSB_ERROR_PIPE = -9, + + /** System call interrupted (perhaps due to signal) */ + LIBUSB_ERROR_INTERRUPTED = -10, + + /** Insufficient memory */ + LIBUSB_ERROR_NO_MEM = -11, + + /** Operation not supported or unimplemented on this platform */ + LIBUSB_ERROR_NOT_SUPPORTED = -12, + + /* NB! Remember to update libusb_error_name() and + libusb_strerror() when adding new error codes here. */ + + /** Other error */ + LIBUSB_ERROR_OTHER = -99, +}; + +/** \ingroup asyncio + * Transfer status codes */ +enum libusb_transfer_status { + /** Transfer completed without error. Note that this does not indicate + * that the entire amount of requested data was transferred. */ + LIBUSB_TRANSFER_COMPLETED, + + /** Transfer failed */ + LIBUSB_TRANSFER_ERROR, + + /** Transfer timed out */ + LIBUSB_TRANSFER_TIMED_OUT, + + /** Transfer was cancelled */ + LIBUSB_TRANSFER_CANCELLED, + + /** For bulk/interrupt endpoints: halt condition detected (endpoint + * stalled). For control endpoints: control request not supported. */ + LIBUSB_TRANSFER_STALL, + + /** Device was disconnected */ + LIBUSB_TRANSFER_NO_DEVICE, + + /** Device sent more data than requested */ + LIBUSB_TRANSFER_OVERFLOW, +}; + +/** \ingroup asyncio + * libusb_transfer.flags values */ +enum libusb_transfer_flags { + /** Report short frames as errors */ + LIBUSB_TRANSFER_SHORT_NOT_OK = 1<<0, + + /** Automatically free() transfer buffer during libusb_free_transfer() */ + LIBUSB_TRANSFER_FREE_BUFFER = 1<<1, + + /** Automatically call libusb_free_transfer() after callback returns. + * If this flag is set, it is illegal to call libusb_free_transfer() + * from your transfer callback, as this will result in a double-free + * when this flag is acted upon. */ + LIBUSB_TRANSFER_FREE_TRANSFER = 1<<2, + + /** Terminate transfers that are a multiple of the endpoint's + * wMaxPacketSize with an extra zero length packet. This is useful + * when a device protocol mandates that each logical request is + * terminated by an incomplete packet (i.e. the logical requests are + * not separated by other means). + * + * This flag only affects host-to-device transfers to bulk and interrupt + * endpoints. In other situations, it is ignored. + * + * This flag only affects transfers with a length that is a multiple of + * the endpoint's wMaxPacketSize. On transfers of other lengths, this + * flag has no effect. Therefore, if you are working with a device that + * needs a ZLP whenever the end of the logical request falls on a packet + * boundary, then it is sensible to set this flag on every + * transfer (you do not have to worry about only setting it on transfers + * that end on the boundary). + * + * This flag is currently only supported on Linux. + * On other systems, libusb_submit_transfer() will return + * LIBUSB_ERROR_NOT_SUPPORTED for every transfer where this flag is set. + * + * Available since libusb-1.0.9. + */ + LIBUSB_TRANSFER_ADD_ZERO_PACKET = 1 << 3, +}; + +/** \ingroup asyncio + * Isochronous packet descriptor. */ +struct libusb_iso_packet_descriptor { + /** Length of data to request in this packet */ + unsigned int length; + + /** Amount of data that was actually transferred */ + unsigned int actual_length; + + /** Status code for this packet */ + enum libusb_transfer_status status; +}; + +struct libusb_transfer; + +/** \ingroup asyncio + * Asynchronous transfer callback function type. When submitting asynchronous + * transfers, you pass a pointer to a callback function of this type via the + * \ref libusb_transfer::callback "callback" member of the libusb_transfer + * structure. libusb will call this function later, when the transfer has + * completed or failed. See \ref asyncio for more information. + * \param transfer The libusb_transfer struct the callback function is being + * notified about. + */ +typedef void (LIBUSB_CALL *libusb_transfer_cb_fn)(struct libusb_transfer *transfer); + +/** \ingroup asyncio + * The generic USB transfer structure. The user populates this structure and + * then submits it in order to request a transfer. After the transfer has + * completed, the library populates the transfer with the results and passes + * it back to the user. + */ +struct libusb_transfer { + /** Handle of the device that this transfer will be submitted to */ + libusb_device_handle *dev_handle; + + /** A bitwise OR combination of \ref libusb_transfer_flags. */ + uint8_t flags; + + /** Address of the endpoint where this transfer will be sent. */ + unsigned char endpoint; + + /** Type of the endpoint from \ref libusb_transfer_type */ + unsigned char type; + + /** Timeout for this transfer in millseconds. A value of 0 indicates no + * timeout. */ + unsigned int timeout; + + /** The status of the transfer. Read-only, and only for use within + * transfer callback function. + * + * If this is an isochronous transfer, this field may read COMPLETED even + * if there were errors in the frames. Use the + * \ref libusb_iso_packet_descriptor::status "status" field in each packet + * to determine if errors occurred. */ + enum libusb_transfer_status status; + + /** Length of the data buffer */ + int length; + + /** Actual length of data that was transferred. Read-only, and only for + * use within transfer callback function. Not valid for isochronous + * endpoint transfers. */ + int actual_length; + + /** Callback function. This will be invoked when the transfer completes, + * fails, or is cancelled. */ + libusb_transfer_cb_fn callback; + + /** User context data to pass to the callback function. */ + void *user_data; + + /** Data buffer */ + unsigned char *buffer; + + /** Number of isochronous packets. Only used for I/O with isochronous + * endpoints. */ + int num_iso_packets; + + /** Isochronous packet descriptors, for isochronous transfers only. */ + struct libusb_iso_packet_descriptor iso_packet_desc +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) + [] /* valid C99 code */ +#else + [0] /* non-standard, but usually working code */ +#endif + ; +}; + +/** \ingroup misc + * Capabilities supported by this instance of libusb. Test if the loaded + * library supports a given capability by calling + * \ref libusb_has_capability(). + */ +enum libusb_capability { + /** The libusb_has_capability() API is available. */ + LIBUSB_CAP_HAS_CAPABILITY = 0, + /** The libusb hotplug API is available. */ + LIBUSB_CAP_HAS_HOTPLUG = 1, +}; + +int LIBUSB_CALL libusb_init(libusb_context **ctx); +void LIBUSB_CALL libusb_exit(libusb_context *ctx); +void LIBUSB_CALL libusb_set_debug(libusb_context *ctx, int level); +const struct libusb_version * LIBUSB_CALL libusb_get_version(void); +int LIBUSB_CALL libusb_has_capability(uint32_t capability); +const char * LIBUSB_CALL libusb_error_name(int errcode); +const char * LIBUSB_CALL libusb_strerror(enum libusb_error errcode); + +ssize_t LIBUSB_CALL libusb_get_device_list(libusb_context *ctx, + libusb_device ***list); +void LIBUSB_CALL libusb_free_device_list(libusb_device **list, + int unref_devices); +libusb_device * LIBUSB_CALL libusb_ref_device(libusb_device *dev); +void LIBUSB_CALL libusb_unref_device(libusb_device *dev); + +int LIBUSB_CALL libusb_get_configuration(libusb_device_handle *dev, + int *config); +int LIBUSB_CALL libusb_get_device_descriptor(libusb_device *dev, + struct libusb_device_descriptor *desc); +int LIBUSB_CALL libusb_get_active_config_descriptor(libusb_device *dev, + struct libusb_config_descriptor **config); +int LIBUSB_CALL libusb_get_config_descriptor(libusb_device *dev, + uint8_t config_index, struct libusb_config_descriptor **config); +int LIBUSB_CALL libusb_get_config_descriptor_by_value(libusb_device *dev, + uint8_t bConfigurationValue, struct libusb_config_descriptor **config); +void LIBUSB_CALL libusb_free_config_descriptor( + struct libusb_config_descriptor *config); +uint8_t LIBUSB_CALL libusb_get_bus_number(libusb_device *dev); +uint8_t LIBUSB_CALL libusb_get_device_address(libusb_device *dev); +int LIBUSB_CALL libusb_get_device_speed(libusb_device *dev); +int LIBUSB_CALL libusb_get_max_packet_size(libusb_device *dev, + unsigned char endpoint); +int LIBUSB_CALL libusb_get_max_iso_packet_size(libusb_device *dev, + unsigned char endpoint); + +int LIBUSB_CALL libusb_open(libusb_device *dev, libusb_device_handle **handle); +void LIBUSB_CALL libusb_close(libusb_device_handle *dev_handle); +libusb_device * LIBUSB_CALL libusb_get_device(libusb_device_handle *dev_handle); + +int LIBUSB_CALL libusb_set_configuration(libusb_device_handle *dev, + int configuration); +int LIBUSB_CALL libusb_claim_interface(libusb_device_handle *dev, + int interface_number); +int LIBUSB_CALL libusb_release_interface(libusb_device_handle *dev, + int interface_number); + +libusb_device_handle * LIBUSB_CALL libusb_open_device_with_vid_pid( + libusb_context *ctx, uint16_t vendor_id, uint16_t product_id); + +int LIBUSB_CALL libusb_set_interface_alt_setting(libusb_device_handle *dev, + int interface_number, int alternate_setting); +int LIBUSB_CALL libusb_clear_halt(libusb_device_handle *dev, + unsigned char endpoint); +int LIBUSB_CALL libusb_reset_device(libusb_device_handle *dev); + +int LIBUSB_CALL libusb_kernel_driver_active(libusb_device_handle *dev, + int interface_number); +int LIBUSB_CALL libusb_detach_kernel_driver(libusb_device_handle *dev, + int interface_number); +int LIBUSB_CALL libusb_attach_kernel_driver(libusb_device_handle *dev, + int interface_number); + +/* async I/O */ + +/** \ingroup asyncio + * Get the data section of a control transfer. This convenience function is here + * to remind you that the data does not start until 8 bytes into the actual + * buffer, as the setup packet comes first. + * + * Calling this function only makes sense from a transfer callback function, + * or situations where you have already allocated a suitably sized buffer at + * transfer->buffer. + * + * \param transfer a transfer + * \returns pointer to the first byte of the data section + */ +static inline unsigned char *libusb_control_transfer_get_data( + struct libusb_transfer *transfer) +{ + return transfer->buffer + LIBUSB_CONTROL_SETUP_SIZE; +} + +/** \ingroup asyncio + * Get the control setup packet of a control transfer. This convenience + * function is here to remind you that the control setup occupies the first + * 8 bytes of the transfer data buffer. + * + * Calling this function only makes sense from a transfer callback function, + * or situations where you have already allocated a suitably sized buffer at + * transfer->buffer. + * + * \param transfer a transfer + * \returns a casted pointer to the start of the transfer data buffer + */ +static inline struct libusb_control_setup *libusb_control_transfer_get_setup( + struct libusb_transfer *transfer) +{ + return (struct libusb_control_setup *) transfer->buffer; +} + +/** \ingroup asyncio + * Helper function to populate the setup packet (first 8 bytes of the data + * buffer) for a control transfer. The wIndex, wValue and wLength values should + * be given in host-endian byte order. + * + * \param buffer buffer to output the setup packet into + * \param bmRequestType see the + * \ref libusb_control_setup::bmRequestType "bmRequestType" field of + * \ref libusb_control_setup + * \param bRequest see the + * \ref libusb_control_setup::bRequest "bRequest" field of + * \ref libusb_control_setup + * \param wValue see the + * \ref libusb_control_setup::wValue "wValue" field of + * \ref libusb_control_setup + * \param wIndex see the + * \ref libusb_control_setup::wIndex "wIndex" field of + * \ref libusb_control_setup + * \param wLength see the + * \ref libusb_control_setup::wLength "wLength" field of + * \ref libusb_control_setup + */ +static inline void libusb_fill_control_setup(unsigned char *buffer, + uint8_t bmRequestType, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, + uint16_t wLength) +{ + struct libusb_control_setup *setup = (struct libusb_control_setup *) buffer; + setup->bmRequestType = bmRequestType; + setup->bRequest = bRequest; + setup->wValue = libusb_cpu_to_le16(wValue); + setup->wIndex = libusb_cpu_to_le16(wIndex); + setup->wLength = libusb_cpu_to_le16(wLength); +} + +struct libusb_transfer * LIBUSB_CALL libusb_alloc_transfer(int iso_packets); +int LIBUSB_CALL libusb_submit_transfer(struct libusb_transfer *transfer); +int LIBUSB_CALL libusb_cancel_transfer(struct libusb_transfer *transfer); +void LIBUSB_CALL libusb_free_transfer(struct libusb_transfer *transfer); + +/** \ingroup asyncio + * Helper function to populate the required \ref libusb_transfer fields + * for a control transfer. + * + * If you pass a transfer buffer to this function, the first 8 bytes will + * be interpreted as a control setup packet, and the wLength field will be + * used to automatically populate the \ref libusb_transfer::length "length" + * field of the transfer. Therefore the recommended approach is: + * -# Allocate a suitably sized data buffer (including space for control setup) + * -# Call libusb_fill_control_setup() + * -# If this is a host-to-device transfer with a data stage, put the data + * in place after the setup packet + * -# Call this function + * -# Call libusb_submit_transfer() + * + * It is also legal to pass a NULL buffer to this function, in which case this + * function will not attempt to populate the length field. Remember that you + * must then populate the buffer and length fields later. + * + * \param transfer the transfer to populate + * \param dev_handle handle of the device that will handle the transfer + * \param buffer data buffer. If provided, this function will interpret the + * first 8 bytes as a setup packet and infer the transfer length from that. + * \param callback callback function to be invoked on transfer completion + * \param user_data user data to pass to callback function + * \param timeout timeout for the transfer in milliseconds + */ +static inline void libusb_fill_control_transfer( + struct libusb_transfer *transfer, libusb_device_handle *dev_handle, + unsigned char *buffer, libusb_transfer_cb_fn callback, void *user_data, + unsigned int timeout) +{ + struct libusb_control_setup *setup = (struct libusb_control_setup *) buffer; + transfer->dev_handle = dev_handle; + transfer->endpoint = 0; + transfer->type = LIBUSB_TRANSFER_TYPE_CONTROL; + transfer->timeout = timeout; + transfer->buffer = buffer; + if (setup) + transfer->length = LIBUSB_CONTROL_SETUP_SIZE + + libusb_le16_to_cpu(setup->wLength); + transfer->user_data = user_data; + transfer->callback = callback; +} + +/** \ingroup asyncio + * Helper function to populate the required \ref libusb_transfer fields + * for a bulk transfer. + * + * \param transfer the transfer to populate + * \param dev_handle handle of the device that will handle the transfer + * \param endpoint address of the endpoint where this transfer will be sent + * \param buffer data buffer + * \param length length of data buffer + * \param callback callback function to be invoked on transfer completion + * \param user_data user data to pass to callback function + * \param timeout timeout for the transfer in milliseconds + */ +static inline void libusb_fill_bulk_transfer(struct libusb_transfer *transfer, + libusb_device_handle *dev_handle, unsigned char endpoint, + unsigned char *buffer, int length, libusb_transfer_cb_fn callback, + void *user_data, unsigned int timeout) +{ + transfer->dev_handle = dev_handle; + transfer->endpoint = endpoint; + transfer->type = LIBUSB_TRANSFER_TYPE_BULK; + transfer->timeout = timeout; + transfer->buffer = buffer; + transfer->length = length; + transfer->user_data = user_data; + transfer->callback = callback; +} + +/** \ingroup asyncio + * Helper function to populate the required \ref libusb_transfer fields + * for an interrupt transfer. + * + * \param transfer the transfer to populate + * \param dev_handle handle of the device that will handle the transfer + * \param endpoint address of the endpoint where this transfer will be sent + * \param buffer data buffer + * \param length length of data buffer + * \param callback callback function to be invoked on transfer completion + * \param user_data user data to pass to callback function + * \param timeout timeout for the transfer in milliseconds + */ +static inline void libusb_fill_interrupt_transfer( + struct libusb_transfer *transfer, libusb_device_handle *dev_handle, + unsigned char endpoint, unsigned char *buffer, int length, + libusb_transfer_cb_fn callback, void *user_data, unsigned int timeout) +{ + transfer->dev_handle = dev_handle; + transfer->endpoint = endpoint; + transfer->type = LIBUSB_TRANSFER_TYPE_INTERRUPT; + transfer->timeout = timeout; + transfer->buffer = buffer; + transfer->length = length; + transfer->user_data = user_data; + transfer->callback = callback; +} + +/** \ingroup asyncio + * Helper function to populate the required \ref libusb_transfer fields + * for an isochronous transfer. + * + * \param transfer the transfer to populate + * \param dev_handle handle of the device that will handle the transfer + * \param endpoint address of the endpoint where this transfer will be sent + * \param buffer data buffer + * \param length length of data buffer + * \param num_iso_packets the number of isochronous packets + * \param callback callback function to be invoked on transfer completion + * \param user_data user data to pass to callback function + * \param timeout timeout for the transfer in milliseconds + */ +static inline void libusb_fill_iso_transfer(struct libusb_transfer *transfer, + libusb_device_handle *dev_handle, unsigned char endpoint, + unsigned char *buffer, int length, int num_iso_packets, + libusb_transfer_cb_fn callback, void *user_data, unsigned int timeout) +{ + transfer->dev_handle = dev_handle; + transfer->endpoint = endpoint; + transfer->type = LIBUSB_TRANSFER_TYPE_ISOCHRONOUS; + transfer->timeout = timeout; + transfer->buffer = buffer; + transfer->length = length; + transfer->num_iso_packets = num_iso_packets; + transfer->user_data = user_data; + transfer->callback = callback; +} + +/** \ingroup asyncio + * Convenience function to set the length of all packets in an isochronous + * transfer, based on the num_iso_packets field in the transfer structure. + * + * \param transfer a transfer + * \param length the length to set in each isochronous packet descriptor + * \see libusb_get_max_packet_size() + */ +static inline void libusb_set_iso_packet_lengths( + struct libusb_transfer *transfer, unsigned int length) +{ + int i; + for (i = 0; i < transfer->num_iso_packets; i++) + transfer->iso_packet_desc[i].length = length; +} + +/** \ingroup asyncio + * Convenience function to locate the position of an isochronous packet + * within the buffer of an isochronous transfer. + * + * This is a thorough function which loops through all preceding packets, + * accumulating their lengths to find the position of the specified packet. + * Typically you will assign equal lengths to each packet in the transfer, + * and hence the above method is sub-optimal. You may wish to use + * libusb_get_iso_packet_buffer_simple() instead. + * + * \param transfer a transfer + * \param packet the packet to return the address of + * \returns the base address of the packet buffer inside the transfer buffer, + * or NULL if the packet does not exist. + * \see libusb_get_iso_packet_buffer_simple() + */ +static inline unsigned char *libusb_get_iso_packet_buffer( + struct libusb_transfer *transfer, unsigned int packet) +{ + int i; + size_t offset = 0; + int _packet; + + /* oops..slight bug in the API. packet is an unsigned int, but we use + * signed integers almost everywhere else. range-check and convert to + * signed to avoid compiler warnings. FIXME for libusb-2. */ + if (packet > INT_MAX) + return NULL; + _packet = packet; + + if (_packet >= transfer->num_iso_packets) + return NULL; + + for (i = 0; i < _packet; i++) + offset += transfer->iso_packet_desc[i].length; + + return transfer->buffer + offset; +} + +/** \ingroup asyncio + * Convenience function to locate the position of an isochronous packet + * within the buffer of an isochronous transfer, for transfers where each + * packet is of identical size. + * + * This function relies on the assumption that every packet within the transfer + * is of identical size to the first packet. Calculating the location of + * the packet buffer is then just a simple calculation: + * buffer + (packet_size * packet) + * + * Do not use this function on transfers other than those that have identical + * packet lengths for each packet. + * + * \param transfer a transfer + * \param packet the packet to return the address of + * \returns the base address of the packet buffer inside the transfer buffer, + * or NULL if the packet does not exist. + * \see libusb_get_iso_packet_buffer() + */ +static inline unsigned char *libusb_get_iso_packet_buffer_simple( + struct libusb_transfer *transfer, unsigned int packet) +{ + int _packet; + + /* oops..slight bug in the API. packet is an unsigned int, but we use + * signed integers almost everywhere else. range-check and convert to + * signed to avoid compiler warnings. FIXME for libusb-2. */ + if (packet > INT_MAX) + return NULL; + _packet = packet; + + if (_packet >= transfer->num_iso_packets) + return NULL; + + return transfer->buffer + (transfer->iso_packet_desc[0].length * _packet); +} + +/* sync I/O */ + +int LIBUSB_CALL libusb_control_transfer(libusb_device_handle *dev_handle, + uint8_t request_type, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, + unsigned char *data, uint16_t wLength, unsigned int timeout); + +int LIBUSB_CALL libusb_bulk_transfer(libusb_device_handle *dev_handle, + unsigned char endpoint, unsigned char *data, int length, + int *actual_length, unsigned int timeout); + +int LIBUSB_CALL libusb_interrupt_transfer(libusb_device_handle *dev_handle, + unsigned char endpoint, unsigned char *data, int length, + int *actual_length, unsigned int timeout); + +/** \ingroup desc + * Retrieve a descriptor from the default control pipe. + * This is a convenience function which formulates the appropriate control + * message to retrieve the descriptor. + * + * \param dev a device handle + * \param desc_type the descriptor type, see \ref libusb_descriptor_type + * \param desc_index the index of the descriptor to retrieve + * \param data output buffer for descriptor + * \param length size of data buffer + * \returns number of bytes returned in data, or LIBUSB_ERROR code on failure + */ +static inline int libusb_get_descriptor(libusb_device_handle *dev, + uint8_t desc_type, uint8_t desc_index, unsigned char *data, int length) +{ + return libusb_control_transfer(dev, LIBUSB_ENDPOINT_IN, + LIBUSB_REQUEST_GET_DESCRIPTOR, (desc_type << 8) | desc_index, 0, data, + (uint16_t) length, 1000); +} + +/** \ingroup desc + * Retrieve a descriptor from a device. + * This is a convenience function which formulates the appropriate control + * message to retrieve the descriptor. The string returned is Unicode, as + * detailed in the USB specifications. + * + * \param dev a device handle + * \param desc_index the index of the descriptor to retrieve + * \param langid the language ID for the string descriptor + * \param data output buffer for descriptor + * \param length size of data buffer + * \returns number of bytes returned in data, or LIBUSB_ERROR code on failure + * \see libusb_get_string_descriptor_ascii() + */ +static inline int libusb_get_string_descriptor(libusb_device_handle *dev, + uint8_t desc_index, uint16_t langid, unsigned char *data, int length) +{ + return libusb_control_transfer(dev, LIBUSB_ENDPOINT_IN, + LIBUSB_REQUEST_GET_DESCRIPTOR, (uint16_t)((LIBUSB_DT_STRING << 8) | desc_index), + langid, data, (uint16_t) length, 1000); +} + +int LIBUSB_CALL libusb_get_string_descriptor_ascii(libusb_device_handle *dev, + uint8_t desc_index, unsigned char *data, int length); + +/* polling and timeouts */ + +int LIBUSB_CALL libusb_try_lock_events(libusb_context *ctx); +void LIBUSB_CALL libusb_lock_events(libusb_context *ctx); +void LIBUSB_CALL libusb_unlock_events(libusb_context *ctx); +int LIBUSB_CALL libusb_event_handling_ok(libusb_context *ctx); +int LIBUSB_CALL libusb_event_handler_active(libusb_context *ctx); +void LIBUSB_CALL libusb_lock_event_waiters(libusb_context *ctx); +void LIBUSB_CALL libusb_unlock_event_waiters(libusb_context *ctx); +int LIBUSB_CALL libusb_wait_for_event(libusb_context *ctx, struct timeval *tv); + +int LIBUSB_CALL libusb_handle_events_timeout(libusb_context *ctx, + struct timeval *tv); +int LIBUSB_CALL libusb_handle_events_timeout_completed(libusb_context *ctx, + struct timeval *tv, int *completed); +int LIBUSB_CALL libusb_handle_events(libusb_context *ctx); +int LIBUSB_CALL libusb_handle_events_completed(libusb_context *ctx, int *completed); +int LIBUSB_CALL libusb_handle_events_locked(libusb_context *ctx, + struct timeval *tv); +int LIBUSB_CALL libusb_pollfds_handle_timeouts(libusb_context *ctx); +int LIBUSB_CALL libusb_get_next_timeout(libusb_context *ctx, + struct timeval *tv); + +/** \ingroup poll + * File descriptor for polling + */ +struct libusb_pollfd { + /** Numeric file descriptor */ + int fd; + + /** Event flags to poll for from . POLLIN indicates that you + * should monitor this file descriptor for becoming ready to read from, + * and POLLOUT indicates that you should monitor this file descriptor for + * nonblocking write readiness. */ + short events; +}; + +/** \ingroup poll + * Callback function, invoked when a new file descriptor should be added + * to the set of file descriptors monitored for events. + * \param fd the new file descriptor + * \param events events to monitor for, see \ref libusb_pollfd for a + * description + * \param user_data User data pointer specified in + * libusb_set_pollfd_notifiers() call + * \see libusb_set_pollfd_notifiers() + */ +typedef void (LIBUSB_CALL *libusb_pollfd_added_cb)(int fd, short events, + void *user_data); + +/** \ingroup poll + * Callback function, invoked when a file descriptor should be removed from + * the set of file descriptors being monitored for events. After returning + * from this callback, do not use that file descriptor again. + * \param fd the file descriptor to stop monitoring + * \param user_data User data pointer specified in + * libusb_set_pollfd_notifiers() call + * \see libusb_set_pollfd_notifiers() + */ +typedef void (LIBUSB_CALL *libusb_pollfd_removed_cb)(int fd, void *user_data); + +const struct libusb_pollfd ** LIBUSB_CALL libusb_get_pollfds( + libusb_context *ctx); +void LIBUSB_CALL libusb_set_pollfd_notifiers(libusb_context *ctx, + libusb_pollfd_added_cb added_cb, libusb_pollfd_removed_cb removed_cb, + void *user_data); + +/** \ingroup desc + * Parse a USB 3.0 endpoint companion descriptor. + * + * \param[in] buf the buffer containing the endpoint companion descriptor + * \param[in] len the length of the buffer + * \param[out] ep_comp a parsed endpoint companion descriptor. must be freed by + * libusb_free_ss_endpoint_comp() + * + * \returns LIBUSB_SUCCESS on success + * \returns LIBUSB_ERROR code on error + */ +int LIBUSB_CALL libusb_parse_ss_endpoint_comp(const void *buf, int len, + struct libusb_ss_endpoint_companion_descriptor **ep_comp); + +/** \ingroup desc + * Free a USB 3.0 endpoint companion descriptor. + * + * \param[in] ep_comp the descriptor to free + */ +void LIBUSB_CALL libusb_free_ss_endpoint_comp(struct libusb_ss_endpoint_companion_descriptor *ep_comp); + +/** \ingroup desc + * Parse a Binary Object Store (BOS) descriptor. + * + * \param[in] buf the buffer containing the BOS descriptor + * \param[in] len the length of the buffer + * \param[out] bos a parsed BOS descriptor. must be freed by + * libusb_free_bos_descriptor() + * + * \returns LIBUSB_SUCCESS on success + * \returns LIBUSB_ERROR code on error + */ +int LIBUSB_CALL libusb_parse_bos_descriptor(const void *buf, int len, + struct libusb_bos_descriptor **bos); + +/** \ingroup desc + * Free a Binary Object Store (BOS) descriptor. + * + * \param[in] bos the descriptor to free + */ +void LIBUSB_CALL libusb_free_bos_descriptor(struct libusb_bos_descriptor *bos); + +/** \ingroup hotplug + * Callback handle. + * + * Callbacks handles are generated by libusb_hotplug_register_callback() + * and can be used to deregister callbacks. Callback handles are unique + * per libusb_context and it is safe to call libusb_hotplug_deregister_callback() + * on an already deregisted callback. + * + * For more information, see \ref hotplug. + */ +typedef int libusb_hotplug_callback_handle; + +/** \ingroup hotplug + * Flags for hotplug events */ +typedef enum { + /** Arm the callback and fire it for all matching currently attached devices. */ + LIBUSB_HOTPLUG_ENUMERATE = 1, +} libusb_hotplug_flag; + +/** \ingroup hotplug + * Hotplug events */ +typedef enum { + /** A device has been plugged in and is ready to use */ + LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED = 0x01, + + /** A device has left and is no longer available. + * It is the user's responsibility to call libusb_close on any handle associated with a disconnected device. + * It is safe to call libusb_get_device_descriptor on a device that has left */ + LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT = 0x02, +} libusb_hotplug_event; + +/** \ingroup hotplug + * Wildcard matching for hotplug events */ +#define LIBUSB_HOTPLUG_MATCH_ANY -1 + +/** \ingroup hotplug + * Hotplug callback function type. When requesting hotplug event notifications, + * you pass a pointer to a callback function of this type. + * + * This callback may be called by an internal event thread and as such it is + * recommended the callback do minimal processing before returning. + * + * libusb will call this function later, when a matching event had happened on + * a matching device. See \ref hotplug for more information. + * + * It is safe to call either libusb_hotplug_register_callback() or + * libusb_hotplug_deregister_callback() from within a callback function. + * + * \param libusb_context context of this notification + * \param device libusb_device this event occurred on + * \param event event that occurred + * \param user_data user data provided when this callback was registered + * \returns bool whether this callback is finished processing events. + * returning 1 will cause this callback to be deregistered + */ +typedef int (LIBUSB_CALL *libusb_hotplug_callback_fn)(libusb_context *ctx, + libusb_device *device, + libusb_hotplug_event event, + void *user_data); + +/** \ingroup hotplug + * Register a hotplug callback function + * + * Register a callback with the libusb_context. The callback will fire + * when a matching event occurs on a matching device. The callback is + * armed until either it is deregistered with libusb_hotplug_deregister_callback() + * or the supplied callback returns 1 to indicate it is finished processing events. + * + * \param[in] ctx context to register this callback with + * \param[in] events bitwise or of events that will trigger this callback. See \ref + * libusb_hotplug_event + * \param[in] flags hotplug callback flags. See \ref libusb_hotplug_flag + * \param[in] vendor_id the vendor id to match or \ref LIBUSB_HOTPLUG_MATCH_ANY + * \param[in] product_id the product id to match or \ref LIBUSB_HOTPLUG_MATCH_ANY + * \param[in] dev_class the device class to match or \ref LIBUSB_HOTPLUG_MATCH_ANY + * \param[in] cb_fn the function to be invoked on a matching event/device + * \param[in] user_data user data to pass to the callback function + * \param[out] handle pointer to store the handle of the allocated callback (can be NULL) + * \returns LIBUSB_SUCCESS on success LIBUSB_ERROR code on failure + */ +int LIBUSB_CALL libusb_hotplug_register_callback(libusb_context *ctx, + libusb_hotplug_event events, + libusb_hotplug_flag flags, + int vendor_id, int product_id, + int dev_class, + libusb_hotplug_callback_fn cb_fn, + void *user_data, + libusb_hotplug_callback_handle *handle); + +/** \ingroup hotplug + * Deregisters a hotplug callback. + * + * Deregister a callback from a libusb_context. This function is safe to call from within + * a hotplug callback. + * + * \param[in] ctx context this callback is registered with + * \param[in] handle the handle of the callback to deregister + */ +void LIBUSB_CALL libusb_hotplug_deregister_callback(libusb_context *ctx, + libusb_hotplug_callback_handle handle); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/compat/libusb-1.0/libusb/libusbi.h b/compat/libusb-1.0/libusb/libusbi.h new file mode 100644 index 0000000..198978e --- /dev/null +++ b/compat/libusb-1.0/libusb/libusbi.h @@ -0,0 +1,974 @@ +/* + * Internal header for libusb + * Copyright (C) 2007-2009 Daniel Drake + * Copyright (c) 2001 Johannes Erdfelt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef LIBUSBI_H +#define LIBUSBI_H + +#include + +#include +#include +#include +#include +#ifdef HAVE_POLL_H +#include +#endif + +#include +#include + +/* Inside the libusb code, mark all public functions as follows: + * return_type API_EXPORTED function_name(params) { ... } + * But if the function returns a pointer, mark it as follows: + * DEFAULT_VISIBILITY return_type * LIBUSB_CALL function_name(params) { ... } + * In the libusb public header, mark all declarations as: + * return_type LIBUSB_CALL function_name(params); + */ +#define API_EXPORTED LIBUSB_CALL DEFAULT_VISIBILITY + +#define DEVICE_DESC_LENGTH 18 + +#define USB_MAXENDPOINTS 32 +#define USB_MAXINTERFACES 32 +#define USB_MAXCONFIG 8 + +struct list_head { + struct list_head *prev, *next; +}; + +/* Get an entry from the list + * ptr - the address of this list_head element in "type" + * type - the data type that contains "member" + * member - the list_head element in "type" + */ +#define list_entry(ptr, type, member) \ + ((type *)((uintptr_t)(ptr) - (uintptr_t)(&((type *)0L)->member))) + +/* Get each entry from a list + * pos - A structure pointer has a "member" element + * head - list head + * member - the list_head element in "pos" + * type - the type of the first parameter + */ +#define list_for_each_entry(pos, head, member, type) \ + for (pos = list_entry((head)->next, type, member); \ + &pos->member != (head); \ + pos = list_entry(pos->member.next, type, member)) + +#define list_for_each_entry_safe(pos, n, head, member, type) \ + for (pos = list_entry((head)->next, type, member), \ + n = list_entry(pos->member.next, type, member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.next, type, member)) + +#define list_empty(entry) ((entry)->next == (entry)) + +static inline void list_init(struct list_head *entry) +{ + entry->prev = entry->next = entry; +} + +static inline void list_add(struct list_head *entry, struct list_head *head) +{ + entry->next = head->next; + entry->prev = head; + + head->next->prev = entry; + head->next = entry; +} + +static inline void list_add_tail(struct list_head *entry, + struct list_head *head) +{ + entry->next = head; + entry->prev = head->prev; + + head->prev->next = entry; + head->prev = entry; +} + +static inline void list_del(struct list_head *entry) +{ + entry->next->prev = entry->prev; + entry->prev->next = entry->next; +} + +#define container_of(ptr, type, member) ({ \ + const typeof( ((type *)0)->member ) *mptr = (ptr); \ + (type *)( (char *)mptr - offsetof(type,member) );}) + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) + +#define TIMESPEC_IS_SET(ts) ((ts)->tv_sec != 0 || (ts)->tv_nsec != 0) + +enum usbi_log_level { + LOG_LEVEL_DEBUG, + LOG_LEVEL_INFO, + LOG_LEVEL_WARNING, + LOG_LEVEL_ERROR, +}; + +void usbi_log(struct libusb_context *ctx, enum usbi_log_level level, + const char *function, const char *format, ...); + +void usbi_log_v(struct libusb_context *ctx, enum usbi_log_level level, + const char *function, const char *format, va_list args); + +#if !defined(_MSC_VER) || _MSC_VER >= 1400 + +#ifdef ENABLE_LOGGING +#define _usbi_log(ctx, level, ...) usbi_log(ctx, level, __FUNCTION__, __VA_ARGS__) +#else +#define _usbi_log(ctx, level, ...) do { (void)(ctx); } while(0) +#endif + +#ifdef ENABLE_DEBUG_LOGGING +#define usbi_dbg(...) _usbi_log(NULL, LOG_LEVEL_DEBUG, __VA_ARGS__) +#else +#define usbi_dbg(...) do {} while(0) +#endif + +#define usbi_info(ctx, ...) _usbi_log(ctx, LOG_LEVEL_INFO, __VA_ARGS__) +#define usbi_warn(ctx, ...) _usbi_log(ctx, LOG_LEVEL_WARNING, __VA_ARGS__) +#define usbi_err(ctx, ...) _usbi_log(ctx, LOG_LEVEL_ERROR, __VA_ARGS__) + +#else /* !defined(_MSC_VER) || _MSC_VER >= 1400 */ + +/* Old MS compilers don't support variadic macros. The code is simple, so we + * repeat it for each loglevel. Note that the debug case is special. + * + * Support for variadic macros was introduced in Visual C++ 2005. + * http://msdn.microsoft.com/en-us/library/ms177415%28v=VS.80%29.aspx + */ + +static inline void usbi_info(struct libusb_context *ctx, const char *fmt, ...) +{ +#ifdef ENABLE_LOGGING + va_list args; + va_start(args, fmt); + usbi_log_v(ctx, LOG_LEVEL_INFO, "", fmt, args); + va_end(args); +#else + (void)ctx; +#endif +} + +static inline void usbi_warn(struct libusb_context *ctx, const char *fmt, ...) +{ +#ifdef ENABLE_LOGGING + va_list args; + va_start(args, fmt); + usbi_log_v(ctx, LOG_LEVEL_WARNING, "", fmt, args); + va_end(args); +#else + (void)ctx; +#endif +} + +static inline void usbi_err(struct libusb_context *ctx, const char *fmt, ...) +{ +#ifdef ENABLE_LOGGING + va_list args; + va_start(args, fmt); + usbi_log_v(ctx, LOG_LEVEL_ERROR, "", fmt, args); + va_end(args); +#else + (void)ctx; +#endif +} + +static inline void usbi_dbg(const char *fmt, ...) +{ +#ifdef ENABLE_DEBUG_LOGGING + va_list args; + va_start(args, fmt); + usbi_log_v(NULL, LOG_LEVEL_DEBUG, "", fmt, args); + va_end(args); +#else + (void)fmt; +#endif +} + +#endif /* !defined(_MSC_VER) || _MSC_VER >= 1400 */ + +#define USBI_GET_CONTEXT(ctx) if (!(ctx)) (ctx) = usbi_default_context +#define DEVICE_CTX(dev) ((dev)->ctx) +#define HANDLE_CTX(handle) (DEVICE_CTX((handle)->dev)) +#define TRANSFER_CTX(transfer) (HANDLE_CTX((transfer)->dev_handle)) +#define ITRANSFER_CTX(transfer) \ + (TRANSFER_CTX(USBI_TRANSFER_TO_LIBUSB_TRANSFER(transfer))) + +#define IS_EPIN(ep) (0 != ((ep) & LIBUSB_ENDPOINT_IN)) +#define IS_EPOUT(ep) (!IS_EPIN(ep)) +#define IS_XFERIN(xfer) (0 != ((xfer)->endpoint & LIBUSB_ENDPOINT_IN)) +#define IS_XFEROUT(xfer) (!IS_XFERIN(xfer)) + +/* Internal abstractions for thread synchronization and poll */ +#if defined(THREADS_POSIX) +#include +#elif defined(OS_WINDOWS) +#include +#endif + +#if defined(OS_LINUX) || defined(OS_DARWIN) || defined(OS_OPENBSD) +#include +#include +#elif defined(OS_WINDOWS) +#include +#endif + +#if defined(OS_WINDOWS) && !defined(__GCC__) +#undef HAVE_GETTIMEOFDAY +int usbi_gettimeofday(struct timeval *tp, void *tzp); +#define LIBUSB_GETTIMEOFDAY_WIN32 +#define HAVE_USBI_GETTIMEOFDAY +#else +#ifdef HAVE_GETTIMEOFDAY +#define usbi_gettimeofday(tv, tz) gettimeofday((tv), (tz)) +#define HAVE_USBI_GETTIMEOFDAY +#endif +#endif + +extern struct libusb_context *usbi_default_context; + +struct libusb_context { + int debug; + int debug_fixed; + + /* internal control pipe, used for interrupting event handling when + * something needs to modify poll fds. */ + int ctrl_pipe[2]; + + struct list_head usb_devs; + usbi_mutex_t usb_devs_lock; + + /* A list of open handles. Backends are free to traverse this if required. + */ + struct list_head open_devs; + usbi_mutex_t open_devs_lock; + + /* A list of registered hotplug callbacks */ + struct list_head hotplug_cbs; + usbi_mutex_t hotplug_cbs_lock; + int hotplug_pipe[2]; + + /* this is a list of in-flight transfer handles, sorted by timeout + * expiration. URBs to timeout the soonest are placed at the beginning of + * the list, URBs that will time out later are placed after, and urbs with + * infinite timeout are always placed at the very end. */ + struct list_head flying_transfers; + usbi_mutex_t flying_transfers_lock; + + /* list of poll fds */ + struct list_head pollfds; + usbi_mutex_t pollfds_lock; + + /* a counter that is set when we want to interrupt event handling, in order + * to modify the poll fd set. and a lock to protect it. */ + unsigned int pollfd_modify; + usbi_mutex_t pollfd_modify_lock; + + /* user callbacks for pollfd changes */ + libusb_pollfd_added_cb fd_added_cb; + libusb_pollfd_removed_cb fd_removed_cb; + void *fd_cb_user_data; + + /* ensures that only one thread is handling events at any one time */ + usbi_mutex_t events_lock; + + /* used to see if there is an active thread doing event handling */ + int event_handler_active; + + /* used to wait for event completion in threads other than the one that is + * event handling */ + usbi_mutex_t event_waiters_lock; + usbi_cond_t event_waiters_cond; + +#ifdef USBI_TIMERFD_AVAILABLE + /* used for timeout handling, if supported by OS. + * this timerfd is maintained to trigger on the next pending timeout */ + int timerfd; +#endif + + struct list_head list; +}; + +#ifdef USBI_TIMERFD_AVAILABLE +#define usbi_using_timerfd(ctx) ((ctx)->timerfd >= 0) +#else +#define usbi_using_timerfd(ctx) (0) +#endif + +struct libusb_device { + /* lock protects refcnt, everything else is finalized at initialization + * time */ + usbi_mutex_t lock; + int refcnt; + + struct libusb_context *ctx; + + uint8_t bus_number; + uint8_t device_address; + uint8_t num_configurations; + enum libusb_speed speed; + + struct list_head list; + unsigned long session_data; + + struct libusb_device_descriptor device_descriptor; + int attached; + + unsigned char os_priv +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) + [] /* valid C99 code */ +#else + [0] /* non-standard, but usually working code */ +#endif + ; +}; + +struct libusb_device_handle { + /* lock protects claimed_interfaces */ + usbi_mutex_t lock; + unsigned long claimed_interfaces; + + struct list_head list; + struct libusb_device *dev; + unsigned char os_priv +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) + [] /* valid C99 code */ +#else + [0] /* non-standard, but usually working code */ +#endif + ; +}; + +enum { + USBI_CLOCK_MONOTONIC, + USBI_CLOCK_REALTIME +}; + +/* in-memory transfer layout: + * + * 1. struct usbi_transfer + * 2. struct libusb_transfer (which includes iso packets) [variable size] + * 3. os private data [variable size] + * + * from a libusb_transfer, you can get the usbi_transfer by rewinding the + * appropriate number of bytes. + * the usbi_transfer includes the number of allocated packets, so you can + * determine the size of the transfer and hence the start and length of the + * OS-private data. + */ + +struct usbi_transfer { + int num_iso_packets; + struct list_head list; + struct timeval timeout; + int transferred; + uint8_t flags; + + /* this lock is held during libusb_submit_transfer() and + * libusb_cancel_transfer() (allowing the OS backend to prevent duplicate + * cancellation, submission-during-cancellation, etc). the OS backend + * should also take this lock in the handle_events path, to prevent the user + * cancelling the transfer from another thread while you are processing + * its completion (presumably there would be races within your OS backend + * if this were possible). */ + usbi_mutex_t lock; +}; + +enum usbi_transfer_flags { + /* The transfer has timed out */ + USBI_TRANSFER_TIMED_OUT = 1 << 0, + + /* Set by backend submit_transfer() if the OS handles timeout */ + USBI_TRANSFER_OS_HANDLES_TIMEOUT = 1 << 1, + + /* Cancellation was requested via libusb_cancel_transfer() */ + USBI_TRANSFER_CANCELLING = 1 << 2, + + /* Operation on the transfer failed because the device disappeared */ + USBI_TRANSFER_DEVICE_DISAPPEARED = 1 << 3, + + /* Set by backend submit_transfer() if the fds in use were updated */ + USBI_TRANSFER_UPDATED_FDS = 1 << 4, +}; + +#define USBI_TRANSFER_TO_LIBUSB_TRANSFER(transfer) \ + ((struct libusb_transfer *)(((unsigned char *)(transfer)) \ + + sizeof(struct usbi_transfer))) +#define LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer) \ + ((struct usbi_transfer *)(((unsigned char *)(transfer)) \ + - sizeof(struct usbi_transfer))) + +static inline void *usbi_transfer_get_os_priv(struct usbi_transfer *transfer) +{ + return ((unsigned char *)transfer) + sizeof(struct usbi_transfer) + + sizeof(struct libusb_transfer) + + (transfer->num_iso_packets + * sizeof(struct libusb_iso_packet_descriptor)); +} + +/* bus structures */ + +/* All standard descriptors have these 2 fields in common */ +struct usb_descriptor_header { + uint8_t bLength; + uint8_t bDescriptorType; +}; + +/* shared data and functions */ + +int usbi_io_init(struct libusb_context *ctx); +void usbi_io_exit(struct libusb_context *ctx); + +struct libusb_device *usbi_alloc_device(struct libusb_context *ctx, + unsigned long session_id); +struct libusb_device *usbi_get_device_by_session_id(struct libusb_context *ctx, + unsigned long session_id); +int usbi_sanitize_device(struct libusb_device *dev); +void usbi_handle_disconnect(struct libusb_device_handle *handle); + +int usbi_handle_transfer_completion(struct usbi_transfer *itransfer, + enum libusb_transfer_status status); +int usbi_handle_transfer_cancellation(struct usbi_transfer *transfer); + +int usbi_parse_descriptor(const unsigned char *source, const char *descriptor, + void *dest, int host_endian); +int usbi_device_cache_descriptor(libusb_device *dev); +int usbi_get_config_index_by_value(struct libusb_device *dev, + uint8_t bConfigurationValue, int *idx); + +void usbi_connect_device (struct libusb_device *dev); +void usbi_disconnect_device (struct libusb_device *dev); + +/* polling */ + +struct usbi_pollfd { + /* must come first */ + struct libusb_pollfd pollfd; + + struct list_head list; +}; + +int usbi_add_pollfd(struct libusb_context *ctx, int fd, short events); +void usbi_remove_pollfd(struct libusb_context *ctx, int fd); +void usbi_fd_notification(struct libusb_context *ctx); + +/* device discovery */ + +/* we traverse usbfs without knowing how many devices we are going to find. + * so we create this discovered_devs model which is similar to a linked-list + * which grows when required. it can be freed once discovery has completed, + * eliminating the need for a list node in the libusb_device structure + * itself. */ +struct discovered_devs { + size_t len; + size_t capacity; + struct libusb_device *devices +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) + [] /* valid C99 code */ +#else + [0] /* non-standard, but usually working code */ +#endif + ; +}; + +struct discovered_devs *discovered_devs_append( + struct discovered_devs *discdevs, struct libusb_device *dev); + +/* OS abstraction */ + +/* This is the interface that OS backends need to implement. + * All fields are mandatory, except ones explicitly noted as optional. */ +struct usbi_os_backend { + /* A human-readable name for your backend, e.g. "Linux usbfs" */ + const char *name; + + /* Perform initialization of your backend. You might use this function + * to determine specific capabilities of the system, allocate required + * data structures for later, etc. + * + * This function is called when a libusb user initializes the library + * prior to use. + * + * Return 0 on success, or a LIBUSB_ERROR code on failure. + */ + int (*init)(struct libusb_context *ctx); + + /* Deinitialization. Optional. This function should destroy anything + * that was set up by init. + * + * This function is called when the user deinitializes the library. + */ + void (*exit)(void); + + /* Enumerate all the USB devices on the system, returning them in a list + * of discovered devices. + * + * Your implementation should enumerate all devices on the system, + * regardless of whether they have been seen before or not. + * + * When you have found a device, compute a session ID for it. The session + * ID should uniquely represent that particular device for that particular + * connection session since boot (i.e. if you disconnect and reconnect a + * device immediately after, it should be assigned a different session ID). + * If your OS cannot provide a unique session ID as described above, + * presenting a session ID of (bus_number << 8 | device_address) should + * be sufficient. Bus numbers and device addresses wrap and get reused, + * but that is an unlikely case. + * + * After computing a session ID for a device, call + * usbi_get_device_by_session_id(). This function checks if libusb already + * knows about the device, and if so, it provides you with a libusb_device + * structure for it. + * + * If usbi_get_device_by_session_id() returns NULL, it is time to allocate + * a new device structure for the device. Call usbi_alloc_device() to + * obtain a new libusb_device structure with reference count 1. Populate + * the bus_number and device_address attributes of the new device, and + * perform any other internal backend initialization you need to do. At + * this point, you should be ready to provide device descriptors and so + * on through the get_*_descriptor functions. Finally, call + * usbi_sanitize_device() to perform some final sanity checks on the + * device. Assuming all of the above succeeded, we can now continue. + * If any of the above failed, remember to unreference the device that + * was returned by usbi_alloc_device(). + * + * At this stage we have a populated libusb_device structure (either one + * that was found earlier, or one that we have just allocated and + * populated). This can now be added to the discovered devices list + * using discovered_devs_append(). Note that discovered_devs_append() + * may reallocate the list, returning a new location for it, and also + * note that reallocation can fail. Your backend should handle these + * error conditions appropriately. + * + * This function should not generate any bus I/O and should not block. + * If I/O is required (e.g. reading the active configuration value), it is + * OK to ignore these suggestions :) + * + * This function is executed when the user wishes to retrieve a list + * of USB devices connected to the system. + * + * Return 0 on success, or a LIBUSB_ERROR code on failure. + */ + int (*get_device_list)(struct libusb_context *ctx, + struct discovered_devs **discdevs); + + /* Open a device for I/O and other USB operations. The device handle + * is preallocated for you, you can retrieve the device in question + * through handle->dev. + * + * Your backend should allocate any internal resources required for I/O + * and other operations so that those operations can happen (hopefully) + * without hiccup. This is also a good place to inform libusb that it + * should monitor certain file descriptors related to this device - + * see the usbi_add_pollfd() function. + * + * This function should not generate any bus I/O and should not block. + * + * This function is called when the user attempts to obtain a device + * handle for a device. + * + * Return: + * - 0 on success + * - LIBUSB_ERROR_ACCESS if the user has insufficient permissions + * - LIBUSB_ERROR_NO_DEVICE if the device has been disconnected since + * discovery + * - another LIBUSB_ERROR code on other failure + * + * Do not worry about freeing the handle on failed open, the upper layers + * do this for you. + */ + int (*open)(struct libusb_device_handle *handle); + + /* Close a device such that the handle cannot be used again. Your backend + * should destroy any resources that were allocated in the open path. + * This may also be a good place to call usbi_remove_pollfd() to inform + * libusb of any file descriptors associated with this device that should + * no longer be monitored. + * + * This function is called when the user closes a device handle. + */ + void (*close)(struct libusb_device_handle *handle); + + /* Retrieve the device descriptor from a device. + * + * The descriptor should be retrieved from memory, NOT via bus I/O to the + * device. This means that you may have to cache it in a private structure + * during get_device_list enumeration. Alternatively, you may be able + * to retrieve it from a kernel interface (some Linux setups can do this) + * still without generating bus I/O. + * + * This function is expected to write DEVICE_DESC_LENGTH (18) bytes into + * buffer, which is guaranteed to be big enough. + * + * This function is called when sanity-checking a device before adding + * it to the list of discovered devices, and also when the user requests + * to read the device descriptor. + * + * This function is expected to return the descriptor in bus-endian format + * (LE). If it returns the multi-byte values in host-endian format, + * set the host_endian output parameter to "1". + * + * Return 0 on success or a LIBUSB_ERROR code on failure. + */ + int (*get_device_descriptor)(struct libusb_device *device, + unsigned char *buffer, int *host_endian); + + /* Get the ACTIVE configuration descriptor for a device. + * + * The descriptor should be retrieved from memory, NOT via bus I/O to the + * device. This means that you may have to cache it in a private structure + * during get_device_list enumeration. You may also have to keep track + * of which configuration is active when the user changes it. + * + * This function is expected to write len bytes of data into buffer, which + * is guaranteed to be big enough. If you can only do a partial write, + * return an error code. + * + * This function is expected to return the descriptor in bus-endian format + * (LE). If it returns the multi-byte values in host-endian format, + * set the host_endian output parameter to "1". + * + * Return: + * - 0 on success + * - LIBUSB_ERROR_NOT_FOUND if the device is in unconfigured state + * - another LIBUSB_ERROR code on other failure + */ + int (*get_active_config_descriptor)(struct libusb_device *device, + unsigned char *buffer, size_t len, int *host_endian); + + /* Get a specific configuration descriptor for a device. + * + * The descriptor should be retrieved from memory, NOT via bus I/O to the + * device. This means that you may have to cache it in a private structure + * during get_device_list enumeration. + * + * The requested descriptor is expressed as a zero-based index (i.e. 0 + * indicates that we are requesting the first descriptor). The index does + * not (necessarily) equal the bConfigurationValue of the configuration + * being requested. + * + * This function is expected to write len bytes of data into buffer, which + * is guaranteed to be big enough. If you can only do a partial write, + * return an error code. + * + * This function is expected to return the descriptor in bus-endian format + * (LE). If it returns the multi-byte values in host-endian format, + * set the host_endian output parameter to "1". + * + * Return 0 on success or a LIBUSB_ERROR code on failure. + */ + int (*get_config_descriptor)(struct libusb_device *device, + uint8_t config_index, unsigned char *buffer, size_t len, + int *host_endian); + + /* Get the bConfigurationValue for the active configuration for a device. + * Optional. This should only be implemented if you can retrieve it from + * cache (don't generate I/O). + * + * If you cannot retrieve this from cache, either do not implement this + * function, or return LIBUSB_ERROR_NOT_SUPPORTED. This will cause + * libusb to retrieve the information through a standard control transfer. + * + * This function must be non-blocking. + * Return: + * - 0 on success + * - LIBUSB_ERROR_NO_DEVICE if the device has been disconnected since it + * was opened + * - LIBUSB_ERROR_NOT_SUPPORTED if the value cannot be retrieved without + * blocking + * - another LIBUSB_ERROR code on other failure. + */ + int (*get_configuration)(struct libusb_device_handle *handle, int *config); + + /* Set the active configuration for a device. + * + * A configuration value of -1 should put the device in unconfigured state. + * + * This function can block. + * + * Return: + * - 0 on success + * - LIBUSB_ERROR_NOT_FOUND if the configuration does not exist + * - LIBUSB_ERROR_BUSY if interfaces are currently claimed (and hence + * configuration cannot be changed) + * - LIBUSB_ERROR_NO_DEVICE if the device has been disconnected since it + * was opened + * - another LIBUSB_ERROR code on other failure. + */ + int (*set_configuration)(struct libusb_device_handle *handle, int config); + + /* Claim an interface. When claimed, the application can then perform + * I/O to an interface's endpoints. + * + * This function should not generate any bus I/O and should not block. + * Interface claiming is a logical operation that simply ensures that + * no other drivers/applications are using the interface, and after + * claiming, no other drivers/applicatiosn can use the interface because + * we now "own" it. + * + * Return: + * - 0 on success + * - LIBUSB_ERROR_NOT_FOUND if the interface does not exist + * - LIBUSB_ERROR_BUSY if the interface is in use by another driver/app + * - LIBUSB_ERROR_NO_DEVICE if the device has been disconnected since it + * was opened + * - another LIBUSB_ERROR code on other failure + */ + int (*claim_interface)(struct libusb_device_handle *handle, int interface_number); + + /* Release a previously claimed interface. + * + * This function should also generate a SET_INTERFACE control request, + * resetting the alternate setting of that interface to 0. It's OK for + * this function to block as a result. + * + * You will only ever be asked to release an interface which was + * successfully claimed earlier. + * + * Return: + * - 0 on success + * - LIBUSB_ERROR_NO_DEVICE if the device has been disconnected since it + * was opened + * - another LIBUSB_ERROR code on other failure + */ + int (*release_interface)(struct libusb_device_handle *handle, int interface_number); + + /* Set the alternate setting for an interface. + * + * You will only ever be asked to set the alternate setting for an + * interface which was successfully claimed earlier. + * + * It's OK for this function to block. + * + * Return: + * - 0 on success + * - LIBUSB_ERROR_NOT_FOUND if the alternate setting does not exist + * - LIBUSB_ERROR_NO_DEVICE if the device has been disconnected since it + * was opened + * - another LIBUSB_ERROR code on other failure + */ + int (*set_interface_altsetting)(struct libusb_device_handle *handle, + int interface_number, int altsetting); + + /* Clear a halt/stall condition on an endpoint. + * + * It's OK for this function to block. + * + * Return: + * - 0 on success + * - LIBUSB_ERROR_NOT_FOUND if the endpoint does not exist + * - LIBUSB_ERROR_NO_DEVICE if the device has been disconnected since it + * was opened + * - another LIBUSB_ERROR code on other failure + */ + int (*clear_halt)(struct libusb_device_handle *handle, + unsigned char endpoint); + + /* Perform a USB port reset to reinitialize a device. + * + * If possible, the handle should still be usable after the reset + * completes, assuming that the device descriptors did not change during + * reset and all previous interface state can be restored. + * + * If something changes, or you cannot easily locate/verify the resetted + * device, return LIBUSB_ERROR_NOT_FOUND. This prompts the application + * to close the old handle and re-enumerate the device. + * + * Return: + * - 0 on success + * - LIBUSB_ERROR_NOT_FOUND if re-enumeration is required, or if the device + * has been disconnected since it was opened + * - another LIBUSB_ERROR code on other failure + */ + int (*reset_device)(struct libusb_device_handle *handle); + + /* Determine if a kernel driver is active on an interface. Optional. + * + * The presence of a kernel driver on an interface indicates that any + * calls to claim_interface would fail with the LIBUSB_ERROR_BUSY code. + * + * Return: + * - 0 if no driver is active + * - 1 if a driver is active + * - LIBUSB_ERROR_NO_DEVICE if the device has been disconnected since it + * was opened + * - another LIBUSB_ERROR code on other failure + */ + int (*kernel_driver_active)(struct libusb_device_handle *handle, + int interface_number); + + /* Detach a kernel driver from an interface. Optional. + * + * After detaching a kernel driver, the interface should be available + * for claim. + * + * Return: + * - 0 on success + * - LIBUSB_ERROR_NOT_FOUND if no kernel driver was active + * - LIBUSB_ERROR_INVALID_PARAM if the interface does not exist + * - LIBUSB_ERROR_NO_DEVICE if the device has been disconnected since it + * was opened + * - another LIBUSB_ERROR code on other failure + */ + int (*detach_kernel_driver)(struct libusb_device_handle *handle, + int interface_number); + + /* Attach a kernel driver to an interface. Optional. + * + * Reattach a kernel driver to the device. + * + * Return: + * - 0 on success + * - LIBUSB_ERROR_NOT_FOUND if no kernel driver was active + * - LIBUSB_ERROR_INVALID_PARAM if the interface does not exist + * - LIBUSB_ERROR_NO_DEVICE if the device has been disconnected since it + * was opened + * - LIBUSB_ERROR_BUSY if a program or driver has claimed the interface, + * preventing reattachment + * - another LIBUSB_ERROR code on other failure + */ + int (*attach_kernel_driver)(struct libusb_device_handle *handle, + int interface_number); + + /* Destroy a device. Optional. + * + * This function is called when the last reference to a device is + * destroyed. It should free any resources allocated in the get_device_list + * path. + */ + void (*destroy_device)(struct libusb_device *dev); + + /* Submit a transfer. Your implementation should take the transfer, + * morph it into whatever form your platform requires, and submit it + * asynchronously. + * + * This function must not block. + * + * Return: + * - 0 on success + * - LIBUSB_ERROR_NO_DEVICE if the device has been disconnected + * - another LIBUSB_ERROR code on other failure + */ + int (*submit_transfer)(struct usbi_transfer *itransfer); + + /* Cancel a previously submitted transfer. + * + * This function must not block. The transfer cancellation must complete + * later, resulting in a call to usbi_handle_transfer_cancellation() + * from the context of handle_events. + */ + int (*cancel_transfer)(struct usbi_transfer *itransfer); + + /* Clear a transfer as if it has completed or cancelled, but do not + * report any completion/cancellation to the library. You should free + * all private data from the transfer as if you were just about to report + * completion or cancellation. + * + * This function might seem a bit out of place. It is used when libusb + * detects a disconnected device - it calls this function for all pending + * transfers before reporting completion (with the disconnect code) to + * the user. Maybe we can improve upon this internal interface in future. + */ + void (*clear_transfer_priv)(struct usbi_transfer *itransfer); + + /* Handle any pending events. This involves monitoring any active + * transfers and processing their completion or cancellation. + * + * The function is passed an array of pollfd structures (size nfds) + * as a result of the poll() system call. The num_ready parameter + * indicates the number of file descriptors that have reported events + * (i.e. the poll() return value). This should be enough information + * for you to determine which actions need to be taken on the currently + * active transfers. + * + * For any cancelled transfers, call usbi_handle_transfer_cancellation(). + * For completed transfers, call usbi_handle_transfer_completion(). + * For control/bulk/interrupt transfers, populate the "transferred" + * element of the appropriate usbi_transfer structure before calling the + * above functions. For isochronous transfers, populate the status and + * transferred fields of the iso packet descriptors of the transfer. + * + * This function should also be able to detect disconnection of the + * device, reporting that situation with usbi_handle_disconnect(). + * + * When processing an event related to a transfer, you probably want to + * take usbi_transfer.lock to prevent races. See the documentation for + * the usbi_transfer structure. + * + * Return 0 on success, or a LIBUSB_ERROR code on failure. + */ + int (*handle_events)(struct libusb_context *ctx, + struct pollfd *fds, POLL_NFDS_TYPE nfds, int num_ready); + + /* Get time from specified clock. At least two clocks must be implemented + by the backend: USBI_CLOCK_REALTIME, and USBI_CLOCK_MONOTONIC. + + Description of clocks: + USBI_CLOCK_REALTIME : clock returns time since system epoch. + USBI_CLOCK_MONOTONIC: clock returns time since unspecified start + time (usually boot). + */ + int (*clock_gettime)(int clkid, struct timespec *tp); + +#ifdef USBI_TIMERFD_AVAILABLE + /* clock ID of the clock that should be used for timerfd */ + clockid_t (*get_timerfd_clockid)(void); +#endif + + /* Number of bytes to reserve for per-device private backend data. + * This private data area is accessible through the "os_priv" field of + * struct libusb_device. */ + size_t device_priv_size; + + /* Number of bytes to reserve for per-handle private backend data. + * This private data area is accessible through the "os_priv" field of + * struct libusb_device. */ + size_t device_handle_priv_size; + + /* Number of bytes to reserve for per-transfer private backend data. + * This private data area is accessible by calling + * usbi_transfer_get_os_priv() on the appropriate usbi_transfer instance. + */ + size_t transfer_priv_size; + + /* Mumber of additional bytes for os_priv for each iso packet. + * Can your backend use this? */ + /* FIXME: linux can't use this any more. if other OS's cannot either, + * then remove this */ + size_t add_iso_packet_size; +}; + +extern const struct usbi_os_backend * const usbi_backend; + +extern const struct usbi_os_backend linux_usbfs_backend; +extern const struct usbi_os_backend darwin_backend; +extern const struct usbi_os_backend openbsd_backend; +extern const struct usbi_os_backend windows_backend; + +extern struct list_head active_contexts_list; +extern usbi_mutex_static_t active_contexts_lock; + +#endif + diff --git a/compat/libusb-1.0/libusb/os/darwin_usb.c b/compat/libusb-1.0/libusb/os/darwin_usb.c new file mode 100644 index 0000000..b31e818 --- /dev/null +++ b/compat/libusb-1.0/libusb/os/darwin_usb.c @@ -0,0 +1,1788 @@ +/* -*- Mode: C; indent-tabs-mode:nil -*- */ +/* + * darwin backend for libusb 1.0 + * Copyright (C) 2008-2013 Nathan Hjelm + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 + #include +#endif + +#include "darwin_usb.h" + +/* async event thread */ +static pthread_mutex_t libusb_darwin_at_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t libusb_darwin_at_cond = PTHREAD_COND_INITIALIZER; + +static clock_serv_t clock_realtime; +static clock_serv_t clock_monotonic; + +static CFRunLoopRef libusb_darwin_acfl = NULL; /* event cf loop */ +static volatile int32_t initCount = 0; + +/* async event thread */ +static pthread_t libusb_darwin_at; + +static int darwin_get_config_descriptor(struct libusb_device *dev, uint8_t config_index, unsigned char *buffer, size_t len, int *host_endian); +static int darwin_claim_interface(struct libusb_device_handle *dev_handle, int iface); +static int darwin_release_interface(struct libusb_device_handle *dev_handle, int iface); +static int darwin_reset_device(struct libusb_device_handle *dev_handle); +static void darwin_async_io_callback (void *refcon, IOReturn result, void *arg0); + +static int darwin_scan_devices(struct libusb_context *ctx); +static int process_new_device (struct libusb_context *ctx, usb_device_t **device, UInt32 locationID); + +#if defined(ENABLE_LOGGING) +static const char *darwin_error_str (int result) { + switch (result) { + case kIOReturnSuccess: + return "no error"; + case kIOReturnNotOpen: + return "device not opened for exclusive access"; + case kIOReturnNoDevice: + return "no connection to an IOService"; + case kIOUSBNoAsyncPortErr: + return "no async port has been opened for interface"; + case kIOReturnExclusiveAccess: + return "another process has device opened for exclusive access"; + case kIOUSBPipeStalled: + return "pipe is stalled"; + case kIOReturnError: + return "could not establish a connection to the Darwin kernel"; + case kIOUSBTransactionTimeout: + return "transaction timed out"; + case kIOReturnBadArgument: + return "invalid argument"; + case kIOReturnAborted: + return "transaction aborted"; + case kIOReturnNotResponding: + return "device not responding"; + case kIOReturnOverrun: + return "data overrun"; + case kIOReturnCannotWire: + return "physical memory can not be wired down"; + case kIOReturnNoResources: + return "out of resources"; + default: + return "unknown error"; + } +} +#endif + +static int darwin_to_libusb (int result) { + switch (result) { + case kIOReturnUnderrun: + case kIOReturnSuccess: + return LIBUSB_SUCCESS; + case kIOReturnNotOpen: + case kIOReturnNoDevice: + return LIBUSB_ERROR_NO_DEVICE; + case kIOReturnExclusiveAccess: + return LIBUSB_ERROR_ACCESS; + case kIOUSBPipeStalled: + return LIBUSB_ERROR_PIPE; + case kIOReturnBadArgument: + return LIBUSB_ERROR_INVALID_PARAM; + case kIOUSBTransactionTimeout: + return LIBUSB_ERROR_TIMEOUT; + case kIOReturnNotResponding: + case kIOReturnAborted: + case kIOReturnError: + case kIOUSBNoAsyncPortErr: + default: + return LIBUSB_ERROR_OTHER; + } +} + + +static int ep_to_pipeRef(struct libusb_device_handle *dev_handle, uint8_t ep, uint8_t *pipep, uint8_t *ifcp) { + struct darwin_device_handle_priv *priv = (struct darwin_device_handle_priv *)dev_handle->os_priv; + + /* current interface */ + struct darwin_interface *cInterface; + + int8_t i, iface; + + usbi_info (HANDLE_CTX(dev_handle), "converting ep address 0x%02x to pipeRef and interface", ep); + + for (iface = 0 ; iface < USB_MAXINTERFACES ; iface++) { + cInterface = &priv->interfaces[iface]; + + if (dev_handle->claimed_interfaces & (1 << iface)) { + for (i = 0 ; i < cInterface->num_endpoints ; i++) { + if (cInterface->endpoint_addrs[i] == ep) { + *pipep = i + 1; + *ifcp = iface; + usbi_info (HANDLE_CTX(dev_handle), "pipe %d on interface %d matches", *pipep, *ifcp); + return 0; + } + } + } + } + + /* No pipe found with the correct endpoint address */ + usbi_warn (HANDLE_CTX(dev_handle), "no pipeRef found with endpoint address 0x%02x.", ep); + + return -1; +} + +static int usb_setup_device_iterator (io_iterator_t *deviceIterator, UInt32 location) { + CFMutableDictionaryRef matchingDict = IOServiceMatching(kIOUSBDeviceClassName); + + if (!matchingDict) + return kIOReturnError; + + if (location) { + CFMutableDictionaryRef propertyMatchDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + + if (propertyMatchDict) { + /* there are no unsigned CFNumber types so treat the value as signed. the os seems to do this + internally (CFNumberType of locationID is 3) */ + CFTypeRef locationCF = CFNumberCreate (NULL, kCFNumberSInt32Type, &location); + + CFDictionarySetValue (propertyMatchDict, CFSTR(kUSBDevicePropertyLocationID), locationCF); + /* release our reference to the CFNumber (CFDictionarySetValue retains it) */ + CFRelease (locationCF); + + CFDictionarySetValue (matchingDict, CFSTR(kIOPropertyMatchKey), propertyMatchDict); + /* release out reference to the CFMutableDictionaryRef (CFDictionarySetValue retains it) */ + CFRelease (propertyMatchDict); + } + /* else we can still proceed as long as the caller accounts for the possibility of other devices in the iterator */ + } + + return IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, deviceIterator); +} + +static usb_device_t **usb_get_next_device (io_iterator_t deviceIterator, UInt32 *locationp) { + io_cf_plugin_ref_t *plugInInterface = NULL; + usb_device_t **device; + io_service_t usbDevice; + long result; + SInt32 score; + + if (!IOIteratorIsValid (deviceIterator)) + return NULL; + + + while ((usbDevice = IOIteratorNext(deviceIterator))) { + result = IOCreatePlugInInterfaceForService(usbDevice, kIOUSBDeviceUserClientTypeID, + kIOCFPlugInInterfaceID, &plugInInterface, + &score); + + /* we are done with the usb_device_t */ + (void)IOObjectRelease(usbDevice); + if (kIOReturnSuccess == result && plugInInterface) + break; + + usbi_dbg ("libusb/darwin.c usb_get_next_device: could not set up plugin for service: %s\n", darwin_error_str (result)); + } + + if (!usbDevice) + return NULL; + + (void)(*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(DeviceInterfaceID), + (LPVOID)&device); + /* Use release instead of IODestroyPlugInInterface to avoid stopping IOServices associated with this device */ + (*plugInInterface)->Release (plugInInterface); + + /* get the location from the device */ + if (locationp) + (*(device))->GetLocationID(device, locationp); + + return device; +} + +static void darwin_devices_attached (void *ptr, io_iterator_t add_devices) { + struct libusb_context *ctx; + usb_device_t **device; + UInt32 location; + + usbi_mutex_lock(&active_contexts_lock); + + while ((device = usb_get_next_device (add_devices, &location))) { + /* add this device to each active context's device list */ + list_for_each_entry(ctx, &active_contexts_list, list, struct libusb_context) { + process_new_device (ctx, device, location); + } + + /* release extra reference */ + (*device)->Release (device); + } + + usbi_mutex_unlock(&active_contexts_lock); +} + +static void darwin_devices_detached (void *ptr, io_iterator_t rem_devices) { + struct libusb_device *dev = NULL; + struct libusb_context *ctx; + + io_service_t device; + bool locationValid; + UInt32 location; + CFTypeRef locationCF; + + while ((device = IOIteratorNext (rem_devices)) != 0) { + /* get the location from the i/o registry */ + locationCF = IORegistryEntryCreateCFProperty (device, CFSTR(kUSBDevicePropertyLocationID), kCFAllocatorDefault, 0); + + IOObjectRelease (device); + + if (!locationCF) + continue; + + locationValid = CFGetTypeID(locationCF) == CFNumberGetTypeID() && + CFNumberGetValue(locationCF, kCFNumberSInt32Type, &location); + + CFRelease (locationCF); + + if (!locationValid) + continue; + + usbi_mutex_lock(&active_contexts_lock); + + list_for_each_entry(ctx, &active_contexts_list, list, struct libusb_context) { + usbi_dbg ("libusb/darwin.c darwin_devices_detached: notifying context %p of device disconnect", ctx); + + dev = usbi_get_device_by_session_id (ctx, location); + if (!dev) { + continue; + } + + /* signal the core that this device has been disconnected. the core will tear down this device + when the reference count reaches 0 */ + usbi_disconnect_device (dev); + } + + usbi_mutex_unlock(&active_contexts_lock); + } +} + +static void darwin_clear_iterator (io_iterator_t iter) { + io_service_t device; + + while ((device = IOIteratorNext (iter)) != 0) + IOObjectRelease (device); +} + +static void *darwin_event_thread_main (void *arg0) { + IOReturn kresult; + struct libusb_context *ctx = (struct libusb_context *)arg0; + CFRunLoopRef runloop; + + /* Set this thread's name, so it can be seen in the debugger + and crash reports. */ +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 + pthread_setname_np ("org.libusb.device-hotplug"); +#endif + + /* Tell the Objective-C garbage collector about this thread. + This is required because, unlike NSThreads, pthreads are + not automatically registered. Although we don't use + Objective-C, we use CoreFoundation, which does. */ +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 + objc_registerThreadWithCollector(); +#endif + + /* hotplug (device arrival/removal) sources */ + CFRunLoopSourceRef libusb_notification_cfsource; + io_notification_port_t libusb_notification_port; + io_iterator_t libusb_rem_device_iterator; + io_iterator_t libusb_add_device_iterator; + + usbi_info (ctx, "creating hotplug event source"); + + runloop = CFRunLoopGetCurrent (); + CFRetain (runloop); + + /* add the notification port to the run loop */ + libusb_notification_port = IONotificationPortCreate (kIOMasterPortDefault); + libusb_notification_cfsource = IONotificationPortGetRunLoopSource (libusb_notification_port); + CFRunLoopAddSource(runloop, libusb_notification_cfsource, kCFRunLoopDefaultMode); + + /* create notifications for removed devices */ + kresult = IOServiceAddMatchingNotification (libusb_notification_port, kIOTerminatedNotification, + IOServiceMatching(kIOUSBDeviceClassName), + (IOServiceMatchingCallback)darwin_devices_detached, + (void *)ctx, &libusb_rem_device_iterator); + + if (kresult != kIOReturnSuccess) { + usbi_err (ctx, "could not add hotplug event source: %s", darwin_error_str (kresult)); + + pthread_exit (NULL); + } + + /* create notifications for attached devices */ + kresult = IOServiceAddMatchingNotification (libusb_notification_port, kIOFirstMatchNotification, + IOServiceMatching(kIOUSBDeviceClassName), + (IOServiceMatchingCallback)darwin_devices_attached, + (void *)ctx, &libusb_add_device_iterator); + + if (kresult != kIOReturnSuccess) { + usbi_err (ctx, "could not add hotplug event source: %s", darwin_error_str (kresult)); + + pthread_exit (NULL); + } + + /* arm notifiers */ + darwin_clear_iterator (libusb_rem_device_iterator); + darwin_clear_iterator (libusb_add_device_iterator); + + usbi_info (ctx, "darwin event thread ready to receive events"); + + /* signal the main thread that the hotplug runloop has been created. */ + pthread_mutex_lock (&libusb_darwin_at_mutex); + libusb_darwin_acfl = runloop; + pthread_cond_signal (&libusb_darwin_at_cond); + pthread_mutex_unlock (&libusb_darwin_at_mutex); + + /* run the runloop */ + CFRunLoopRun(); + + usbi_info (ctx, "darwin event thread exiting"); + + /* remove the notification cfsource */ + CFRunLoopRemoveSource(runloop, libusb_notification_cfsource, kCFRunLoopDefaultMode); + + /* delete notification port */ + IONotificationPortDestroy (libusb_notification_port); + + /* delete iterators */ + IOObjectRelease (libusb_rem_device_iterator); + IOObjectRelease (libusb_add_device_iterator); + + CFRelease (runloop); + + libusb_darwin_acfl = NULL; + + pthread_exit (NULL); +} + +static int darwin_init(struct libusb_context *ctx) { + host_name_port_t host_self; + int rc; + + rc = darwin_scan_devices (ctx); + if (LIBUSB_SUCCESS != rc) { + return rc; + } + + if (OSAtomicIncrement32Barrier(&initCount) == 1) { + /* create the clocks that will be used */ + + host_self = mach_host_self(); + host_get_clock_service(host_self, CALENDAR_CLOCK, &clock_realtime); + host_get_clock_service(host_self, SYSTEM_CLOCK, &clock_monotonic); + mach_port_deallocate(mach_task_self(), host_self); + + pthread_create (&libusb_darwin_at, NULL, darwin_event_thread_main, (void *)ctx); + + pthread_mutex_lock (&libusb_darwin_at_mutex); + while (!libusb_darwin_acfl) + pthread_cond_wait (&libusb_darwin_at_cond, &libusb_darwin_at_mutex); + pthread_mutex_unlock (&libusb_darwin_at_mutex); + } + + return 0; +} + +static void darwin_exit (void) { + if (OSAtomicDecrement32Barrier(&initCount) == 0) { + mach_port_deallocate(mach_task_self(), clock_realtime); + mach_port_deallocate(mach_task_self(), clock_monotonic); + + /* stop the event runloop and wait for the thread to terminate. */ + CFRunLoopStop (libusb_darwin_acfl); + pthread_join (libusb_darwin_at, NULL); + } +} + +static int darwin_get_device_descriptor(struct libusb_device *dev, unsigned char *buffer, int *host_endian) { + struct darwin_device_priv *priv = (struct darwin_device_priv *)dev->os_priv; + + /* return cached copy */ + memmove (buffer, &(priv->dev_descriptor), DEVICE_DESC_LENGTH); + + *host_endian = 0; + + return 0; +} + +static int get_configuration_index (struct libusb_device *dev, int config_value) { + struct darwin_device_priv *priv = (struct darwin_device_priv *)dev->os_priv; + UInt8 i, numConfig; + IOUSBConfigurationDescriptorPtr desc; + IOReturn kresult; + + /* is there a simpler way to determine the index? */ + kresult = (*(priv->device))->GetNumberOfConfigurations (priv->device, &numConfig); + if (kresult != kIOReturnSuccess) + return darwin_to_libusb (kresult); + + for (i = 0 ; i < numConfig ; i++) { + (*(priv->device))->GetConfigurationDescriptorPtr (priv->device, i, &desc); + + if (desc->bConfigurationValue == config_value) + return i; + } + + /* configuration not found */ + return LIBUSB_ERROR_OTHER; +} + +static int darwin_get_active_config_descriptor(struct libusb_device *dev, unsigned char *buffer, size_t len, int *host_endian) { + struct darwin_device_priv *priv = (struct darwin_device_priv *)dev->os_priv; + int config_index; + + if (0 == priv->active_config) + return LIBUSB_ERROR_NOT_FOUND; + + config_index = get_configuration_index (dev, priv->active_config); + if (config_index < 0) + return config_index; + + return darwin_get_config_descriptor (dev, config_index, buffer, len, host_endian); +} + +static int darwin_get_config_descriptor(struct libusb_device *dev, uint8_t config_index, unsigned char *buffer, size_t len, int *host_endian) { + struct darwin_device_priv *priv = (struct darwin_device_priv *)dev->os_priv; + IOUSBConfigurationDescriptorPtr desc; + IOReturn kresult; + + if (!priv || !priv->device) + return LIBUSB_ERROR_OTHER; + + kresult = (*priv->device)->GetConfigurationDescriptorPtr (priv->device, config_index, &desc); + if (kresult == kIOReturnSuccess) { + /* copy descriptor */ + if (libusb_le16_to_cpu(desc->wTotalLength) < len) + len = libusb_le16_to_cpu(desc->wTotalLength); + + memmove (buffer, desc, len); + + /* GetConfigurationDescriptorPtr returns the descriptor in USB bus order */ + *host_endian = 0; + } + + return darwin_to_libusb (kresult); +} + +/* check whether the os has configured the device */ +static int darwin_check_configuration (struct libusb_context *ctx, struct libusb_device *dev, usb_device_t **darwin_device) { + struct darwin_device_priv *priv = (struct darwin_device_priv *)dev->os_priv; + + IOUSBConfigurationDescriptorPtr configDesc; + IOUSBFindInterfaceRequest request; + kern_return_t kresult; + io_iterator_t interface_iterator; + io_service_t firstInterface; + + if (priv->dev_descriptor.bNumConfigurations < 1) { + usbi_err (ctx, "device has no configurations"); + return LIBUSB_ERROR_OTHER; /* no configurations at this speed so we can't use it */ + } + + /* find the first configuration */ + kresult = (*darwin_device)->GetConfigurationDescriptorPtr (darwin_device, 0, &configDesc); + priv->first_config = (kIOReturnSuccess == kresult) ? configDesc->bConfigurationValue : 1; + + /* check if the device is already configured. there is probably a better way than iterating over the + to accomplish this (the trick is we need to avoid a call to GetConfigurations since buggy devices + might lock up on the device request) */ + + /* Setup the Interface Request */ + request.bInterfaceClass = kIOUSBFindInterfaceDontCare; + request.bInterfaceSubClass = kIOUSBFindInterfaceDontCare; + request.bInterfaceProtocol = kIOUSBFindInterfaceDontCare; + request.bAlternateSetting = kIOUSBFindInterfaceDontCare; + + kresult = (*(darwin_device))->CreateInterfaceIterator(darwin_device, &request, &interface_iterator); + if (kresult) + return darwin_to_libusb (kresult); + + /* iterate once */ + firstInterface = IOIteratorNext(interface_iterator); + + /* done with the interface iterator */ + IOObjectRelease(interface_iterator); + + if (firstInterface) { + IOObjectRelease (firstInterface); + + /* device is configured */ + if (priv->dev_descriptor.bNumConfigurations == 1) + /* to avoid problems with some devices get the configurations value from the configuration descriptor */ + priv->active_config = priv->first_config; + else + /* devices with more than one configuration should work with GetConfiguration */ + (*darwin_device)->GetConfiguration (darwin_device, &priv->active_config); + } else + /* not configured */ + priv->active_config = 0; + + usbi_info (ctx, "active config: %u, first config: %u", priv->active_config, priv->first_config); + + return 0; +} + +static int darwin_request_descriptor (usb_device_t **device, UInt8 desc, UInt8 desc_index, void *buffer, size_t buffer_size) { + IOUSBDevRequest req; + + memset (buffer, 0, buffer_size); + + /* Set up request for descriptor/ */ + req.bmRequestType = USBmakebmRequestType(kUSBIn, kUSBStandard, kUSBDevice); + req.bRequest = kUSBRqGetDescriptor; + req.wValue = desc << 8; + req.wIndex = desc_index; + req.wLength = buffer_size; + req.pData = buffer; + + return (*device)->DeviceRequest (device, &req); +} + +static int darwin_cache_device_descriptor (struct libusb_context *ctx, struct libusb_device *dev, usb_device_t **device) { + struct darwin_device_priv *priv; + int retries = 2, delay = 30000; + int unsuspended = 0, try_unsuspend = 1, try_reconfigure = 1; + int is_open = 0; + int ret = 0, ret2; + UInt8 bDeviceClass; + UInt16 idProduct, idVendor; + + (*device)->GetDeviceClass (device, &bDeviceClass); + (*device)->GetDeviceProduct (device, &idProduct); + (*device)->GetDeviceVendor (device, &idVendor); + + priv = (struct darwin_device_priv *)dev->os_priv; + + /* try to open the device (we can usually continue even if this fails) */ + is_open = ((*device)->USBDeviceOpenSeize(device) == kIOReturnSuccess); + + /**** retrieve device descriptor ****/ + do { + /* according to Apple's documentation the device must be open for DeviceRequest but we may not be able to open some + * devices and Apple's USB Prober doesn't bother to open the device before issuing a descriptor request. Still, + * to follow the spec as closely as possible, try opening the device */ + ret = darwin_request_descriptor (device, kUSBDeviceDesc, 0, &priv->dev_descriptor, sizeof(priv->dev_descriptor)); + + + if (kIOReturnOverrun == ret && kUSBDeviceDesc == priv->dev_descriptor.bDescriptorType) + /* received an overrun error but we still received a device descriptor */ + ret = kIOReturnSuccess; + + if (kIOUSBVendorIDAppleComputer == idVendor) { + /* NTH: don't bother retrying or unsuspending Apple devices */ + break; + } + + if (kIOReturnSuccess == ret && (0 == priv->dev_descriptor.bNumConfigurations || + 0 == priv->dev_descriptor.bcdUSB)) { + /* work around for incorrectly configured devices */ + if (try_reconfigure && is_open) { + usbi_dbg("descriptor appears to be invalid. resetting configuration before trying again..."); + + /* set the first configuration */ + (*device)->SetConfiguration(device, 1); + + /* don't try to reconfigure again */ + try_reconfigure = 0; + } + + ret = kIOUSBPipeStalled; + } + + if (kIOReturnSuccess != ret && is_open && try_unsuspend) { + /* device may be suspended. unsuspend it and try again */ +#if DeviceVersion >= 320 + UInt32 info; + + /* IOUSBFamily 320+ provides a way to detect device suspension but earlier versions do not */ + (void)(*device)->GetUSBDeviceInformation (device, &info); + + try_unsuspend = info & (1 << kUSBInformationDeviceIsSuspendedBit); +#endif + + if (try_unsuspend) { + /* resume the device */ + ret2 = (*device)->USBDeviceSuspend (device, 0); + if (kIOReturnSuccess != ret2) { + /* prevent log spew from poorly behaving devices. this indicates the + os actually had trouble communicating with the device */ + usbi_dbg("could not retrieve device descriptor. failed to unsuspend: %s",darwin_error_str(ret2)); + } else + unsuspended = 1; + + try_unsuspend = 0; + } + } + + if (kIOReturnSuccess != ret) { + usbi_dbg("kernel responded with code: 0x%08x. sleeping for %d ms before trying again", ret, delay/1000); + /* sleep for a little while before trying again */ + usleep (delay); + } + } while (kIOReturnSuccess != ret && retries--); + + if (unsuspended) + /* resuspend the device */ + (void)(*device)->USBDeviceSuspend (device, 1); + + if (is_open) + (void) (*device)->USBDeviceClose (device); + + if (ret != kIOReturnSuccess) { + /* a debug message was already printed out for this error */ + if (LIBUSB_CLASS_HUB == bDeviceClass) + usbi_dbg ("could not retrieve device descriptor %.4x:%.4x: %s. skipping device", idVendor, idProduct, darwin_error_str (ret)); + else + usbi_warn (ctx, "could not retrieve device descriptor %.4x:%.4x: %s. skipping device", idVendor, idProduct, darwin_error_str (ret)); + + return -1; + } + + usbi_dbg ("device descriptor:"); + usbi_dbg (" bDescriptorType: 0x%02x", priv->dev_descriptor.bDescriptorType); + usbi_dbg (" bcdUSB: 0x%04x", priv->dev_descriptor.bcdUSB); + usbi_dbg (" bDeviceClass: 0x%02x", priv->dev_descriptor.bDeviceClass); + usbi_dbg (" bDeviceSubClass: 0x%02x", priv->dev_descriptor.bDeviceSubClass); + usbi_dbg (" bDeviceProtocol: 0x%02x", priv->dev_descriptor.bDeviceProtocol); + usbi_dbg (" bMaxPacketSize0: 0x%02x", priv->dev_descriptor.bMaxPacketSize0); + usbi_dbg (" idVendor: 0x%04x", priv->dev_descriptor.idVendor); + usbi_dbg (" idProduct: 0x%04x", priv->dev_descriptor.idProduct); + usbi_dbg (" bcdDevice: 0x%04x", priv->dev_descriptor.bcdDevice); + usbi_dbg (" iManufacturer: 0x%02x", priv->dev_descriptor.iManufacturer); + usbi_dbg (" iProduct: 0x%02x", priv->dev_descriptor.iProduct); + usbi_dbg (" iSerialNumber: 0x%02x", priv->dev_descriptor.iSerialNumber); + usbi_dbg (" bNumConfigurations: 0x%02x", priv->dev_descriptor.bNumConfigurations); + + /* catch buggy hubs (which appear to be virtual). Apple's own USB prober has problems with these devices. */ + if (libusb_le16_to_cpu (priv->dev_descriptor.idProduct) != idProduct) { + /* not a valid device */ + usbi_warn (ctx, "idProduct from iokit (%04x) does not match idProduct in descriptor (%04x). skipping device", + idProduct, libusb_le16_to_cpu (priv->dev_descriptor.idProduct)); + return -1; + } + + return 0; +} + +static int process_new_device (struct libusb_context *ctx, usb_device_t **device, UInt32 locationID) { + struct libusb_device *dev = NULL; + struct darwin_device_priv *priv; + UInt8 devSpeed; + UInt16 address; + int ret = 0; + + do { + usbi_info (ctx, "allocating new device for location 0x%08x", locationID); + + dev = usbi_alloc_device(ctx, locationID); + if (!dev) { + return LIBUSB_ERROR_NO_MEM; + } + + priv = (struct darwin_device_priv *)dev->os_priv; + priv->device = device; + + /* increment the device's reference count (it is decremented in darwin_destroy_device) */ + (*device)->AddRef (device); + + (*device)->GetDeviceAddress (device, (USBDeviceAddress *)&address); + + ret = darwin_cache_device_descriptor (ctx, dev, device); + if (ret < 0) + break; + + /* check current active configuration (and cache the first configuration value-- which may be used by claim_interface) */ + ret = darwin_check_configuration (ctx, dev, device); + if (ret < 0) + break; + + dev->bus_number = locationID >> 24; + dev->device_address = address; + + (*device)->GetDeviceSpeed (device, &devSpeed); + + switch (devSpeed) { + case kUSBDeviceSpeedLow: dev->speed = LIBUSB_SPEED_LOW; break; + case kUSBDeviceSpeedFull: dev->speed = LIBUSB_SPEED_FULL; break; + case kUSBDeviceSpeedHigh: dev->speed = LIBUSB_SPEED_HIGH; break; +#if DeviceVersion >= 500 + case kUSBDeviceSpeedSuper: dev->speed = LIBUSB_SPEED_SUPER; break; +#endif + default: + usbi_warn (ctx, "Got unknown device speed %d", devSpeed); + } + + /* save our location, we'll need this later */ + priv->location = locationID; + snprintf(priv->sys_path, 20, "%03i-%04x-%04x-%02x-%02x", address, priv->dev_descriptor.idVendor, priv->dev_descriptor.idProduct, + priv->dev_descriptor.bDeviceClass, priv->dev_descriptor.bDeviceSubClass); + + ret = usbi_sanitize_device (dev); + if (ret < 0) + break; + + usbi_info (ctx, "found device with address %d at %s", dev->device_address, priv->sys_path); + } while (0); + + if (0 == ret) { + usbi_connect_device (dev); + } else { + libusb_unref_device (dev); + } + + return ret; +} + +static int darwin_scan_devices(struct libusb_context *ctx) { + io_iterator_t deviceIterator; + usb_device_t **device; + kern_return_t kresult; + UInt32 location; + + kresult = usb_setup_device_iterator (&deviceIterator, 0); + if (kresult != kIOReturnSuccess) + return darwin_to_libusb (kresult); + + while ((device = usb_get_next_device (deviceIterator, &location)) != NULL) { + (void) process_new_device (ctx, device, location); + + /* process_new_device added a reference so we need to release the one + from QueryInterface */ + (*device)->Release (device); + } + + IOObjectRelease(deviceIterator); + + return 0; +} + +static int darwin_open (struct libusb_device_handle *dev_handle) { + struct darwin_device_handle_priv *priv = (struct darwin_device_handle_priv *)dev_handle->os_priv; + struct darwin_device_priv *dpriv = (struct darwin_device_priv *)dev_handle->dev->os_priv; + IOReturn kresult; + + if (0 == dpriv->open_count) { + /* try to open the device */ + kresult = (*(dpriv->device))->USBDeviceOpenSeize (dpriv->device); + if (kresult != kIOReturnSuccess) { + usbi_err (HANDLE_CTX (dev_handle), "USBDeviceOpen: %s", darwin_error_str(kresult)); + + if (kIOReturnExclusiveAccess != kresult) { + return darwin_to_libusb (kresult); + } + + /* it is possible to perform some actions on a device that is not open so do not return an error */ + priv->is_open = 0; + } else { + priv->is_open = 1; + } + + /* create async event source */ + kresult = (*(dpriv->device))->CreateDeviceAsyncEventSource (dpriv->device, &priv->cfSource); + if (kresult != kIOReturnSuccess) { + usbi_err (HANDLE_CTX (dev_handle), "CreateDeviceAsyncEventSource: %s", darwin_error_str(kresult)); + + if (priv->is_open) { + (*(dpriv->device))->USBDeviceClose (dpriv->device); + } + + priv->is_open = 0; + + return darwin_to_libusb (kresult); + } + + CFRetain (libusb_darwin_acfl); + + /* add the cfSource to the aync run loop */ + CFRunLoopAddSource(libusb_darwin_acfl, priv->cfSource, kCFRunLoopCommonModes); + } + + /* device opened successfully */ + dpriv->open_count++; + + /* create a file descriptor for notifications */ + pipe (priv->fds); + + /* set the pipe to be non-blocking */ + fcntl (priv->fds[1], F_SETFD, O_NONBLOCK); + + usbi_add_pollfd(HANDLE_CTX(dev_handle), priv->fds[0], POLLIN); + + usbi_info (HANDLE_CTX (dev_handle), "device open for access"); + + return 0; +} + +static void darwin_close (struct libusb_device_handle *dev_handle) { + struct darwin_device_handle_priv *priv = (struct darwin_device_handle_priv *)dev_handle->os_priv; + struct darwin_device_priv *dpriv = (struct darwin_device_priv *)dev_handle->dev->os_priv; + IOReturn kresult; + int i; + + if (dpriv->open_count == 0) { + /* something is probably very wrong if this is the case */ + usbi_err (HANDLE_CTX (dev_handle), "Close called on a device that was not open!\n"); + return; + } + + dpriv->open_count--; + + /* make sure all interfaces are released */ + for (i = 0 ; i < USB_MAXINTERFACES ; i++) + if (dev_handle->claimed_interfaces & (1 << i)) + libusb_release_interface (dev_handle, i); + + if (0 == dpriv->open_count) { + /* delete the device's async event source */ + if (priv->cfSource) { + CFRunLoopRemoveSource (libusb_darwin_acfl, priv->cfSource, kCFRunLoopDefaultMode); + CFRelease (priv->cfSource); + priv->cfSource = NULL; + CFRelease (libusb_darwin_acfl); + } + + if (priv->is_open) { + /* close the device */ + kresult = (*(dpriv->device))->USBDeviceClose(dpriv->device); + if (kresult) { + /* Log the fact that we had a problem closing the file, however failing a + * close isn't really an error, so return success anyway */ + usbi_err (HANDLE_CTX (dev_handle), "USBDeviceClose: %s", darwin_error_str(kresult)); + } + } + } + + /* file descriptors are maintained per-instance */ + usbi_remove_pollfd (HANDLE_CTX (dev_handle), priv->fds[0]); + close (priv->fds[1]); + close (priv->fds[0]); + + priv->fds[0] = priv->fds[1] = -1; +} + +static int darwin_get_configuration(struct libusb_device_handle *dev_handle, int *config) { + struct darwin_device_priv *dpriv = (struct darwin_device_priv *)dev_handle->dev->os_priv; + + *config = (int) dpriv->active_config; + + return 0; +} + +static int darwin_set_configuration(struct libusb_device_handle *dev_handle, int config) { + struct darwin_device_priv *dpriv = (struct darwin_device_priv *)dev_handle->dev->os_priv; + IOReturn kresult; + int i; + + /* Setting configuration will invalidate the interface, so we need + to reclaim it. First, dispose of existing interfaces, if any. */ + for (i = 0 ; i < USB_MAXINTERFACES ; i++) + if (dev_handle->claimed_interfaces & (1 << i)) + darwin_release_interface (dev_handle, i); + + kresult = (*(dpriv->device))->SetConfiguration (dpriv->device, config); + if (kresult != kIOReturnSuccess) + return darwin_to_libusb (kresult); + + /* Reclaim any interfaces. */ + for (i = 0 ; i < USB_MAXINTERFACES ; i++) + if (dev_handle->claimed_interfaces & (1 << i)) + darwin_claim_interface (dev_handle, i); + + dpriv->active_config = config; + + return 0; +} + +static int darwin_get_interface (usb_device_t **darwin_device, uint8_t ifc, io_service_t *usbInterfacep) { + IOUSBFindInterfaceRequest request; + kern_return_t kresult; + io_iterator_t interface_iterator; + CFTypeRef bInterfaceNumberCF; + int bInterfaceNumber; + + *usbInterfacep = IO_OBJECT_NULL; + + /* Setup the Interface Request */ + request.bInterfaceClass = kIOUSBFindInterfaceDontCare; + request.bInterfaceSubClass = kIOUSBFindInterfaceDontCare; + request.bInterfaceProtocol = kIOUSBFindInterfaceDontCare; + request.bAlternateSetting = kIOUSBFindInterfaceDontCare; + + kresult = (*(darwin_device))->CreateInterfaceIterator(darwin_device, &request, &interface_iterator); + if (kresult) + return kresult; + + while ((*usbInterfacep = IOIteratorNext(interface_iterator))) { + /* find the interface number */ + bInterfaceNumberCF = IORegistryEntryCreateCFProperty (*usbInterfacep, CFSTR("bInterfaceNumber"), + kCFAllocatorDefault, 0); + if (!bInterfaceNumberCF) { + continue; + } + + CFNumberGetValue(bInterfaceNumberCF, kCFNumberIntType, &bInterfaceNumber); + + CFRelease(bInterfaceNumberCF); + + if ((uint8_t) bInterfaceNumber == ifc) { + break; + } + + (void) IOObjectRelease (*usbInterfacep); + } + + /* done with the interface iterator */ + IOObjectRelease(interface_iterator); + + return 0; +} + +static int get_endpoints (struct libusb_device_handle *dev_handle, int iface) { + struct darwin_device_handle_priv *priv = (struct darwin_device_handle_priv *)dev_handle->os_priv; + + /* current interface */ + struct darwin_interface *cInterface = &priv->interfaces[iface]; + + kern_return_t kresult; + + u_int8_t numep, direction, number; + u_int8_t dont_care1, dont_care3; + u_int16_t dont_care2; + int i; + + usbi_info (HANDLE_CTX (dev_handle), "building table of endpoints."); + + /* retrieve the total number of endpoints on this interface */ + kresult = (*(cInterface->interface))->GetNumEndpoints(cInterface->interface, &numep); + if (kresult) { + usbi_err (HANDLE_CTX (dev_handle), "can't get number of endpoints for interface: %s", darwin_error_str(kresult)); + return darwin_to_libusb (kresult); + } + + /* iterate through pipe references */ + for (i = 1 ; i <= numep ; i++) { + kresult = (*(cInterface->interface))->GetPipeProperties(cInterface->interface, i, &direction, &number, &dont_care1, + &dont_care2, &dont_care3); + + if (kresult != kIOReturnSuccess) { + usbi_err (HANDLE_CTX (dev_handle), "error getting pipe information for pipe %d: %s", i, darwin_error_str(kresult)); + + return darwin_to_libusb (kresult); + } + + usbi_info (HANDLE_CTX (dev_handle), "interface: %i pipe %i: dir: %i number: %i", iface, i, direction, number); + + cInterface->endpoint_addrs[i - 1] = ((direction << 7 & LIBUSB_ENDPOINT_DIR_MASK) | (number & LIBUSB_ENDPOINT_ADDRESS_MASK)); + } + + cInterface->num_endpoints = numep; + + return 0; +} + +static int darwin_claim_interface(struct libusb_device_handle *dev_handle, int iface) { + struct darwin_device_priv *dpriv = (struct darwin_device_priv *)dev_handle->dev->os_priv; + struct darwin_device_handle_priv *priv = (struct darwin_device_handle_priv *)dev_handle->os_priv; + io_service_t usbInterface = IO_OBJECT_NULL; + IOReturn kresult; + IOCFPlugInInterface **plugInInterface = NULL; + SInt32 score; + + /* current interface */ + struct darwin_interface *cInterface = &priv->interfaces[iface]; + + kresult = darwin_get_interface (dpriv->device, iface, &usbInterface); + if (kresult != kIOReturnSuccess) + return darwin_to_libusb (kresult); + + /* make sure we have an interface */ + if (!usbInterface && dpriv->first_config != 0) { + usbi_info (HANDLE_CTX (dev_handle), "no interface found; setting configuration: %d", dpriv->first_config); + + /* set the configuration */ + kresult = darwin_set_configuration (dev_handle, dpriv->first_config); + if (kresult != LIBUSB_SUCCESS) { + usbi_err (HANDLE_CTX (dev_handle), "could not set configuration"); + return kresult; + } + + kresult = darwin_get_interface (dpriv->device, iface, &usbInterface); + if (kresult) { + usbi_err (HANDLE_CTX (dev_handle), "darwin_get_interface: %s", darwin_error_str(kresult)); + return darwin_to_libusb (kresult); + } + } + + if (!usbInterface) { + usbi_err (HANDLE_CTX (dev_handle), "interface not found"); + return LIBUSB_ERROR_NOT_FOUND; + } + + /* get an interface to the device's interface */ + kresult = IOCreatePlugInInterfaceForService (usbInterface, kIOUSBInterfaceUserClientTypeID, + kIOCFPlugInInterfaceID, &plugInInterface, &score); + + /* ignore release error */ + (void)IOObjectRelease (usbInterface); + + if (kresult) { + usbi_err (HANDLE_CTX (dev_handle), "IOCreatePlugInInterfaceForService: %s", darwin_error_str(kresult)); + return darwin_to_libusb (kresult); + } + + if (!plugInInterface) { + usbi_err (HANDLE_CTX (dev_handle), "plugin interface not found"); + return LIBUSB_ERROR_NOT_FOUND; + } + + /* Do the actual claim */ + kresult = (*plugInInterface)->QueryInterface(plugInInterface, + CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID), + (LPVOID)&cInterface->interface); + /* We no longer need the intermediate plug-in */ + /* Use release instead of IODestroyPlugInInterface to avoid stopping IOServices associated with this device */ + (*plugInInterface)->Release (plugInInterface); + if (kresult || !cInterface->interface) { + usbi_err (HANDLE_CTX (dev_handle), "QueryInterface: %s", darwin_error_str(kresult)); + return darwin_to_libusb (kresult); + } + + /* claim the interface */ + kresult = (*(cInterface->interface))->USBInterfaceOpen(cInterface->interface); + if (kresult) { + usbi_err (HANDLE_CTX (dev_handle), "USBInterfaceOpen: %s", darwin_error_str(kresult)); + return darwin_to_libusb (kresult); + } + + /* update list of endpoints */ + kresult = get_endpoints (dev_handle, iface); + if (kresult) { + /* this should not happen */ + darwin_release_interface (dev_handle, iface); + usbi_err (HANDLE_CTX (dev_handle), "could not build endpoint table"); + return kresult; + } + + cInterface->cfSource = NULL; + + /* create async event source */ + kresult = (*(cInterface->interface))->CreateInterfaceAsyncEventSource (cInterface->interface, &cInterface->cfSource); + if (kresult != kIOReturnSuccess) { + usbi_err (HANDLE_CTX (dev_handle), "could not create async event source"); + + /* can't continue without an async event source */ + (void)darwin_release_interface (dev_handle, iface); + + return darwin_to_libusb (kresult); + } + + /* add the cfSource to the async thread's run loop */ + CFRunLoopAddSource(libusb_darwin_acfl, cInterface->cfSource, kCFRunLoopDefaultMode); + + usbi_info (HANDLE_CTX (dev_handle), "interface opened"); + + return 0; +} + +static int darwin_release_interface(struct libusb_device_handle *dev_handle, int iface) { + struct darwin_device_handle_priv *priv = (struct darwin_device_handle_priv *)dev_handle->os_priv; + IOReturn kresult; + + /* current interface */ + struct darwin_interface *cInterface = &priv->interfaces[iface]; + + /* Check to see if an interface is open */ + if (!cInterface->interface) + return LIBUSB_SUCCESS; + + /* clean up endpoint data */ + cInterface->num_endpoints = 0; + + /* delete the interface's async event source */ + if (cInterface->cfSource) { + CFRunLoopRemoveSource (libusb_darwin_acfl, cInterface->cfSource, kCFRunLoopDefaultMode); + CFRelease (cInterface->cfSource); + } + + kresult = (*(cInterface->interface))->USBInterfaceClose(cInterface->interface); + if (kresult) + usbi_err (HANDLE_CTX (dev_handle), "USBInterfaceClose: %s", darwin_error_str(kresult)); + + kresult = (*(cInterface->interface))->Release(cInterface->interface); + if (kresult != kIOReturnSuccess) + usbi_err (HANDLE_CTX (dev_handle), "Release: %s", darwin_error_str(kresult)); + + cInterface->interface = IO_OBJECT_NULL; + + return darwin_to_libusb (kresult); +} + +static int darwin_set_interface_altsetting(struct libusb_device_handle *dev_handle, int iface, int altsetting) { + struct darwin_device_handle_priv *priv = (struct darwin_device_handle_priv *)dev_handle->os_priv; + IOReturn kresult; + + /* current interface */ + struct darwin_interface *cInterface = &priv->interfaces[iface]; + + if (!cInterface->interface) + return LIBUSB_ERROR_NO_DEVICE; + + kresult = (*(cInterface->interface))->SetAlternateInterface (cInterface->interface, altsetting); + if (kresult != kIOReturnSuccess) + darwin_reset_device (dev_handle); + + /* update list of endpoints */ + kresult = get_endpoints (dev_handle, iface); + if (kresult) { + /* this should not happen */ + darwin_release_interface (dev_handle, iface); + usbi_err (HANDLE_CTX (dev_handle), "could not build endpoint table"); + return kresult; + } + + return darwin_to_libusb (kresult); +} + +static int darwin_clear_halt(struct libusb_device_handle *dev_handle, unsigned char endpoint) { + struct darwin_device_handle_priv *priv = (struct darwin_device_handle_priv *)dev_handle->os_priv; + + /* current interface */ + struct darwin_interface *cInterface; + uint8_t pipeRef, iface; + IOReturn kresult; + + /* determine the interface/endpoint to use */ + if (ep_to_pipeRef (dev_handle, endpoint, &pipeRef, &iface) != 0) { + usbi_err (HANDLE_CTX (dev_handle), "endpoint not found on any open interface"); + + return LIBUSB_ERROR_NOT_FOUND; + } + + cInterface = &priv->interfaces[iface]; + +#if (InterfaceVersion < 190) + kresult = (*(cInterface->interface))->ClearPipeStall(cInterface->interface, pipeRef); +#else + /* newer versions of darwin support clearing additional bits on the device's endpoint */ + kresult = (*(cInterface->interface))->ClearPipeStallBothEnds(cInterface->interface, pipeRef); +#endif + if (kresult) + usbi_err (HANDLE_CTX (dev_handle), "ClearPipeStall: %s", darwin_error_str (kresult)); + + return darwin_to_libusb (kresult); +} + +static int darwin_reset_device(struct libusb_device_handle *dev_handle) { + struct darwin_device_priv *dpriv = (struct darwin_device_priv *)dev_handle->dev->os_priv; + IOUSBDeviceDescriptor descriptor; + IOUSBConfigurationDescriptorPtr cached_configuration; + IOUSBConfigurationDescriptor configuration; + bool reenumerate = false; + IOReturn kresult; + int i; + + kresult = (*(dpriv->device))->ResetDevice (dpriv->device); + if (kresult) { + usbi_err (HANDLE_CTX (dev_handle), "ResetDevice: %s", darwin_error_str (kresult)); + return darwin_to_libusb (kresult); + } + + do { + usbi_dbg ("darwin/reset_device: checking if device descriptor changed"); + + /* ignore return code. if we can't get a descriptor it might be worthwhile re-enumerating anway */ + (void) darwin_request_descriptor (dpriv->device, kUSBDeviceDesc, 0, &descriptor, sizeof (descriptor)); + + /* check if the device descriptor has changed */ + if (0 != memcmp (&dpriv->dev_descriptor, &descriptor, sizeof (descriptor))) { + reenumerate = true; + break; + } + + /* check if any configuration descriptor has changed */ + for (i = 0 ; i < descriptor.bNumConfigurations ; ++i) { + usbi_dbg ("darwin/reset_device: checking if configuration descriptor %d changed", i); + + (void) darwin_request_descriptor (dpriv->device, kUSBConfDesc, i, &configuration, sizeof (configuration)); + (*(dpriv->device))->GetConfigurationDescriptorPtr (dpriv->device, i, &cached_configuration); + + if (!cached_configuration || 0 != memcmp (cached_configuration, &configuration, sizeof (configuration))) { + reenumerate = true; + break; + } + } + } while (0); + + if (reenumerate) { + usbi_dbg ("darwin/reset_device: device requires reenumeration"); + (void) (*(dpriv->device))->USBDeviceReEnumerate (dpriv->device, 0); + return LIBUSB_ERROR_NOT_FOUND; + } + + usbi_dbg ("darwin/reset_device: device reset complete"); + + return LIBUSB_SUCCESS; +} + +static int darwin_kernel_driver_active(struct libusb_device_handle *dev_handle, int interface) { + struct darwin_device_priv *dpriv = (struct darwin_device_priv *)dev_handle->dev->os_priv; + io_service_t usbInterface; + CFTypeRef driver; + IOReturn kresult; + + kresult = darwin_get_interface (dpriv->device, interface, &usbInterface); + if (kresult) { + usbi_err (HANDLE_CTX (dev_handle), "darwin_get_interface: %s", darwin_error_str(kresult)); + + return darwin_to_libusb (kresult); + } + + driver = IORegistryEntryCreateCFProperty (usbInterface, kIOBundleIdentifierKey, kCFAllocatorDefault, 0); + IOObjectRelease (usbInterface); + + if (driver) { + CFRelease (driver); + + return 1; + } + + /* no driver */ + return 0; +} + +/* attaching/detaching kernel drivers is not currently supported (maybe in the future?) */ +static int darwin_attach_kernel_driver (struct libusb_device_handle *dev_handle, int interface) { + (void)dev_handle; + (void)interface; + return LIBUSB_ERROR_NOT_SUPPORTED; +} + +static int darwin_detach_kernel_driver (struct libusb_device_handle *dev_handle, int interface) { + (void)dev_handle; + (void)interface; + return LIBUSB_ERROR_NOT_SUPPORTED; +} + +static void darwin_destroy_device(struct libusb_device *dev) { + struct darwin_device_priv *dpriv = (struct darwin_device_priv *) dev->os_priv; + + if (dpriv->device) { + /* it is an internal error if the reference count of a device is < 0 after release */ + assert(0 <= (*(dpriv->device))->Release(dpriv->device)); + + dpriv->device = NULL; + } +} + +static int submit_bulk_transfer(struct usbi_transfer *itransfer) { + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct darwin_device_handle_priv *priv = (struct darwin_device_handle_priv *)transfer->dev_handle->os_priv; + + IOReturn ret; + uint8_t transferType; + /* None of the values below are used in libusb for bulk transfers */ + uint8_t direction, number, interval, pipeRef, iface; + uint16_t maxPacketSize; + + struct darwin_interface *cInterface; + + if (ep_to_pipeRef (transfer->dev_handle, transfer->endpoint, &pipeRef, &iface) != 0) { + usbi_err (TRANSFER_CTX (transfer), "endpoint not found on any open interface"); + + return LIBUSB_ERROR_NOT_FOUND; + } + + cInterface = &priv->interfaces[iface]; + + (*(cInterface->interface))->GetPipeProperties (cInterface->interface, pipeRef, &direction, &number, + &transferType, &maxPacketSize, &interval); + + if (0 != (transfer->length % maxPacketSize)) { + /* do not need a zero packet */ + transfer->flags &= ~LIBUSB_TRANSFER_ADD_ZERO_PACKET; + } + + /* submit the request */ + /* timeouts are unavailable on interrupt endpoints */ + if (transferType == kUSBInterrupt) { + if (IS_XFERIN(transfer)) + ret = (*(cInterface->interface))->ReadPipeAsync(cInterface->interface, pipeRef, transfer->buffer, + transfer->length, darwin_async_io_callback, itransfer); + else + ret = (*(cInterface->interface))->WritePipeAsync(cInterface->interface, pipeRef, transfer->buffer, + transfer->length, darwin_async_io_callback, itransfer); + } else { + itransfer->flags |= USBI_TRANSFER_OS_HANDLES_TIMEOUT; + + if (IS_XFERIN(transfer)) + ret = (*(cInterface->interface))->ReadPipeAsyncTO(cInterface->interface, pipeRef, transfer->buffer, + transfer->length, transfer->timeout, transfer->timeout, + darwin_async_io_callback, (void *)itransfer); + else + ret = (*(cInterface->interface))->WritePipeAsyncTO(cInterface->interface, pipeRef, transfer->buffer, + transfer->length, transfer->timeout, transfer->timeout, + darwin_async_io_callback, (void *)itransfer); + } + + if (ret) + usbi_err (TRANSFER_CTX (transfer), "bulk transfer failed (dir = %s): %s (code = 0x%08x)", IS_XFERIN(transfer) ? "In" : "Out", + darwin_error_str(ret), ret); + + return darwin_to_libusb (ret); +} + +static int submit_iso_transfer(struct usbi_transfer *itransfer) { + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct darwin_transfer_priv *tpriv = usbi_transfer_get_os_priv(itransfer); + struct darwin_device_handle_priv *priv = (struct darwin_device_handle_priv *)transfer->dev_handle->os_priv; + + IOReturn kresult; + uint8_t direction, number, interval, pipeRef, iface, transferType; + uint16_t maxPacketSize; + UInt64 frame; + AbsoluteTime atTime; + int i; + + struct darwin_interface *cInterface; + + /* construct an array of IOUSBIsocFrames, reuse the old one if possible */ + if (tpriv->isoc_framelist && tpriv->num_iso_packets != transfer->num_iso_packets) { + free(tpriv->isoc_framelist); + tpriv->isoc_framelist = NULL; + } + + if (!tpriv->isoc_framelist) { + tpriv->num_iso_packets = transfer->num_iso_packets; + tpriv->isoc_framelist = (IOUSBIsocFrame*) calloc (transfer->num_iso_packets, sizeof(IOUSBIsocFrame)); + if (!tpriv->isoc_framelist) + return LIBUSB_ERROR_NO_MEM; + } + + /* copy the frame list from the libusb descriptor (the structures differ only is member order) */ + for (i = 0 ; i < transfer->num_iso_packets ; i++) + tpriv->isoc_framelist[i].frReqCount = transfer->iso_packet_desc[i].length; + + /* determine the interface/endpoint to use */ + if (ep_to_pipeRef (transfer->dev_handle, transfer->endpoint, &pipeRef, &iface) != 0) { + usbi_err (TRANSFER_CTX (transfer), "endpoint not found on any open interface"); + + return LIBUSB_ERROR_NOT_FOUND; + } + + cInterface = &priv->interfaces[iface]; + + /* determine the properties of this endpoint and the speed of the device */ + (*(cInterface->interface))->GetPipeProperties (cInterface->interface, pipeRef, &direction, &number, + &transferType, &maxPacketSize, &interval); + + /* Last but not least we need the bus frame number */ + kresult = (*(cInterface->interface))->GetBusFrameNumber(cInterface->interface, &frame, &atTime); + if (kresult) { + usbi_err (TRANSFER_CTX (transfer), "failed to get bus frame number: %d", kresult); + free(tpriv->isoc_framelist); + tpriv->isoc_framelist = NULL; + + return darwin_to_libusb (kresult); + } + + /* schedule for a frame a little in the future */ + frame += 4; + + if (cInterface->frames[transfer->endpoint] && frame < cInterface->frames[transfer->endpoint]) + frame = cInterface->frames[transfer->endpoint]; + + /* submit the request */ + if (IS_XFERIN(transfer)) + kresult = (*(cInterface->interface))->ReadIsochPipeAsync(cInterface->interface, pipeRef, transfer->buffer, frame, + transfer->num_iso_packets, tpriv->isoc_framelist, darwin_async_io_callback, + itransfer); + else + kresult = (*(cInterface->interface))->WriteIsochPipeAsync(cInterface->interface, pipeRef, transfer->buffer, frame, + transfer->num_iso_packets, tpriv->isoc_framelist, darwin_async_io_callback, + itransfer); + + if (LIBUSB_SPEED_FULL == transfer->dev_handle->dev->speed) + /* Full speed */ + cInterface->frames[transfer->endpoint] = frame + transfer->num_iso_packets * (1 << (interval - 1)); + else + /* High/super speed */ + cInterface->frames[transfer->endpoint] = frame + transfer->num_iso_packets * (1 << (interval - 1)) / 8; + + if (kresult != kIOReturnSuccess) { + usbi_err (TRANSFER_CTX (transfer), "isochronous transfer failed (dir: %s): %s", IS_XFERIN(transfer) ? "In" : "Out", + darwin_error_str(kresult)); + free (tpriv->isoc_framelist); + tpriv->isoc_framelist = NULL; + } + + return darwin_to_libusb (kresult); +} + +static int submit_control_transfer(struct usbi_transfer *itransfer) { + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct libusb_control_setup *setup = (struct libusb_control_setup *) transfer->buffer; + struct darwin_device_priv *dpriv = (struct darwin_device_priv *)transfer->dev_handle->dev->os_priv; + struct darwin_device_handle_priv *priv = (struct darwin_device_handle_priv *)transfer->dev_handle->os_priv; + struct darwin_transfer_priv *tpriv = usbi_transfer_get_os_priv(itransfer); + + IOReturn kresult; + + bzero(&tpriv->req, sizeof(tpriv->req)); + + /* IOUSBDeviceInterface expects the request in cpu endianess */ + tpriv->req.bmRequestType = setup->bmRequestType; + tpriv->req.bRequest = setup->bRequest; + /* these values should be in bus order from libusb_fill_control_setup */ + tpriv->req.wValue = OSSwapLittleToHostInt16 (setup->wValue); + tpriv->req.wIndex = OSSwapLittleToHostInt16 (setup->wIndex); + tpriv->req.wLength = OSSwapLittleToHostInt16 (setup->wLength); + /* data is stored after the libusb control block */ + tpriv->req.pData = transfer->buffer + LIBUSB_CONTROL_SETUP_SIZE; + tpriv->req.completionTimeout = transfer->timeout; + tpriv->req.noDataTimeout = transfer->timeout; + + itransfer->flags |= USBI_TRANSFER_OS_HANDLES_TIMEOUT; + + /* all transfers in libusb-1.0 are async */ + + if (transfer->endpoint) { + struct darwin_interface *cInterface; + uint8_t pipeRef, iface; + + if (ep_to_pipeRef (transfer->dev_handle, transfer->endpoint, &pipeRef, &iface) != 0) { + usbi_err (TRANSFER_CTX (transfer), "endpoint not found on any open interface"); + + return LIBUSB_ERROR_NOT_FOUND; + } + + cInterface = &priv->interfaces[iface]; + + kresult = (*(cInterface->interface))->ControlRequestAsyncTO (cInterface->interface, pipeRef, &(tpriv->req), darwin_async_io_callback, itransfer); + } else + /* control request on endpoint 0 */ + kresult = (*(dpriv->device))->DeviceRequestAsyncTO(dpriv->device, &(tpriv->req), darwin_async_io_callback, itransfer); + + if (kresult != kIOReturnSuccess) + usbi_err (TRANSFER_CTX (transfer), "control request failed: %s", darwin_error_str(kresult)); + + return darwin_to_libusb (kresult); +} + +static int darwin_submit_transfer(struct usbi_transfer *itransfer) { + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + + switch (transfer->type) { + case LIBUSB_TRANSFER_TYPE_CONTROL: + return submit_control_transfer(itransfer); + case LIBUSB_TRANSFER_TYPE_BULK: + case LIBUSB_TRANSFER_TYPE_INTERRUPT: + return submit_bulk_transfer(itransfer); + case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS: + return submit_iso_transfer(itransfer); + default: + usbi_err (TRANSFER_CTX(transfer), "unknown endpoint type %d", transfer->type); + return LIBUSB_ERROR_INVALID_PARAM; + } +} + +static int cancel_control_transfer(struct usbi_transfer *itransfer) { + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct darwin_device_priv *dpriv = (struct darwin_device_priv *)transfer->dev_handle->dev->os_priv; + IOReturn kresult; + + usbi_info (ITRANSFER_CTX (itransfer), "WARNING: aborting all transactions control pipe"); + + if (!dpriv->device) + return LIBUSB_ERROR_NO_DEVICE; + + kresult = (*(dpriv->device))->USBDeviceAbortPipeZero (dpriv->device); + + return darwin_to_libusb (kresult); +} + +static int darwin_abort_transfers (struct usbi_transfer *itransfer) { + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct darwin_device_priv *dpriv = (struct darwin_device_priv *)transfer->dev_handle->dev->os_priv; + struct darwin_device_handle_priv *priv = (struct darwin_device_handle_priv *)transfer->dev_handle->os_priv; + struct darwin_interface *cInterface; + uint8_t pipeRef, iface; + IOReturn kresult; + + if (ep_to_pipeRef (transfer->dev_handle, transfer->endpoint, &pipeRef, &iface) != 0) { + usbi_err (TRANSFER_CTX (transfer), "endpoint not found on any open interface"); + + return LIBUSB_ERROR_NOT_FOUND; + } + + cInterface = &priv->interfaces[iface]; + + if (!dpriv->device) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_info (ITRANSFER_CTX (itransfer), "WARNING: aborting all transactions on interface %d pipe %d", iface, pipeRef); + + /* abort transactions */ + (*(cInterface->interface))->AbortPipe (cInterface->interface, pipeRef); + + usbi_info (ITRANSFER_CTX (itransfer), "calling clear pipe stall to clear the data toggle bit"); + + /* clear the data toggle bit */ +#if (InterfaceVersion < 190) + kresult = (*(cInterface->interface))->ClearPipeStall(cInterface->interface, pipeRef); +#else + /* newer versions of darwin support clearing additional bits on the device's endpoint */ + kresult = (*(cInterface->interface))->ClearPipeStallBothEnds(cInterface->interface, pipeRef); +#endif + + return darwin_to_libusb (kresult); +} + +static int darwin_cancel_transfer(struct usbi_transfer *itransfer) { + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + + switch (transfer->type) { + case LIBUSB_TRANSFER_TYPE_CONTROL: + return cancel_control_transfer(itransfer); + case LIBUSB_TRANSFER_TYPE_BULK: + case LIBUSB_TRANSFER_TYPE_INTERRUPT: + case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS: + return darwin_abort_transfers (itransfer); + default: + usbi_err (TRANSFER_CTX(transfer), "unknown endpoint type %d", transfer->type); + return LIBUSB_ERROR_INVALID_PARAM; + } +} + +static void darwin_clear_transfer_priv (struct usbi_transfer *itransfer) { + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct darwin_transfer_priv *tpriv = usbi_transfer_get_os_priv(itransfer); + + if (transfer->type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS && tpriv->isoc_framelist) { + free (tpriv->isoc_framelist); + tpriv->isoc_framelist = NULL; + } +} + +static void darwin_async_io_callback (void *refcon, IOReturn result, void *arg0) { + struct usbi_transfer *itransfer = (struct usbi_transfer *)refcon; + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct darwin_device_handle_priv *priv = (struct darwin_device_handle_priv *)transfer->dev_handle->os_priv; + struct darwin_msg_async_io_complete message = {.itransfer = itransfer, .result = result, + .size = (UInt32) (uintptr_t) arg0}; + + usbi_info (ITRANSFER_CTX (itransfer), "an async io operation has completed"); + + /* if requested write a zero packet */ + if (kIOReturnSuccess == result && IS_XFEROUT(transfer) && transfer->flags & LIBUSB_TRANSFER_ADD_ZERO_PACKET) { + struct darwin_interface *cInterface; + uint8_t iface, pipeRef; + + (void) ep_to_pipeRef (transfer->dev_handle, transfer->endpoint, &pipeRef, &iface); + cInterface = &priv->interfaces[iface]; + + (*(cInterface->interface))->WritePipe (cInterface->interface, pipeRef, transfer->buffer, 0); + } + + /* send a completion message to the device's file descriptor */ + write (priv->fds[1], &message, sizeof (message)); +} + +static int darwin_transfer_status (struct usbi_transfer *itransfer, kern_return_t result) { + if (itransfer->flags & USBI_TRANSFER_TIMED_OUT) + result = kIOUSBTransactionTimeout; + + switch (result) { + case kIOReturnUnderrun: + case kIOReturnSuccess: + return LIBUSB_TRANSFER_COMPLETED; + case kIOReturnAborted: + return LIBUSB_TRANSFER_CANCELLED; + case kIOUSBPipeStalled: + usbi_warn (ITRANSFER_CTX (itransfer), "transfer error: pipe is stalled"); + return LIBUSB_TRANSFER_STALL; + case kIOReturnOverrun: + usbi_err (ITRANSFER_CTX (itransfer), "transfer error: data overrun"); + return LIBUSB_TRANSFER_OVERFLOW; + case kIOUSBTransactionTimeout: + usbi_err (ITRANSFER_CTX (itransfer), "transfer error: timed out"); + itransfer->flags |= USBI_TRANSFER_TIMED_OUT; + return LIBUSB_TRANSFER_TIMED_OUT; + default: + usbi_err (ITRANSFER_CTX (itransfer), "transfer error: %s (value = 0x%08x)", darwin_error_str (result), result); + return LIBUSB_TRANSFER_ERROR; + } +} + +static void darwin_handle_callback (struct usbi_transfer *itransfer, kern_return_t result, UInt32 io_size) { + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct darwin_transfer_priv *tpriv = usbi_transfer_get_os_priv(itransfer); + int isIsoc = LIBUSB_TRANSFER_TYPE_ISOCHRONOUS == transfer->type; + int isBulk = LIBUSB_TRANSFER_TYPE_BULK == transfer->type; + int isControl = LIBUSB_TRANSFER_TYPE_CONTROL == transfer->type; + int isInterrupt = LIBUSB_TRANSFER_TYPE_INTERRUPT == transfer->type; + int i; + + if (!isIsoc && !isBulk && !isControl && !isInterrupt) { + usbi_err (TRANSFER_CTX(transfer), "unknown endpoint type %d", transfer->type); + return; + } + + usbi_info (ITRANSFER_CTX (itransfer), "handling %s completion with kernel status %d", + isControl ? "control" : isBulk ? "bulk" : isIsoc ? "isoc" : "interrupt", result); + + if (kIOReturnSuccess == result || kIOReturnUnderrun == result) { + if (isIsoc && tpriv->isoc_framelist) { + /* copy isochronous results back */ + + for (i = 0; i < transfer->num_iso_packets ; i++) { + struct libusb_iso_packet_descriptor *lib_desc = &transfer->iso_packet_desc[i]; + lib_desc->status = darwin_to_libusb (tpriv->isoc_framelist[i].frStatus); + lib_desc->actual_length = tpriv->isoc_framelist[i].frActCount; + } + } else if (!isIsoc) + itransfer->transferred += io_size; + } + + /* it is ok to handle cancelled transfers without calling usbi_handle_transfer_cancellation (we catch timeout transfers) */ + usbi_handle_transfer_completion (itransfer, darwin_transfer_status (itransfer, result)); +} + +static int op_handle_events(struct libusb_context *ctx, struct pollfd *fds, POLL_NFDS_TYPE nfds, int num_ready) { + struct darwin_msg_async_io_complete message; + POLL_NFDS_TYPE i = 0; + ssize_t ret; + + usbi_mutex_lock(&ctx->open_devs_lock); + + for (i = 0; i < nfds && num_ready > 0; i++) { + struct pollfd *pollfd = &fds[i]; + + usbi_info (ctx, "checking fd %i with revents = %x", pollfd->fd, pollfd->revents); + + if (!pollfd->revents) + continue; + + num_ready--; + + if (pollfd->revents & POLLERR) { + /* this probably will never happen so ignore the error an move on. */ + continue; + } + + /* there is only one type of message */ + ret = read (pollfd->fd, &message, sizeof (message)); + if (ret < (ssize_t) sizeof (message)) { + usbi_dbg ("WARNING: short read on async io completion pipe\n"); + continue; + } + + darwin_handle_callback (message.itransfer, message.result, message.size); + } + + usbi_mutex_unlock(&ctx->open_devs_lock); + + return 0; +} + +static int darwin_clock_gettime(int clk_id, struct timespec *tp) { + mach_timespec_t sys_time; + clock_serv_t clock_ref; + + switch (clk_id) { + case USBI_CLOCK_REALTIME: + /* CLOCK_REALTIME represents time since the epoch */ + clock_ref = clock_realtime; + break; + case USBI_CLOCK_MONOTONIC: + /* use system boot time as reference for the monotonic clock */ + clock_ref = clock_monotonic; + break; + default: + return LIBUSB_ERROR_INVALID_PARAM; + } + + clock_get_time (clock_ref, &sys_time); + + tp->tv_sec = sys_time.tv_sec; + tp->tv_nsec = sys_time.tv_nsec; + + return 0; +} + +const struct usbi_os_backend darwin_backend = { + .name = "Darwin", + .init = darwin_init, + .exit = darwin_exit, + .get_device_list = NULL, /* not needed */ + .get_device_descriptor = darwin_get_device_descriptor, + .get_active_config_descriptor = darwin_get_active_config_descriptor, + .get_config_descriptor = darwin_get_config_descriptor, + + .open = darwin_open, + .close = darwin_close, + .get_configuration = darwin_get_configuration, + .set_configuration = darwin_set_configuration, + .claim_interface = darwin_claim_interface, + .release_interface = darwin_release_interface, + + .set_interface_altsetting = darwin_set_interface_altsetting, + .clear_halt = darwin_clear_halt, + .reset_device = darwin_reset_device, + + .kernel_driver_active = darwin_kernel_driver_active, + .detach_kernel_driver = darwin_detach_kernel_driver, + .attach_kernel_driver = darwin_attach_kernel_driver, + + .destroy_device = darwin_destroy_device, + + .submit_transfer = darwin_submit_transfer, + .cancel_transfer = darwin_cancel_transfer, + .clear_transfer_priv = darwin_clear_transfer_priv, + + .handle_events = op_handle_events, + + .clock_gettime = darwin_clock_gettime, + + .device_priv_size = sizeof(struct darwin_device_priv), + .device_handle_priv_size = sizeof(struct darwin_device_handle_priv), + .transfer_priv_size = sizeof(struct darwin_transfer_priv), + .add_iso_packet_size = 0, +}; + diff --git a/compat/libusb-1.0/libusb/os/darwin_usb.h b/compat/libusb-1.0/libusb/os/darwin_usb.h new file mode 100644 index 0000000..368dec2 --- /dev/null +++ b/compat/libusb-1.0/libusb/os/darwin_usb.h @@ -0,0 +1,175 @@ +/* + * darwin backend for libusb 1.0 + * Copyright (C) 2008-2013 Nathan Hjelm + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined(LIBUSB_DARWIN_H) +#define LIBUSB_DARWIN_H + +#include "libusbi.h" + +#include +#include +#include +#include + +/* IOUSBInterfaceInferface */ +#if defined (kIOUSBInterfaceInterfaceID300) + +#define usb_interface_t IOUSBInterfaceInterface300 +#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID300 +#define InterfaceVersion 300 + +#elif defined (kIOUSBInterfaceInterfaceID245) + +#define usb_interface_t IOUSBInterfaceInterface245 +#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID245 +#define InterfaceVersion 245 + +#elif defined (kIOUSBInterfaceInterfaceID220) + +#define usb_interface_t IOUSBInterfaceInterface220 +#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID220 +#define InterfaceVersion 220 + +#elif defined (kIOUSBInterfaceInterfaceID197) + +#define usb_interface_t IOUSBInterfaceInterface197 +#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID197 +#define InterfaceVersion 197 + +#elif defined (kIOUSBInterfaceInterfaceID190) + +#define usb_interface_t IOUSBInterfaceInterface190 +#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID190 +#define InterfaceVersion 190 + +#elif defined (kIOUSBInterfaceInterfaceID182) + +#define usb_interface_t IOUSBInterfaceInterface182 +#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID182 +#define InterfaceVersion 182 + +#else + +#error "IOUSBFamily is too old. Please upgrade your OS" + +#endif + +/* IOUSBDeviceInterface */ +#if defined (kIOUSBDeviceInterfaceID500) + +#define usb_device_t IOUSBDeviceInterface500 +#define DeviceInterfaceID kIOUSBDeviceInterfaceID500 +#define DeviceVersion 500 + +#elif defined (kIOUSBDeviceInterfaceID320) + +#define usb_device_t IOUSBDeviceInterface320 +#define DeviceInterfaceID kIOUSBDeviceInterfaceID320 +#define DeviceVersion 320 + +#elif defined (kIOUSBDeviceInterfaceID300) + +#define usb_device_t IOUSBDeviceInterface300 +#define DeviceInterfaceID kIOUSBDeviceInterfaceID300 +#define DeviceVersion 300 + +#elif defined (kIOUSBDeviceInterfaceID245) + +#define usb_device_t IOUSBDeviceInterface245 +#define DeviceInterfaceID kIOUSBDeviceInterfaceID245 +#define DeviceVersion 245 + +#elif defined (kIOUSBDeviceInterfaceID197) + +#define usb_device_t IOUSBDeviceInterface197 +#define DeviceInterfaceID kIOUSBDeviceInterfaceID197 +#define DeviceVersion 197 + +#elif defined (kIOUSBDeviceInterfaceID187) + +#define usb_device_t IOUSBDeviceInterface187 +#define DeviceInterfaceID kIOUSBDeviceInterfaceID187 +#define DeviceVersion 187 + +#elif defined (kIOUSBDeviceInterfaceID182) + +#define usb_device_t IOUSBDeviceInterface182 +#define DeviceInterfaceID kIOUSBDeviceInterfaceID182 +#define DeviceVersion 182 + +#else + +#error "IOUSBFamily is too old. Please upgrade your OS" + +#endif + +#if !defined(IO_OBJECT_NULL) +#define IO_OBJECT_NULL ((io_object_t) 0) +#endif + +typedef IOCFPlugInInterface *io_cf_plugin_ref_t; +typedef IONotificationPortRef io_notification_port_t; + +/* private structures */ +struct darwin_device_priv { + IOUSBDeviceDescriptor dev_descriptor; + UInt32 location; + char sys_path[21]; + usb_device_t **device; + int open_count; + UInt8 first_config, active_config; +}; + +struct darwin_device_handle_priv { + int is_open; + CFRunLoopSourceRef cfSource; + int fds[2]; + + struct darwin_interface { + usb_interface_t **interface; + uint8_t num_endpoints; + CFRunLoopSourceRef cfSource; + uint64_t frames[256]; + uint8_t endpoint_addrs[USB_MAXENDPOINTS]; + } interfaces[USB_MAXINTERFACES]; +}; + +struct darwin_transfer_priv { + /* Isoc */ + IOUSBIsocFrame *isoc_framelist; + int num_iso_packets; + + /* Control */ +#if !defined (LIBUSB_NO_TIMEOUT_DEVICE) + IOUSBDevRequestTO req; +#else + IOUSBDevRequest req; +#endif + + /* Bulk */ +}; + +/* structure for signaling io completion */ +struct darwin_msg_async_io_complete { + struct usbi_transfer *itransfer; + IOReturn result; + UInt32 size; +}; + +#endif diff --git a/compat/libusb-1.0/libusb/os/linux_netlink.c b/compat/libusb-1.0/libusb/os/linux_netlink.c new file mode 100644 index 0000000..20c652f --- /dev/null +++ b/compat/libusb-1.0/libusb/os/linux_netlink.c @@ -0,0 +1,231 @@ +/* -*- Mode: C; c-basic-offset:8 ; indent-tabs-mode:t -*- */ +/* + * Linux usbfs backend for libusb + * Copyright (C) 2007-2009 Daniel Drake + * Copyright (c) 2001 Johannes Erdfelt + * Copyright (c) 2013 Nathan Hjelm + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libusb.h" +#include "libusbi.h" +#include "linux_usbfs.h" + +#include +#include + +#define KERNEL 1 + +static int linux_netlink_socket = -1; +static pthread_t libusb_linux_event_thread; + +static void *linux_netlink_event_thread_main(void *arg); + +struct sockaddr_nl snl = { .nl_family=AF_NETLINK, .nl_groups=KERNEL }; + +int linux_netlink_start_event_monitor(void) +{ + int ret; + + snl.nl_groups = KERNEL; + + linux_netlink_socket = socket(PF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_KOBJECT_UEVENT); + if (-1 == linux_netlink_socket) { + return LIBUSB_ERROR_OTHER; + } + + ret = bind(linux_netlink_socket, (struct sockaddr *) &snl, sizeof(snl)); + if (0 != ret) { + return LIBUSB_ERROR_OTHER; + } + + /* TODO -- add authentication */ + /* setsockopt(linux_netlink_socket, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)); */ + + ret = pthread_create(&libusb_linux_event_thread, NULL, linux_netlink_event_thread_main, NULL); + if (0 != ret) { + return LIBUSB_ERROR_OTHER; + } + + return LIBUSB_SUCCESS; +} + +int linux_netlink_stop_event_monitor(void) +{ + int r; + + if (-1 == linux_netlink_socket) { + /* already closed. nothing to do */ + return LIBUSB_SUCCESS; + } + + r = close(linux_netlink_socket); + if (0 > r) { + usbi_err(NULL, "error closing netlink socket. %s", strerror(errno)); + return LIBUSB_ERROR_OTHER; + } + + pthread_cancel(libusb_linux_event_thread); + + linux_netlink_socket = -1; + + return LIBUSB_SUCCESS; +} + +static const char *netlink_message_parse (const char *buffer, size_t len, const char *key) +{ + size_t keylen = strlen(key); + size_t offset; + + for (offset = 0 ; offset < len && '\0' != buffer[offset] ; offset += strlen(buffer + offset) + 1) { + if (0 == strncmp(buffer + offset, key, keylen) && + '=' == buffer[offset + keylen]) { + return buffer + offset + keylen + 1; + } + } + + return NULL; +} + +/* parse parts of netlink message common to both libudev and the kernel */ +static int linux_netlink_parse(char *buffer, size_t len, int *detached, const char **sys_name, + uint8_t *busnum, uint8_t *devaddr) { + const char *tmp; + int i; + + errno = 0; + + *sys_name = NULL; + *detached = 0; + *busnum = 0; + *devaddr = 0; + + tmp = netlink_message_parse((const char *) buffer, len, "ACTION"); + if (0 == strcmp(tmp, "remove")) { + *detached = 1; + } else if (0 != strcmp(tmp, "add")) { + usbi_dbg("unknown device action"); + return -1; + } + + /* check that this is a usb message */ + tmp = netlink_message_parse(buffer, len, "SUBSYSTEM"); + if (NULL == tmp || 0 != strcmp(tmp, "usb")) { + /* not usb. ignore */ + return -1; + } + + tmp = netlink_message_parse(buffer, len, "BUSNUM"); + if (NULL == tmp) { + /* no bus number (likely a usb interface). ignore*/ + return -1; + } + + *busnum = (uint8_t)(strtoul(tmp, NULL, 10) & 0xff); + if (errno) { + errno = 0; + return -1; + } + + tmp = netlink_message_parse(buffer, len, "DEVNUM"); + if (NULL == tmp) { + return -1; + } + + *devaddr = (uint8_t)(strtoul(tmp, NULL, 10) & 0xff); + if (errno) { + errno = 0; + return -1; + } + + tmp = netlink_message_parse(buffer, len, "DEVPATH"); + if (NULL == tmp) { + return -1; + } + + for (i = strlen(tmp) - 1 ; i ; --i) { + if ('/' ==tmp[i]) { + *sys_name = tmp + i + 1; + break; + } + } + + /* found a usb device */ + return 0; +} + +static void *linux_netlink_event_thread_main(void *arg) +{ + struct pollfd fds = {.fd = linux_netlink_socket, + .events = POLLIN}; + unsigned char buffer[1024]; + struct iovec iov = {.iov_base = buffer, .iov_len = sizeof(buffer)}; + struct msghdr meh = { .msg_iov=&iov, .msg_iovlen=1, + .msg_name=&snl, .msg_namelen=sizeof(snl) }; + uint8_t busnum, devaddr; + int detached, r; + size_t len; + + /* silence compiler warning */ + (void) arg; + + while (1 == poll(&fds, 1, -1)) { + const char *sys_name = NULL; + + if (POLLIN != fds.revents) { + break; + } + + /* read netlink message */ + memset(buffer, 0, sizeof(buffer)); + len = recvmsg(linux_netlink_socket, &meh, 0); + if (len < 32) { + usbi_dbg("error recieving message from netlink"); + continue; + } + + /* TODO -- authenticate this message is from the kernel or udevd */ + + r = linux_netlink_parse(buffer, len, &detached, &sys_name, + &busnum, &devaddr); + if (r) + continue; + + usbi_dbg("netlink hotplug found device busnum: %hhu, devaddr: %hhu, sys_name: %s, removed: %s", + busnum, devaddr, sys_name, detached ? "yes" : "no"); + + /* signal device is available (or not) to all contexts */ + if (detached) + linux_hotplug_disconnected(busnum, devaddr, sys_name); + else + linux_hotplug_enumerate(busnum, devaddr, sys_name); + } + + return NULL; +} diff --git a/compat/libusb-1.0/libusb/os/linux_udev.c b/compat/libusb-1.0/libusb/os/linux_udev.c new file mode 100644 index 0000000..abe1e66 --- /dev/null +++ b/compat/libusb-1.0/libusb/os/linux_udev.c @@ -0,0 +1,242 @@ +/* -*- Mode: C; c-basic-offset:8 ; indent-tabs-mode:t -*- */ +/* + * Linux usbfs backend for libusb + * Copyright (C) 2007-2009 Daniel Drake + * Copyright (c) 2001 Johannes Erdfelt + * Copyright (c) 2012-2013 Nathan Hjelm + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libusb.h" +#include "libusbi.h" +#include "linux_usbfs.h" + +/* udev context */ +static struct udev *udev_ctx = NULL; +static int udev_monitor_fd = -1; +static struct udev_monitor *udev_monitor = NULL; +static pthread_t linux_event_thread; + +static void udev_hotplug_event(void); +static void *linux_udev_event_thread_main(void *arg); + +int linux_udev_start_event_monitor(void) +{ + int r; + + if (NULL == udev_ctx) { + udev_ctx = udev_new(); + if (!udev_ctx) { + return LIBUSB_ERROR_OTHER; + } + } + + udev_monitor = udev_monitor_new_from_netlink(udev_ctx, "udev"); + if (!udev_monitor) { + usbi_err(NULL, "could not initialize udev monitor"); + return LIBUSB_ERROR_OTHER; + } + + r = udev_monitor_filter_add_match_subsystem_devtype(udev_monitor, "usb", 0); + if (r) { + usbi_err(NULL, "could not initialize udev monitor filter for \"usb\" subsystem"); + return LIBUSB_ERROR_OTHER; + } + + if (udev_monitor_enable_receiving(udev_monitor)) { + usbi_err(NULL, "failed to enable the udev monitor"); + return LIBUSB_ERROR_OTHER; + } + + udev_monitor_fd = udev_monitor_get_fd(udev_monitor); + + pthread_create(&linux_event_thread, NULL, linux_udev_event_thread_main, NULL); + + return LIBUSB_SUCCESS; +} + +int linux_udev_stop_event_monitor(void) +{ + if (-1 == udev_monitor_fd) { + /* this should never happen */ + return LIBUSB_ERROR_OTHER; + } + + /* Cancel the event thread. This is the only way to garauntee the thread + exits since closing the monitor fd won't necessarily cause poll + to return. */ + pthread_cancel(linux_event_thread); + + /* Release the udev monitor */ + udev_monitor_unref(udev_monitor); + udev_monitor = NULL; + udev_monitor_fd = -1; + + /* Clean up the udev context */ + udev_unref(udev_ctx); + udev_ctx = NULL; + + return LIBUSB_SUCCESS; +} + +static void *linux_udev_event_thread_main(void __attribute__((unused)) *arg) +{ + struct pollfd fds = {.fd = udev_monitor_fd, + .events = POLLIN}; + + usbi_dbg("udev event thread entering."); + + while (1 == poll(&fds, 1, -1)) { + if (NULL == udev_monitor || POLLIN != fds.revents) { + break; + } + + udev_hotplug_event(); + } + + usbi_dbg("udev event thread exiting"); + + return NULL; +} + +static int udev_device_info(struct libusb_context *ctx, int detached, + struct udev_device *udev_dev, uint8_t *busnum, + uint8_t *devaddr, const char **sys_name) { + const char *dev_node; + + dev_node = udev_device_get_devnode(udev_dev); + if (!dev_node) { + return LIBUSB_ERROR_OTHER; + } + + *sys_name = udev_device_get_sysname(udev_dev); + if (!*sys_name) { + return LIBUSB_ERROR_OTHER; + } + + return linux_get_device_address(ctx, detached, busnum, devaddr, + dev_node, *sys_name); +} + +static void udev_hotplug_event(void) +{ + struct udev_device* udev_dev; + const char* udev_action; + const char* sys_name = NULL; + uint8_t busnum = 0, devaddr = 0; + int detached; + int r; + + if (NULL == udev_monitor) { + return; + } + + do { + udev_dev = udev_monitor_receive_device(udev_monitor); + if (!udev_dev) { + usbi_err(NULL, "failed to read data from udev monitor socket."); + return; + } + + udev_action = udev_device_get_action(udev_dev); + if (!udev_action) { + break; + } + + detached = !strncmp(udev_action, "remove", 6); + + r = udev_device_info(NULL, detached, udev_dev, &busnum, &devaddr, &sys_name); + if (LIBUSB_SUCCESS != r) { + break; + } + + usbi_dbg("udev hotplug event. action: %s.", udev_action); + + if (strncmp(udev_action, "add", 3) == 0) { + linux_hotplug_enumerate(busnum, devaddr, sys_name); + } else if (detached) { + linux_hotplug_disconnected(busnum, devaddr, sys_name); + } else { + usbi_err(NULL, "ignoring udev action %s", udev_action); + } + } while (0); + + udev_device_unref(udev_dev); +} + +int linux_udev_scan_devices(struct libusb_context *ctx) +{ + struct udev_enumerate *enumerator; + struct udev_list_entry *devices, *entry; + struct udev_device *udev_dev; + const char *sys_name; + int r; + + if (NULL == udev_ctx) { + udev_ctx = udev_new(); + if (!udev_ctx) { + return LIBUSB_ERROR_OTHER; + } + } + + enumerator = udev_enumerate_new(udev_ctx); + if (NULL == enumerator) { + usbi_err(ctx, "error creating udev enumerator"); + return LIBUSB_ERROR_OTHER; + } + + udev_enumerate_add_match_subsystem(enumerator, "usb"); + udev_enumerate_scan_devices(enumerator); + devices = udev_enumerate_get_list_entry(enumerator); + + udev_list_entry_foreach(entry, devices) { + const char *path = udev_list_entry_get_name(entry); + uint8_t busnum = 0, devaddr = 0; + + udev_dev = udev_device_new_from_syspath(udev_ctx, path); + + r = udev_device_info(ctx, 0, udev_dev, &busnum, &devaddr, &sys_name); + if (r) { + udev_device_unref(udev_dev); + continue; + } + + linux_enumerate_device(ctx, busnum, devaddr, sys_name); + udev_device_unref(udev_dev); + } + + udev_enumerate_unref(enumerator); + + return LIBUSB_SUCCESS; +} + diff --git a/compat/libusb-1.0/libusb/os/linux_usbfs.c b/compat/libusb-1.0/libusb/os/linux_usbfs.c new file mode 100644 index 0000000..bd847c2 --- /dev/null +++ b/compat/libusb-1.0/libusb/os/linux_usbfs.c @@ -0,0 +1,2613 @@ +/* -*- Mode: C; c-basic-offset:8 ; indent-tabs-mode:t -*- */ +/* + * Linux usbfs backend for libusb + * Copyright (C) 2007-2009 Daniel Drake + * Copyright (c) 2001 Johannes Erdfelt + * Copyright (c) 2013 Nathan Hjelm + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libusb.h" +#include "libusbi.h" +#include "linux_usbfs.h" + +/* sysfs vs usbfs: + * opening a usbfs node causes the device to be resumed, so we attempt to + * avoid this during enumeration. + * + * sysfs allows us to read the kernel's in-memory copies of device descriptors + * and so forth, avoiding the need to open the device: + * - The binary "descriptors" file was added in 2.6.23. + * - The "busnum" file was added in 2.6.22 + * - The "devnum" file has been present since pre-2.6.18 + * - the "bConfigurationValue" file has been present since pre-2.6.18 + * + * If we have bConfigurationValue, busnum, and devnum, then we can determine + * the active configuration without having to open the usbfs node in RDWR mode. + * We assume this is the case if we see the busnum file (indicates 2.6.22+). + * The busnum file is important as that is the only way we can relate sysfs + * devices to usbfs nodes. + * + * If we also have descriptors, we can obtain the device descriptor and active + * configuration without touching usbfs at all. + * + * The descriptors file originally only contained the active configuration + * descriptor alongside the device descriptor, but all configurations are + * included as of Linux 2.6.26. + */ + +/* endianness for multi-byte fields: + * + * Descriptors exposed by usbfs have the multi-byte fields in the device + * descriptor as host endian. Multi-byte fields in the other descriptors are + * bus-endian. The kernel documentation says otherwise, but it is wrong. + */ + +const char *usbfs_path = NULL; + +/* use usbdev*.* device names in /dev instead of the usbfs bus directories */ +static int usbdev_names = 0; + +/* Linux 2.6.32 adds support for a bulk continuation URB flag. this basically + * allows us to mark URBs as being part of a specific logical transfer when + * we submit them to the kernel. then, on any error except a cancellation, all + * URBs within that transfer will be cancelled and no more URBs will be + * accepted for the transfer, meaning that no more data can creep in. + * + * The BULK_CONTINUATION flag must be set on all URBs within a bulk transfer + * (in either direction) except the first. + * For IN transfers, we must also set SHORT_NOT_OK on all URBs except the + * last; it means that the kernel should treat a short reply as an error. + * For OUT transfers, SHORT_NOT_OK must not be set. it isn't needed (OUT + * transfers can't be short unless there's already some sort of error), and + * setting this flag is disallowed (a kernel with USB debugging enabled will + * reject such URBs). + */ +static int supports_flag_bulk_continuation = -1; + +/* Linux 2.6.31 fixes support for the zero length packet URB flag. This + * allows us to mark URBs that should be followed by a zero length data + * packet, which can be required by device- or class-specific protocols. + */ +static int supports_flag_zero_packet = -1; + +/* clock ID for monotonic clock, as not all clock sources are available on all + * systems. appropriate choice made at initialization time. */ +static clockid_t monotonic_clkid = -1; + +/* do we have a busnum to relate devices? this also implies that we can read + * the active configuration through bConfigurationValue */ +static int sysfs_can_relate_devices = 0; + +/* do we have a descriptors file? */ +static int sysfs_has_descriptors = 0; + +/* how many times have we initted (and not exited) ? */ +static volatile int init_count = 0; + +/* lock for init_count */ +static pthread_mutex_t hotplug_lock = PTHREAD_MUTEX_INITIALIZER; + +static int linux_start_event_monitor(void); +static int linux_stop_event_monitor(void); +static int linux_scan_devices(struct libusb_context *ctx); + +#if !defined(USE_UDEV) +static int linux_default_scan_devices (struct libusb_context *ctx); +#endif + +struct linux_device_priv { + char *sysfs_dir; + unsigned char *dev_descriptor; + unsigned char *config_descriptor; +}; + +struct linux_device_handle_priv { + int fd; + uint32_t caps; +}; + +enum reap_action { + NORMAL = 0, + /* submission failed after the first URB, so await cancellation/completion + * of all the others */ + SUBMIT_FAILED, + + /* cancelled by user or timeout */ + CANCELLED, + + /* completed multi-URB transfer in non-final URB */ + COMPLETED_EARLY, + + /* one or more urbs encountered a low-level error */ + ERROR, +}; + +struct linux_transfer_priv { + union { + struct usbfs_urb *urbs; + struct usbfs_urb **iso_urbs; + }; + + enum reap_action reap_action; + int num_urbs; + int num_retired; + enum libusb_transfer_status reap_status; + + /* next iso packet in user-supplied transfer to be populated */ + int iso_packet_offset; +}; + +static void _get_usbfs_path(struct libusb_device *dev, char *path) +{ + if (usbdev_names) + snprintf(path, PATH_MAX, "%s/usbdev%d.%d", + usbfs_path, dev->bus_number, dev->device_address); + else + snprintf(path, PATH_MAX, "%s/%03d/%03d", + usbfs_path, dev->bus_number, dev->device_address); +} + +static struct linux_device_priv *_device_priv(struct libusb_device *dev) +{ + return (struct linux_device_priv *) dev->os_priv; +} + +static struct linux_device_handle_priv *_device_handle_priv( + struct libusb_device_handle *handle) +{ + return (struct linux_device_handle_priv *) handle->os_priv; +} + +/* check dirent for a /dev/usbdev%d.%d name + * optionally return bus/device on success */ +static int _is_usbdev_entry(struct dirent *entry, int *bus_p, int *dev_p) +{ + int busnum, devnum; + + if (sscanf(entry->d_name, "usbdev%d.%d", &busnum, &devnum) != 2) + return 0; + + usbi_dbg("found: %s", entry->d_name); + if (bus_p != NULL) + *bus_p = busnum; + if (dev_p != NULL) + *dev_p = devnum; + return 1; +} + +static int check_usb_vfs(const char *dirname) +{ + DIR *dir; + struct dirent *entry; + int found = 0; + + dir = opendir(dirname); + if (!dir) + return 0; + + while ((entry = readdir(dir)) != NULL) { + if (entry->d_name[0] == '.') + continue; + + /* We assume if we find any files that it must be the right place */ + found = 1; + break; + } + + closedir(dir); + return found; +} + +static const char *find_usbfs_path(void) +{ + const char *path = "/dev/bus/usb"; + const char *ret = NULL; + + if (check_usb_vfs(path)) { + ret = path; + } else { + path = "/proc/bus/usb"; + if (check_usb_vfs(path)) + ret = path; + } + + /* look for /dev/usbdev*.* if the normal places fail */ + if (ret == NULL) { + struct dirent *entry; + DIR *dir; + + path = "/dev"; + dir = opendir(path); + if (dir != NULL) { + while ((entry = readdir(dir)) != NULL) { + if (_is_usbdev_entry(entry, NULL, NULL)) { + /* found one; that's enough */ + ret = path; + usbdev_names = 1; + break; + } + } + closedir(dir); + } + } + + if (ret != NULL) + usbi_dbg("found usbfs at %s", ret); + + return ret; +} + +/* the monotonic clock is not usable on all systems (e.g. embedded ones often + * seem to lack it). fall back to REALTIME if we have to. */ +static clockid_t find_monotonic_clock(void) +{ +#ifdef CLOCK_MONOTONIC + struct timespec ts; + int r; + + /* Linux 2.6.28 adds CLOCK_MONOTONIC_RAW but we don't use it + * because it's not available through timerfd */ + r = clock_gettime(CLOCK_MONOTONIC, &ts); + if (r == 0) + return CLOCK_MONOTONIC; + usbi_dbg("monotonic clock doesn't work, errno %d", errno); +#endif + + return CLOCK_REALTIME; +} + +static int kernel_version_ge(int major, int minor, int sublevel) +{ + struct utsname uts; + int atoms, kmajor, kminor, ksublevel; + + if (uname(&uts) < 0) + return -1; + atoms = sscanf(uts.release, "%d.%d.%d", &kmajor, &kminor, &ksublevel); + if (atoms < 1) + return -1; + + if (kmajor > major) + return 1; + if (kmajor < major) + return 0; + + /* kmajor == major */ + if (atoms < 2) + return 0 == minor && 0 == sublevel; + if (kminor > minor) + return 1; + if (kminor < minor) + return 0; + + /* kminor == minor */ + if (atoms < 3) + return 0 == sublevel; + + return ksublevel >= sublevel; +} + +/* Return 1 if filename exists inside dirname in sysfs. + SYSFS_DEVICE_PATH is assumed to be the beginning of the path. */ +static int sysfs_has_file(const char *dirname, const char *filename) +{ + struct stat statbuf; + char path[PATH_MAX]; + int r; + + snprintf(path, PATH_MAX, "%s/%s/%s", SYSFS_DEVICE_PATH, dirname, filename); + r = stat(path, &statbuf); + if (r == 0 && S_ISREG(statbuf.st_mode)) + return 1; + + return 0; +} + +static int op_init(struct libusb_context *ctx) +{ + struct stat statbuf; + int r; + + usbfs_path = find_usbfs_path(); + if (!usbfs_path) { + usbi_err(ctx, "could not find usbfs"); + return LIBUSB_ERROR_OTHER; + } + + if (monotonic_clkid == -1) + monotonic_clkid = find_monotonic_clock(); + + if (supports_flag_bulk_continuation == -1) { + /* bulk continuation URB flag available from Linux 2.6.32 */ + supports_flag_bulk_continuation = kernel_version_ge(2,6,32); + if (supports_flag_bulk_continuation == -1) { + usbi_err(ctx, "error checking for bulk continuation support"); + return LIBUSB_ERROR_OTHER; + } + } + + if (supports_flag_bulk_continuation) + usbi_dbg("bulk continuation flag supported"); + + if (-1 == supports_flag_zero_packet) { + /* zero length packet URB flag fixed since Linux 2.6.31 */ + supports_flag_zero_packet = kernel_version_ge(2,6,31); + if (-1 == supports_flag_zero_packet) { + usbi_err(ctx, "error checking for zero length packet support"); + return LIBUSB_ERROR_OTHER; + } + } + + if (supports_flag_zero_packet) + usbi_dbg("zero length packet flag supported"); + + r = stat(SYSFS_DEVICE_PATH, &statbuf); + if (r == 0 && S_ISDIR(statbuf.st_mode)) { + DIR *devices = opendir(SYSFS_DEVICE_PATH); + struct dirent *entry; + + usbi_dbg("found usb devices in sysfs"); + + if (!devices) { + usbi_err(ctx, "opendir devices failed errno=%d", errno); + return LIBUSB_ERROR_IO; + } + + /* Make sure sysfs supports all the required files. If it + * does not, then usbfs will be used instead. Determine + * this by looping through the directories in + * SYSFS_DEVICE_PATH. With the assumption that there will + * always be subdirectories of the name usbN (usb1, usb2, + * etc) representing the root hubs, check the usbN + * subdirectories to see if they have all the needed files. + * This algorithm uses the usbN subdirectories (root hubs) + * because a device disconnection will cause a race + * condition regarding which files are available, sometimes + * causing an incorrect result. The root hubs are used + * because it is assumed that they will always be present. + * See the "sysfs vs usbfs" comment at the top of this file + * for more details. */ + while ((entry = readdir(devices))) { + int has_busnum=0, has_devnum=0, has_descriptors=0; + int has_configuration_value=0; + + /* Only check the usbN directories. */ + if (strncmp(entry->d_name, "usb", 3) != 0) + continue; + + /* Check for the files libusb needs from sysfs. */ + has_busnum = sysfs_has_file(entry->d_name, "busnum"); + has_devnum = sysfs_has_file(entry->d_name, "devnum"); + has_descriptors = sysfs_has_file(entry->d_name, "descriptors"); + has_configuration_value = sysfs_has_file(entry->d_name, "bConfigurationValue"); + + if (has_busnum && has_devnum && has_configuration_value) + sysfs_can_relate_devices = 1; + if (has_descriptors) + sysfs_has_descriptors = 1; + + /* Only need to check until we've found ONE device which + has all the attributes. */ + if (sysfs_has_descriptors && sysfs_can_relate_devices) + break; + } + closedir(devices); + + /* Only use sysfs descriptors if the rest of + sysfs will work for libusb. */ + if (!sysfs_can_relate_devices) + sysfs_has_descriptors = 0; + } else { + usbi_dbg("sysfs usb info not available"); + sysfs_has_descriptors = 0; + sysfs_can_relate_devices = 0; + } + + pthread_mutex_lock(&hotplug_lock); + if (!init_count++) { + /* start up hotplug event handler */ + r = linux_start_event_monitor(); + if (LIBUSB_SUCCESS != r) { + usbi_err(ctx, "error starting hotplug event monitor"); + return r; + } + } + pthread_mutex_unlock(&hotplug_lock); + + r = linux_scan_devices(ctx); + if (LIBUSB_SUCCESS != r) { + return r; + } + + return r; +} + +static void op_exit(void) +{ + if (!init_count) { + /* should not happen */ + return; + } + + pthread_mutex_lock(&hotplug_lock); + if (!--init_count) { + /* tear down event handler */ + (void)linux_stop_event_monitor(); + } + pthread_mutex_unlock(&hotplug_lock); +} + +static int linux_start_event_monitor(void) +{ +#if defined(USE_UDEV) + return linux_udev_start_event_monitor(); +#else + return linux_netlink_start_event_monitor(); +#endif +} + +static int linux_stop_event_monitor(void) +{ +#if defined(USE_UDEV) + return linux_udev_stop_event_monitor(); +#else + return linux_netlink_stop_event_monitor(); +#endif +} + +static int linux_scan_devices(struct libusb_context *ctx) +{ +#if defined(USE_UDEV) + return linux_udev_scan_devices(ctx); +#else + return linux_default_scan_devices(ctx); +#endif +} + +static int usbfs_get_device_descriptor(struct libusb_device *dev, + unsigned char *buffer) +{ + struct linux_device_priv *priv = _device_priv(dev); + + /* return cached copy */ + memcpy(buffer, priv->dev_descriptor, DEVICE_DESC_LENGTH); + return 0; +} + +static int _open_sysfs_attr(struct libusb_device *dev, const char *attr) +{ + struct linux_device_priv *priv = _device_priv(dev); + char filename[PATH_MAX]; + int fd; + + snprintf(filename, PATH_MAX, "%s/%s/%s", + SYSFS_DEVICE_PATH, priv->sysfs_dir, attr); + fd = open(filename, O_RDONLY); + if (fd < 0) { + usbi_err(DEVICE_CTX(dev), + "open %s failed ret=%d errno=%d", filename, fd, errno); + return LIBUSB_ERROR_IO; + } + + return fd; +} + +/* Note only suitable for attributes which always read >= 0, < 0 is error */ +static int __read_sysfs_attr(struct libusb_context *ctx, + const char *devname, const char *attr) +{ + char filename[PATH_MAX]; + FILE *f; + int r, value; + + snprintf(filename, PATH_MAX, "%s/%s/%s", SYSFS_DEVICE_PATH, + devname, attr); + f = fopen(filename, "r"); + if (f == NULL) { + if (errno == ENOENT) { + /* File doesn't exist. Assume the device has been + disconnected (see trac ticket #70). */ + return LIBUSB_ERROR_NO_DEVICE; + } + usbi_err(ctx, "open %s failed errno=%d", filename, errno); + return LIBUSB_ERROR_IO; + } + + r = fscanf(f, "%d", &value); + fclose(f); + if (r != 1) { + usbi_err(ctx, "fscanf %s returned %d, errno=%d", attr, r, errno); + return LIBUSB_ERROR_NO_DEVICE; /* For unplug race (trac #70) */ + } + if (value < 0) { + usbi_err(ctx, "%s contains a negative value", filename); + return LIBUSB_ERROR_IO; + } + + return value; +} + +static int sysfs_get_device_descriptor(struct libusb_device *dev, + unsigned char *buffer) +{ + int fd; + ssize_t r; + + /* sysfs provides access to an in-memory copy of the device descriptor, + * so we use that rather than keeping our own copy */ + + fd = _open_sysfs_attr(dev, "descriptors"); + if (fd < 0) + return fd; + + r = read(fd, buffer, DEVICE_DESC_LENGTH);; + close(fd); + if (r < 0) { + usbi_err(DEVICE_CTX(dev), "read failed, ret=%d errno=%d", fd, errno); + return LIBUSB_ERROR_IO; + } else if (r < DEVICE_DESC_LENGTH) { + usbi_err(DEVICE_CTX(dev), "short read %d/%d", r, DEVICE_DESC_LENGTH); + return LIBUSB_ERROR_IO; + } + + return 0; +} + +static int op_get_device_descriptor(struct libusb_device *dev, + unsigned char *buffer, int *host_endian) +{ + if (sysfs_has_descriptors) { + *host_endian = 0; + return sysfs_get_device_descriptor(dev, buffer); + } else { + *host_endian = 1; + return usbfs_get_device_descriptor(dev, buffer); + } +} + +static int usbfs_get_active_config_descriptor(struct libusb_device *dev, + unsigned char *buffer, size_t len) +{ + struct linux_device_priv *priv = _device_priv(dev); + if (!priv->config_descriptor) + return LIBUSB_ERROR_NOT_FOUND; /* device is unconfigured */ + + /* retrieve cached copy */ + memcpy(buffer, priv->config_descriptor, len); + return 0; +} + +/* read the bConfigurationValue for a device */ +static int sysfs_get_active_config(struct libusb_device *dev, int *config) +{ + char *endptr; + char tmp[4] = {0, 0, 0, 0}; + long num; + int fd; + ssize_t r; + + fd = _open_sysfs_attr(dev, "bConfigurationValue"); + if (fd < 0) + return fd; + + r = read(fd, tmp, sizeof(tmp)); + close(fd); + if (r < 0) { + usbi_err(DEVICE_CTX(dev), + "read bConfigurationValue failed ret=%d errno=%d", r, errno); + return LIBUSB_ERROR_IO; + } else if (r == 0) { + usbi_dbg("device unconfigured"); + *config = -1; + return 0; + } + + if (tmp[sizeof(tmp) - 1] != 0) { + usbi_err(DEVICE_CTX(dev), "not null-terminated?"); + return LIBUSB_ERROR_IO; + } else if (tmp[0] == 0) { + usbi_err(DEVICE_CTX(dev), "no configuration value?"); + return LIBUSB_ERROR_IO; + } + + num = strtol(tmp, &endptr, 10); + if (endptr == tmp) { + usbi_err(DEVICE_CTX(dev), "error converting '%s' to integer", tmp); + return LIBUSB_ERROR_IO; + } + + *config = (int) num; + return 0; +} + +/* takes a usbfs/descriptors fd seeked to the start of a configuration, and + * seeks to the next one. */ +static int seek_to_next_config(struct libusb_context *ctx, int fd, + int host_endian) +{ + struct libusb_config_descriptor config; + unsigned char tmp[6]; + off_t off; + ssize_t r; + + /* read first 6 bytes of descriptor */ + r = read(fd, tmp, sizeof(tmp)); + if (r < 0) { + usbi_err(ctx, "read failed ret=%d errno=%d", r, errno); + return LIBUSB_ERROR_IO; + } else if (r < (ssize_t)sizeof(tmp)) { + usbi_err(ctx, "short descriptor read %d/%d", r, sizeof(tmp)); + return LIBUSB_ERROR_IO; + } + + /* seek forward to end of config */ + usbi_parse_descriptor(tmp, "bbwbb", &config, host_endian); + off = lseek(fd, config.wTotalLength - sizeof(tmp), SEEK_CUR); + if (off < 0) { + usbi_err(ctx, "seek failed ret=%d errno=%d", off, errno); + return LIBUSB_ERROR_IO; + } + + return 0; +} + +static int sysfs_get_active_config_descriptor(struct libusb_device *dev, + unsigned char *buffer, size_t len) +{ + int fd; + ssize_t r; + off_t off; + int to_copy; + int config; + unsigned char tmp[6]; + + r = sysfs_get_active_config(dev, &config); + if (r < 0) + return r; + if (config == -1) + return LIBUSB_ERROR_NOT_FOUND; + + usbi_dbg("active configuration %d", config); + + /* sysfs provides access to an in-memory copy of the device descriptor, + * so we use that rather than keeping our own copy */ + + fd = _open_sysfs_attr(dev, "descriptors"); + if (fd < 0) + return fd; + + /* device might have been unconfigured since we read bConfigurationValue, + * so first check that there is any config descriptor data at all... */ + off = lseek(fd, 0, SEEK_END); + if (off < 1) { + usbi_err(DEVICE_CTX(dev), "end seek failed, ret=%d errno=%d", + off, errno); + close(fd); + return LIBUSB_ERROR_IO; + } else if (off == DEVICE_DESC_LENGTH) { + close(fd); + return LIBUSB_ERROR_NOT_FOUND; + } + + off = lseek(fd, DEVICE_DESC_LENGTH, SEEK_SET); + if (off < 0) { + usbi_err(DEVICE_CTX(dev), "seek failed, ret=%d errno=%d", off, errno); + close(fd); + return LIBUSB_ERROR_IO; + } + + /* unbounded loop: we expect the descriptor to be present under all + * circumstances */ + while (1) { + r = read(fd, tmp, sizeof(tmp)); + if (r < 0) { + usbi_err(DEVICE_CTX(dev), "read failed, ret=%d errno=%d", + fd, errno); + return LIBUSB_ERROR_IO; + } else if (r < (ssize_t)sizeof(tmp)) { + usbi_err(DEVICE_CTX(dev), "short read %d/%d", r, sizeof(tmp)); + return LIBUSB_ERROR_IO; + } + + /* check bConfigurationValue */ + if (tmp[5] == config) + break; + + /* try the next descriptor */ + off = lseek(fd, 0 - sizeof(tmp), SEEK_CUR); + if (off < 0) + return LIBUSB_ERROR_IO; + + r = seek_to_next_config(DEVICE_CTX(dev), fd, 0); + if (r < 0) + return r; + } + + to_copy = (len < sizeof(tmp)) ? len : sizeof(tmp); + memcpy(buffer, tmp, to_copy); + if (len > sizeof(tmp)) { + r = read(fd, buffer + sizeof(tmp), len - sizeof(tmp)); + if (r < 0) { + usbi_err(DEVICE_CTX(dev), "read failed, ret=%d errno=%d", + fd, errno); + r = LIBUSB_ERROR_IO; + } else if (r == 0) { + usbi_dbg("device is unconfigured"); + r = LIBUSB_ERROR_NOT_FOUND; + } else if ((size_t)r < len - sizeof(tmp)) { + usbi_err(DEVICE_CTX(dev), "short read %d/%d", r, len); + r = LIBUSB_ERROR_IO; + } + } else { + r = 0; + } + + close(fd); + return r; +} + +int linux_get_device_address (struct libusb_context *ctx, int detached, + uint8_t *busnum, uint8_t *devaddr, + const char *dev_node, const char *sys_name) +{ + int retbus, retdev; + + usbi_dbg("getting address for device: %s detached: %d", + sys_name, detached); + /* can't use sysfs to read the bus and device number if the + device has been detached */ + if (!sysfs_can_relate_devices || detached || NULL == sys_name) { + if (NULL == dev_node) { + return LIBUSB_ERROR_OTHER; + } + + /* will this work with all supported kernel versions? */ + if (!strncmp(dev_node, "/dev/bus/usb", 12)) { + sscanf (dev_node, "/dev/bus/usb/%hhd/%hhd", busnum, devaddr); + } else if (!strncmp(dev_node, "/proc/bus/usb", 13)) { + sscanf (dev_node, "/proc/bus/usb/%hhd/%hhd", busnum, devaddr); + } + + return LIBUSB_SUCCESS; + } + + usbi_dbg("scan %s", sys_name); + + *busnum = retbus = __read_sysfs_attr(ctx, sys_name, "busnum"); + if (retbus < 0) + return retbus; + + *devaddr = retdev = __read_sysfs_attr(ctx, sys_name, "devnum"); + if (retdev < 0) + return retdev; + + usbi_dbg("bus=%d dev=%d", *busnum, *devaddr); + if (retbus > 255 || retdev > 255) + return LIBUSB_ERROR_INVALID_PARAM; + + return LIBUSB_SUCCESS; +} + +static int op_get_active_config_descriptor(struct libusb_device *dev, + unsigned char *buffer, size_t len, int *host_endian) +{ + *host_endian = *host_endian; + if (sysfs_has_descriptors) { + return sysfs_get_active_config_descriptor(dev, buffer, len); + } else { + return usbfs_get_active_config_descriptor(dev, buffer, len); + } +} + +/* takes a usbfs fd, attempts to find the requested config and copy a certain + * amount of it into an output buffer. */ +static int get_config_descriptor(struct libusb_context *ctx, int fd, + uint8_t config_index, unsigned char *buffer, size_t len) +{ + off_t off; + ssize_t r; + + off = lseek(fd, DEVICE_DESC_LENGTH, SEEK_SET); + if (off < 0) { + usbi_err(ctx, "seek failed ret=%d errno=%d", off, errno); + return LIBUSB_ERROR_IO; + } + + /* might need to skip some configuration descriptors to reach the + * requested configuration */ + while (config_index > 0) { + r = seek_to_next_config(ctx, fd, 1); + if (r < 0) + return r; + config_index--; + } + + /* read the rest of the descriptor */ + r = read(fd, buffer, len); + if (r < 0) { + usbi_err(ctx, "read failed ret=%d errno=%d", r, errno); + return LIBUSB_ERROR_IO; + } else if ((size_t)r < len) { + usbi_err(ctx, "short output read %d/%d", r, len); + return LIBUSB_ERROR_IO; + } + + return 0; +} + +static int op_get_config_descriptor(struct libusb_device *dev, + uint8_t config_index, unsigned char *buffer, size_t len, int *host_endian) +{ + char filename[PATH_MAX]; + int fd; + int r; + + *host_endian = *host_endian; + /* always read from usbfs: sysfs only has the active descriptor + * this will involve waking the device up, but oh well! */ + + /* FIXME: the above is no longer true, new kernels have all descriptors + * in the descriptors file. but its kinda hard to detect if the kernel + * is sufficiently new. */ + + _get_usbfs_path(dev, filename); + fd = open(filename, O_RDONLY); + if (fd < 0) { + usbi_err(DEVICE_CTX(dev), + "open '%s' failed, ret=%d errno=%d", filename, fd, errno); + return LIBUSB_ERROR_IO; + } + + r = get_config_descriptor(DEVICE_CTX(dev), fd, config_index, buffer, len); + close(fd); + return r; +} + +/* cache the active config descriptor in memory. a value of -1 means that + * we aren't sure which one is active, so just assume the first one. + * only for usbfs. */ +static int cache_active_config(struct libusb_device *dev, int fd, + int active_config) +{ + struct linux_device_priv *priv = _device_priv(dev); + struct libusb_config_descriptor config; + unsigned char tmp[8]; + unsigned char *buf; + int idx; + int r; + + if (active_config == -1) { + idx = 0; + } else { + r = usbi_get_config_index_by_value(dev, active_config, &idx); + if (r < 0) + return r; + if (idx == -1) + return LIBUSB_ERROR_NOT_FOUND; + } + + r = get_config_descriptor(DEVICE_CTX(dev), fd, idx, tmp, sizeof(tmp)); + if (r < 0) { + usbi_err(DEVICE_CTX(dev), "first read error %d", r); + return r; + } + + usbi_parse_descriptor(tmp, "bbw", &config, 0); + buf = malloc(config.wTotalLength); + if (!buf) + return LIBUSB_ERROR_NO_MEM; + + r = get_config_descriptor(DEVICE_CTX(dev), fd, idx, buf, + config.wTotalLength); + if (r < 0) { + free(buf); + return r; + } + + if (priv->config_descriptor) + free(priv->config_descriptor); + priv->config_descriptor = buf; + return 0; +} + +/* send a control message to retrieve active configuration */ +static int usbfs_get_active_config(struct libusb_device *dev, int fd) +{ + unsigned char active_config = 0; + int r; + + struct usbfs_ctrltransfer ctrl = { + .bmRequestType = LIBUSB_ENDPOINT_IN, + .bRequest = LIBUSB_REQUEST_GET_CONFIGURATION, + .wValue = 0, + .wIndex = 0, + .wLength = 1, + .timeout = 1000, + .data = &active_config + }; + + r = ioctl(fd, IOCTL_USBFS_CONTROL, &ctrl); + if (r < 0) { + if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + + /* we hit this error path frequently with buggy devices :( */ + usbi_warn(DEVICE_CTX(dev), + "get_configuration failed ret=%d errno=%d", r, errno); + return LIBUSB_ERROR_IO; + } + + return active_config; +} + +static int initialize_device(struct libusb_device *dev, uint8_t busnum, + uint8_t devaddr, const char *sysfs_dir) +{ + struct linux_device_priv *priv = _device_priv(dev); + unsigned char *dev_buf; + char path[PATH_MAX]; + int fd, speed; + int active_config = 0; + int device_configured = 1; + ssize_t r; + + dev->bus_number = busnum; + dev->device_address = devaddr; + + if (sysfs_dir) { + priv->sysfs_dir = malloc(strlen(sysfs_dir) + 1); + if (!priv->sysfs_dir) + return LIBUSB_ERROR_NO_MEM; + strcpy(priv->sysfs_dir, sysfs_dir); + + /* Note speed can contain 1.5, in this case __read_sysfs_attr + will stop parsing at the '.' and return 1 */ + speed = __read_sysfs_attr(DEVICE_CTX(dev), sysfs_dir, "speed"); + if (speed >= 0) { + switch (speed) { + case 1: dev->speed = LIBUSB_SPEED_LOW; break; + case 12: dev->speed = LIBUSB_SPEED_FULL; break; + case 480: dev->speed = LIBUSB_SPEED_HIGH; break; + case 5000: dev->speed = LIBUSB_SPEED_SUPER; break; + default: + usbi_warn(DEVICE_CTX(dev), "Unknown device speed: %d Mbps", speed); + } + } + } + + if (sysfs_has_descriptors) + return 0; + + /* cache device descriptor in memory so that we can retrieve it later + * without waking the device up (op_get_device_descriptor) */ + + priv->dev_descriptor = NULL; + priv->config_descriptor = NULL; + + if (sysfs_can_relate_devices) { + int tmp = sysfs_get_active_config(dev, &active_config); + if (tmp < 0) + return tmp; + if (active_config == -1) + device_configured = 0; + } + + _get_usbfs_path(dev, path); + fd = open(path, O_RDWR); + if (fd < 0 && errno == EACCES) { + fd = open(path, O_RDONLY); + /* if we only have read-only access to the device, we cannot + * send a control message to determine the active config. just + * assume the first one is active. */ + active_config = -1; + } + + if (fd < 0) { + usbi_err(DEVICE_CTX(dev), "open failed, ret=%d errno=%d", fd, errno); + return LIBUSB_ERROR_IO; + } + + if (!sysfs_can_relate_devices) { + if (active_config == -1) { + /* if we only have read-only access to the device, we cannot + * send a control message to determine the active config. just + * assume the first one is active. */ + usbi_warn(DEVICE_CTX(dev), "access to %s is read-only; cannot " + "determine active configuration descriptor", path); + } else { + active_config = usbfs_get_active_config(dev, fd); + if (active_config == LIBUSB_ERROR_IO) { + /* buggy devices sometimes fail to report their active config. + * assume unconfigured and continue the probing */ + usbi_warn(DEVICE_CTX(dev), "couldn't query active " + "configuration, assumung unconfigured"); + device_configured = 0; + } else if (active_config < 0) { + close(fd); + return active_config; + } else if (active_config == 0) { + /* some buggy devices have a configuration 0, but we're + * reaching into the corner of a corner case here, so let's + * not support buggy devices in these circumstances. + * stick to the specs: a configuration value of 0 means + * unconfigured. */ + usbi_dbg("active cfg 0? assuming unconfigured device"); + device_configured = 0; + } + } + } + + dev_buf = malloc(DEVICE_DESC_LENGTH); + if (!dev_buf) { + close(fd); + return LIBUSB_ERROR_NO_MEM; + } + + r = read(fd, dev_buf, DEVICE_DESC_LENGTH); + if (r < 0) { + usbi_err(DEVICE_CTX(dev), + "read descriptor failed ret=%d errno=%d", fd, errno); + free(dev_buf); + close(fd); + return LIBUSB_ERROR_IO; + } else if (r < DEVICE_DESC_LENGTH) { + usbi_err(DEVICE_CTX(dev), "short descriptor read (%d)", r); + free(dev_buf); + close(fd); + return LIBUSB_ERROR_IO; + } + + /* bit of a hack: set num_configurations now because cache_active_config() + * calls usbi_get_config_index_by_value() which uses it */ + dev->num_configurations = dev_buf[DEVICE_DESC_LENGTH - 1]; + + if (device_configured) { + r = cache_active_config(dev, fd, active_config); + if (r < 0) { + close(fd); + free(dev_buf); + return r; + } + } + + close(fd); + priv->dev_descriptor = dev_buf; + return 0; +} + +int linux_enumerate_device(struct libusb_context *ctx, + uint8_t busnum, uint8_t devaddr, + const char *sysfs_dir) +{ + unsigned long session_id; + struct libusb_device *dev; + int r = 0; + + /* FIXME: session ID is not guaranteed unique as addresses can wrap and + * will be reused. instead we should add a simple sysfs attribute with + * a session ID. */ + session_id = busnum << 8 | devaddr; + usbi_dbg("busnum %d devaddr %d session_id %ld", busnum, devaddr, + session_id); + + usbi_dbg("allocating new device for %d/%d (session %ld)", + busnum, devaddr, session_id); + dev = usbi_alloc_device(ctx, session_id); + if (!dev) + return LIBUSB_ERROR_NO_MEM; + + r = initialize_device(dev, busnum, devaddr, sysfs_dir); + if (r < 0) + goto out; + r = usbi_sanitize_device(dev); + if (r < 0) + goto out; +out: + if (r < 0) + libusb_unref_device(dev); + else + usbi_connect_device(dev); + + return r; +} + +void linux_hotplug_enumerate(uint8_t busnum, uint8_t devaddr, const char *sys_name) +{ + struct libusb_context *ctx; + + usbi_mutex_lock(&active_contexts_lock); + list_for_each_entry(ctx, &active_contexts_list, list, struct libusb_context) { + if (usbi_get_device_by_session_id(ctx, busnum << 8 | devaddr)) { + /* device already exists in the context */ + usbi_dbg("device already exists in context"); + continue; + } + + linux_enumerate_device(ctx, busnum, devaddr, sys_name); + } + usbi_mutex_unlock(&active_contexts_lock); +} + +void linux_hotplug_disconnected(uint8_t busnum, uint8_t devaddr, const char *sys_name) +{ + struct libusb_context *ctx, *tmp; + struct libusb_device *dev; + + usbi_mutex_lock(&active_contexts_lock); + list_for_each_entry_safe(ctx, tmp, &active_contexts_list, list, struct libusb_context) { + dev = usbi_get_device_by_session_id (ctx, busnum << 8 | devaddr); + if (NULL != dev) { + usbi_disconnect_device (dev); + } else { + usbi_err(ctx, "device not found for session %x %s", busnum << 8 | devaddr, sys_name); + } + } + usbi_mutex_unlock(&active_contexts_lock); +} + +#if !defined(USE_UDEV) +/* open a bus directory and adds all discovered devices to the context */ +static int usbfs_scan_busdir(struct libusb_context *ctx, uint8_t busnum) +{ + DIR *dir; + char dirpath[PATH_MAX]; + struct dirent *entry; + int r = LIBUSB_ERROR_IO; + + snprintf(dirpath, PATH_MAX, "%s/%03d", usbfs_path, busnum); + usbi_dbg("%s", dirpath); + dir = opendir(dirpath); + if (!dir) { + usbi_err(ctx, "opendir '%s' failed, errno=%d", dirpath, errno); + /* FIXME: should handle valid race conditions like hub unplugged + * during directory iteration - this is not an error */ + return r; + } + + while ((entry = readdir(dir))) { + int devaddr; + + if (entry->d_name[0] == '.') + continue; + + devaddr = atoi(entry->d_name); + if (devaddr == 0) { + usbi_dbg("unknown dir entry %s", entry->d_name); + continue; + } + + if (linux_enumerate_device(ctx, busnum, (uint8_t) devaddr, NULL)) { + usbi_dbg("failed to enumerate dir entry %s", entry->d_name); + continue; + } + + r = 0; + } + + closedir(dir); + return r; +} + +static int usbfs_get_device_list(struct libusb_context *ctx) +{ + struct dirent *entry; + DIR *buses = opendir(usbfs_path); + int r = 0; + + if (!buses) { + usbi_err(ctx, "opendir buses failed errno=%d", errno); + return LIBUSB_ERROR_IO; + } + + while ((entry = readdir(buses))) { + int busnum; + + if (entry->d_name[0] == '.') + continue; + + if (usbdev_names) { + int devaddr; + if (!_is_usbdev_entry(entry, &busnum, &devaddr)) + continue; + + r = linux_enumerate_device(ctx, busnum, (uint8_t) devaddr, NULL); + if (r < 0) { + usbi_dbg("failed to enumerate dir entry %s", entry->d_name); + continue; + } + } else { + busnum = atoi(entry->d_name); + if (busnum == 0) { + usbi_dbg("unknown dir entry %s", entry->d_name); + continue; + } + + r = usbfs_scan_busdir(ctx, busnum); + if (r < 0) + break; + } + } + + closedir(buses); + return r; + +} + +static int sysfs_scan_device(struct libusb_context *ctx, const char *devname) +{ + uint8_t busnum, devaddr; + int ret; + + ret = linux_get_device_address (ctx, 0, &busnum, &devaddr, NULL, devname); + if (LIBUSB_SUCCESS != ret) { + return ret; + } + + return linux_enumerate_device(ctx, busnum & 0xff, devaddr & 0xff, + devname); +} + +static int sysfs_get_device_list(struct libusb_context *ctx) +{ + DIR *devices = opendir(SYSFS_DEVICE_PATH); + struct dirent *entry; + int r = LIBUSB_ERROR_IO; + + if (!devices) { + usbi_err(ctx, "opendir devices failed errno=%d", errno); + return r; + } + + while ((entry = readdir(devices))) { + if ((!isdigit(entry->d_name[0]) && strncmp(entry->d_name, "usb", 3)) + || strchr(entry->d_name, ':')) + continue; + + if (sysfs_scan_device(ctx, entry->d_name)) { + usbi_dbg("failed to enumerate dir entry %s", entry->d_name); + continue; + } + + r = 0; + } + + closedir(devices); + return r; +} + +static int linux_default_scan_devices (struct libusb_context *ctx) +{ + /* we can retrieve device list and descriptors from sysfs or usbfs. + * sysfs is preferable, because if we use usbfs we end up resuming + * any autosuspended USB devices. however, sysfs is not available + * everywhere, so we need a usbfs fallback too. + * + * as described in the "sysfs vs usbfs" comment at the top of this + * file, sometimes we have sysfs but not enough information to + * relate sysfs devices to usbfs nodes. op_init() determines the + * adequacy of sysfs and sets sysfs_can_relate_devices. + */ + if (sysfs_can_relate_devices != 0) + return sysfs_get_device_list(ctx); + else + return usbfs_get_device_list(ctx); +} +#endif + +static int op_open(struct libusb_device_handle *handle) +{ + struct linux_device_handle_priv *hpriv = _device_handle_priv(handle); + char filename[PATH_MAX]; + int r; + + _get_usbfs_path(handle->dev, filename); + usbi_dbg("opening %s", filename); + hpriv->fd = open(filename, O_RDWR); + if (hpriv->fd < 0) { + if (errno == EACCES) { + usbi_err(HANDLE_CTX(handle), "libusb couldn't open USB device %s: " + "Permission denied.", filename); + usbi_err(HANDLE_CTX(handle), + "libusb requires write access to USB device nodes."); + return LIBUSB_ERROR_ACCESS; + } else if (errno == ENOENT) { + usbi_err(HANDLE_CTX(handle), "libusb couldn't open USB device %s: " + "No such file or directory.", filename); + return LIBUSB_ERROR_NO_DEVICE; + } else { + usbi_err(HANDLE_CTX(handle), + "open failed, code %d errno %d", hpriv->fd, errno); + return LIBUSB_ERROR_IO; + } + } + + r = ioctl(hpriv->fd, IOCTL_USBFS_GET_CAPABILITIES, &hpriv->caps); + if (r < 0) { + if (errno == ENOTTY) + usbi_dbg("%s: getcap not available", filename); + else + usbi_err(HANDLE_CTX(handle), + "%s: getcap failed (%d)", filename, errno); + hpriv->caps = 0; + if (supports_flag_zero_packet) + hpriv->caps |= USBFS_CAP_ZERO_PACKET; + if (supports_flag_bulk_continuation) + hpriv->caps |= USBFS_CAP_BULK_CONTINUATION; + } + + return usbi_add_pollfd(HANDLE_CTX(handle), hpriv->fd, POLLOUT); +} + +static void op_close(struct libusb_device_handle *dev_handle) +{ + int fd = _device_handle_priv(dev_handle)->fd; + usbi_remove_pollfd(HANDLE_CTX(dev_handle), fd); + close(fd); +} + +static int op_get_configuration(struct libusb_device_handle *handle, + int *config) +{ + int r; + if (sysfs_can_relate_devices != 1) + return LIBUSB_ERROR_NOT_SUPPORTED; + + r = sysfs_get_active_config(handle->dev, config); + if (r < 0) + return r; + + if (*config == -1) { + usbi_err(HANDLE_CTX(handle), "device unconfigured"); + *config = 0; + } + + return 0; +} + +static int op_set_configuration(struct libusb_device_handle *handle, int config) +{ + struct linux_device_priv *priv = _device_priv(handle->dev); + int fd = _device_handle_priv(handle)->fd; + int r = ioctl(fd, IOCTL_USBFS_SETCONFIG, &config); + if (r) { + if (errno == EINVAL) + return LIBUSB_ERROR_NOT_FOUND; + else if (errno == EBUSY) + return LIBUSB_ERROR_BUSY; + else if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_err(HANDLE_CTX(handle), "failed, error %d errno %d", r, errno); + return LIBUSB_ERROR_OTHER; + } + + if (!sysfs_has_descriptors) { + /* update our cached active config descriptor */ + if (config == -1) { + if (priv->config_descriptor) { + free(priv->config_descriptor); + priv->config_descriptor = NULL; + } + } else { + r = cache_active_config(handle->dev, fd, config); + if (r < 0) + usbi_warn(HANDLE_CTX(handle), + "failed to update cached config descriptor, error %d", r); + } + } + + return 0; +} + +static int op_claim_interface(struct libusb_device_handle *handle, int iface) +{ + int fd = _device_handle_priv(handle)->fd; + int r = ioctl(fd, IOCTL_USBFS_CLAIMINTF, &iface); + if (r) { + if (errno == ENOENT) + return LIBUSB_ERROR_NOT_FOUND; + else if (errno == EBUSY) + return LIBUSB_ERROR_BUSY; + else if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_err(HANDLE_CTX(handle), + "claim interface failed, error %d errno %d", r, errno); + return LIBUSB_ERROR_OTHER; + } + return 0; +} + +static int op_release_interface(struct libusb_device_handle *handle, int iface) +{ + int fd = _device_handle_priv(handle)->fd; + int r = ioctl(fd, IOCTL_USBFS_RELEASEINTF, &iface); + if (r) { + if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_err(HANDLE_CTX(handle), + "release interface failed, error %d errno %d", r, errno); + return LIBUSB_ERROR_OTHER; + } + return 0; +} + +static int op_set_interface(struct libusb_device_handle *handle, int iface, + int altsetting) +{ + int fd = _device_handle_priv(handle)->fd; + struct usbfs_setinterface setintf; + int r; + + setintf.interface = iface; + setintf.altsetting = altsetting; + r = ioctl(fd, IOCTL_USBFS_SETINTF, &setintf); + if (r) { + if (errno == EINVAL) + return LIBUSB_ERROR_NOT_FOUND; + else if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_err(HANDLE_CTX(handle), + "setintf failed error %d errno %d", r, errno); + return LIBUSB_ERROR_OTHER; + } + + return 0; +} + +static int op_clear_halt(struct libusb_device_handle *handle, + unsigned char endpoint) +{ + int fd = _device_handle_priv(handle)->fd; + unsigned int _endpoint = endpoint; + int r = ioctl(fd, IOCTL_USBFS_CLEAR_HALT, &_endpoint); + if (r) { + if (errno == ENOENT) + return LIBUSB_ERROR_NOT_FOUND; + else if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_err(HANDLE_CTX(handle), + "clear_halt failed error %d errno %d", r, errno); + return LIBUSB_ERROR_OTHER; + } + + return 0; +} + +static int op_reset_device(struct libusb_device_handle *handle) +{ + int fd = _device_handle_priv(handle)->fd; + int i, r, ret = 0; + + /* Doing a device reset will cause the usbfs driver to get unbound + from any interfaces it is bound to. By voluntarily unbinding + the usbfs driver ourself, we stop the kernel from rebinding + the interface after reset (which would end up with the interface + getting bound to the in kernel driver if any). */ + for (i = 0; i < USB_MAXINTERFACES; i++) { + if (handle->claimed_interfaces & (1L << i)) { + op_release_interface(handle, i); + } + } + + usbi_mutex_lock(&handle->lock); + r = ioctl(fd, IOCTL_USBFS_RESET, NULL); + if (r) { + if (errno == ENODEV) { + ret = LIBUSB_ERROR_NOT_FOUND; + goto out; + } + + usbi_err(HANDLE_CTX(handle), + "reset failed error %d errno %d", r, errno); + ret = LIBUSB_ERROR_OTHER; + goto out; + } + + /* And re-claim any interfaces which were claimed before the reset */ + for (i = 0; i < USB_MAXINTERFACES; i++) { + if (handle->claimed_interfaces & (1L << i)) { + r = op_claim_interface(handle, i); + if (r) { + usbi_warn(HANDLE_CTX(handle), + "failed to re-claim interface %d after reset", i); + handle->claimed_interfaces &= ~(1L << i); + } + } + } +out: + usbi_mutex_unlock(&handle->lock); + return ret; +} + +static int op_kernel_driver_active(struct libusb_device_handle *handle, + int interface) +{ + int fd = _device_handle_priv(handle)->fd; + struct usbfs_getdriver getdrv; + int r; + + getdrv.interface = interface; + r = ioctl(fd, IOCTL_USBFS_GETDRIVER, &getdrv); + if (r) { + if (errno == ENODATA) + return 0; + else if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_err(HANDLE_CTX(handle), + "get driver failed error %d errno %d", r, errno); + return LIBUSB_ERROR_OTHER; + } + + return 1; +} + +static int op_detach_kernel_driver(struct libusb_device_handle *handle, + int interface) +{ + int fd = _device_handle_priv(handle)->fd; + struct usbfs_ioctl command; + int r; + + command.ifno = interface; + command.ioctl_code = IOCTL_USBFS_DISCONNECT; + command.data = NULL; + + r = ioctl(fd, IOCTL_USBFS_IOCTL, &command); + if (r) { + if (errno == ENODATA) + return LIBUSB_ERROR_NOT_FOUND; + else if (errno == EINVAL) + return LIBUSB_ERROR_INVALID_PARAM; + else if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_err(HANDLE_CTX(handle), + "detach failed error %d errno %d", r, errno); + return LIBUSB_ERROR_OTHER; + } + + return 0; +} + +static int op_attach_kernel_driver(struct libusb_device_handle *handle, + int interface) +{ + int fd = _device_handle_priv(handle)->fd; + struct usbfs_ioctl command; + int r; + + command.ifno = interface; + command.ioctl_code = IOCTL_USBFS_CONNECT; + command.data = NULL; + + r = ioctl(fd, IOCTL_USBFS_IOCTL, &command); + if (r < 0) { + if (errno == ENODATA) + return LIBUSB_ERROR_NOT_FOUND; + else if (errno == EINVAL) + return LIBUSB_ERROR_INVALID_PARAM; + else if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + else if (errno == EBUSY) + return LIBUSB_ERROR_BUSY; + + usbi_err(HANDLE_CTX(handle), + "attach failed error %d errno %d", r, errno); + return LIBUSB_ERROR_OTHER; + } else if (r == 0) { + return LIBUSB_ERROR_NOT_FOUND; + } + + return 0; +} + +static void op_destroy_device(struct libusb_device *dev) +{ + struct linux_device_priv *priv = _device_priv(dev); + if (!sysfs_has_descriptors) { + if (priv->dev_descriptor) + free(priv->dev_descriptor); + if (priv->config_descriptor) + free(priv->config_descriptor); + } + if (priv->sysfs_dir) + free(priv->sysfs_dir); +} + +/* URBs are discarded in reverse order of submission to avoid races. */ +static int discard_urbs(struct usbi_transfer *itransfer, int first, int last_plus_one) +{ + struct libusb_transfer *transfer = + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct linux_transfer_priv *tpriv = + usbi_transfer_get_os_priv(itransfer); + struct linux_device_handle_priv *dpriv = + _device_handle_priv(transfer->dev_handle); + int i, ret = 0; + struct usbfs_urb *urb; + + for (i = last_plus_one - 1; i >= first; i--) { + if (LIBUSB_TRANSFER_TYPE_ISOCHRONOUS == transfer->type) + urb = tpriv->iso_urbs[i]; + else + urb = &tpriv->urbs[i]; + + if (0 == ioctl(dpriv->fd, IOCTL_USBFS_DISCARDURB, urb)) + continue; + + if (EINVAL == errno) { + usbi_dbg("URB not found --> assuming ready to be reaped"); + if (i == (last_plus_one - 1)) + ret = LIBUSB_ERROR_NOT_FOUND; + } else if (ENODEV == errno) { + usbi_dbg("Device not found for URB --> assuming ready to be reaped"); + ret = LIBUSB_ERROR_NO_DEVICE; + } else { + usbi_warn(TRANSFER_CTX(transfer), + "unrecognised discard errno %d", errno); + ret = LIBUSB_ERROR_OTHER; + } + } + return ret; +} + +static void free_iso_urbs(struct linux_transfer_priv *tpriv) +{ + int i; + for (i = 0; i < tpriv->num_urbs; i++) { + struct usbfs_urb *urb = tpriv->iso_urbs[i]; + if (!urb) + break; + free(urb); + } + + free(tpriv->iso_urbs); + tpriv->iso_urbs = NULL; +} + +static int submit_bulk_transfer(struct usbi_transfer *itransfer, + unsigned char urb_type) +{ + struct libusb_transfer *transfer = + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct linux_transfer_priv *tpriv = usbi_transfer_get_os_priv(itransfer); + struct linux_device_handle_priv *dpriv = + _device_handle_priv(transfer->dev_handle); + struct usbfs_urb *urbs; + int is_out = (transfer->endpoint & LIBUSB_ENDPOINT_DIR_MASK) + == LIBUSB_ENDPOINT_OUT; + int bulk_buffer_len, use_bulk_continuation; + int r; + int i; + size_t alloc_size; + + if (tpriv->urbs) + return LIBUSB_ERROR_BUSY; + + if (is_out && (transfer->flags & LIBUSB_TRANSFER_ADD_ZERO_PACKET) && + !(dpriv->caps & USBFS_CAP_ZERO_PACKET)) + return LIBUSB_ERROR_NOT_SUPPORTED; + + /* + * Older versions of usbfs place a 16kb limit on bulk URBs. We work + * around this by splitting large transfers into 16k blocks, and then + * submit all urbs at once. it would be simpler to submit one urb at + * a time, but there is a big performance gain doing it this way. + * + * Newer versions lift the 16k limit (USBFS_CAP_NO_PACKET_SIZE_LIM), + * using arbritary large transfers can still be a bad idea though, as + * the kernel needs to allocate physical contiguous memory for this, + * which may fail for large buffers. + * + * The kernel solves this problem by splitting the transfer into + * blocks itself when the host-controller is scatter-gather capable + * (USBFS_CAP_BULK_SCATTER_GATHER), which most controllers are. + * + * Last, there is the issue of short-transfers when splitting, for + * short split-transfers to work reliable USBFS_CAP_BULK_CONTINUATION + * is needed, but this is not always available. + */ + if (dpriv->caps & USBFS_CAP_BULK_SCATTER_GATHER) { + /* Good! Just submit everything in one go */ + bulk_buffer_len = transfer->length ? transfer->length : 1; + use_bulk_continuation = 0; + } else if (dpriv->caps & USBFS_CAP_BULK_CONTINUATION) { + /* Split the transfers and use bulk-continuation to + avoid issues with short-transfers */ + bulk_buffer_len = MAX_BULK_BUFFER_LENGTH; + use_bulk_continuation = 1; + } else if (dpriv->caps & USBFS_CAP_NO_PACKET_SIZE_LIM) { + /* Don't split, assume the kernel can alloc the buffer + (otherwise the submit will fail with -ENOMEM) */ + bulk_buffer_len = transfer->length ? transfer->length : 1; + use_bulk_continuation = 0; + } else { + /* Bad, splitting without bulk-continuation, short transfers + which end before the last urb will not work reliable! */ + /* Note we don't warn here as this is "normal" on kernels < + 2.6.32 and not a problem for most applications */ + bulk_buffer_len = MAX_BULK_BUFFER_LENGTH; + use_bulk_continuation = 0; + } + + int num_urbs = transfer->length / bulk_buffer_len; + int last_urb_partial = 0; + + if (transfer->length == 0) { + num_urbs = 1; + } else if ((transfer->length % bulk_buffer_len) > 0) { + last_urb_partial = 1; + num_urbs++; + } + usbi_dbg("need %d urbs for new transfer with length %d", num_urbs, + transfer->length); + alloc_size = num_urbs * sizeof(struct usbfs_urb); + urbs = malloc(alloc_size); + if (!urbs) + return LIBUSB_ERROR_NO_MEM; + memset(urbs, 0, alloc_size); + tpriv->urbs = urbs; + tpriv->num_urbs = num_urbs; + tpriv->num_retired = 0; + tpriv->reap_action = NORMAL; + tpriv->reap_status = LIBUSB_TRANSFER_COMPLETED; + + for (i = 0; i < num_urbs; i++) { + struct usbfs_urb *urb = &urbs[i]; + urb->usercontext = itransfer; + urb->type = urb_type; + urb->endpoint = transfer->endpoint; + urb->buffer = transfer->buffer + (i * bulk_buffer_len); + /* don't set the short not ok flag for the last URB */ + if (use_bulk_continuation && !is_out && i < num_urbs - 1) + urb->flags = USBFS_URB_SHORT_NOT_OK; + if (i == num_urbs - 1 && last_urb_partial) + urb->buffer_length = transfer->length % bulk_buffer_len; + else if (transfer->length == 0) + urb->buffer_length = 0; + else + urb->buffer_length = bulk_buffer_len; + + if (i > 0 && use_bulk_continuation) + urb->flags |= USBFS_URB_BULK_CONTINUATION; + + /* we have already checked that the flag is supported */ + if (is_out && i == num_urbs - 1 && + transfer->flags & LIBUSB_TRANSFER_ADD_ZERO_PACKET) + urb->flags |= USBFS_URB_ZERO_PACKET; + + r = ioctl(dpriv->fd, IOCTL_USBFS_SUBMITURB, urb); + if (r < 0) { + if (errno == ENODEV) { + r = LIBUSB_ERROR_NO_DEVICE; + } else { + usbi_err(TRANSFER_CTX(transfer), + "submiturb failed error %d errno=%d", r, errno); + r = LIBUSB_ERROR_IO; + } + + /* if the first URB submission fails, we can simply free up and + * return failure immediately. */ + if (i == 0) { + usbi_dbg("first URB failed, easy peasy"); + free(urbs); + tpriv->urbs = NULL; + return r; + } + + /* if it's not the first URB that failed, the situation is a bit + * tricky. we may need to discard all previous URBs. there are + * complications: + * - discarding is asynchronous - discarded urbs will be reaped + * later. the user must not have freed the transfer when the + * discarded URBs are reaped, otherwise libusb will be using + * freed memory. + * - the earlier URBs may have completed successfully and we do + * not want to throw away any data. + * - this URB failing may be no error; EREMOTEIO means that + * this transfer simply didn't need all the URBs we submitted + * so, we report that the transfer was submitted successfully and + * in case of error we discard all previous URBs. later when + * the final reap completes we can report error to the user, + * or success if an earlier URB was completed successfully. + */ + tpriv->reap_action = EREMOTEIO == errno ? COMPLETED_EARLY : SUBMIT_FAILED; + + /* The URBs we haven't submitted yet we count as already + * retired. */ + tpriv->num_retired += num_urbs - i; + + /* If we completed short then don't try to discard. */ + if (COMPLETED_EARLY == tpriv->reap_action) + return 0; + + discard_urbs(itransfer, 0, i); + + usbi_dbg("reporting successful submission but waiting for %d " + "discards before reporting error", i); + return 0; + } + } + + return 0; +} + +static int submit_iso_transfer(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct linux_transfer_priv *tpriv = usbi_transfer_get_os_priv(itransfer); + struct linux_device_handle_priv *dpriv = + _device_handle_priv(transfer->dev_handle); + struct usbfs_urb **urbs; + size_t alloc_size; + int num_packets = transfer->num_iso_packets; + int i; + int this_urb_len = 0; + int num_urbs = 1; + int packet_offset = 0; + unsigned int packet_len; + unsigned char *urb_buffer = transfer->buffer; + + if (tpriv->iso_urbs) + return LIBUSB_ERROR_BUSY; + + /* usbfs places a 32kb limit on iso URBs. we divide up larger requests + * into smaller units to meet such restriction, then fire off all the + * units at once. it would be simpler if we just fired one unit at a time, + * but there is a big performance gain through doing it this way. + * + * Newer kernels lift the 32k limit (USBFS_CAP_NO_PACKET_SIZE_LIM), + * using arbritary large transfers is still be a bad idea though, as + * the kernel needs to allocate physical contiguous memory for this, + * which may fail for large buffers. + */ + + /* calculate how many URBs we need */ + for (i = 0; i < num_packets; i++) { + unsigned int space_remaining = MAX_ISO_BUFFER_LENGTH - this_urb_len; + packet_len = transfer->iso_packet_desc[i].length; + + if (packet_len > space_remaining) { + num_urbs++; + this_urb_len = packet_len; + } else { + this_urb_len += packet_len; + } + } + usbi_dbg("need %d 32k URBs for transfer", num_urbs); + + alloc_size = num_urbs * sizeof(*urbs); + urbs = malloc(alloc_size); + if (!urbs) + return LIBUSB_ERROR_NO_MEM; + memset(urbs, 0, alloc_size); + + tpriv->iso_urbs = urbs; + tpriv->num_urbs = num_urbs; + tpriv->num_retired = 0; + tpriv->reap_action = NORMAL; + tpriv->iso_packet_offset = 0; + + /* allocate + initialize each URB with the correct number of packets */ + for (i = 0; i < num_urbs; i++) { + struct usbfs_urb *urb; + unsigned int space_remaining_in_urb = MAX_ISO_BUFFER_LENGTH; + int urb_packet_offset = 0; + unsigned char *urb_buffer_orig = urb_buffer; + int j; + int k; + + /* swallow up all the packets we can fit into this URB */ + while (packet_offset < transfer->num_iso_packets) { + packet_len = transfer->iso_packet_desc[packet_offset].length; + if (packet_len <= space_remaining_in_urb) { + /* throw it in */ + urb_packet_offset++; + packet_offset++; + space_remaining_in_urb -= packet_len; + urb_buffer += packet_len; + } else { + /* it can't fit, save it for the next URB */ + break; + } + } + + alloc_size = sizeof(*urb) + + (urb_packet_offset * sizeof(struct usbfs_iso_packet_desc)); + urb = malloc(alloc_size); + if (!urb) { + free_iso_urbs(tpriv); + return LIBUSB_ERROR_NO_MEM; + } + memset(urb, 0, alloc_size); + urbs[i] = urb; + + /* populate packet lengths */ + for (j = 0, k = packet_offset - urb_packet_offset; + k < packet_offset; k++, j++) { + packet_len = transfer->iso_packet_desc[k].length; + urb->iso_frame_desc[j].length = packet_len; + } + + urb->usercontext = itransfer; + urb->type = USBFS_URB_TYPE_ISO; + /* FIXME: interface for non-ASAP data? */ + urb->flags = USBFS_URB_ISO_ASAP; + urb->endpoint = transfer->endpoint; + urb->number_of_packets = urb_packet_offset; + urb->buffer = urb_buffer_orig; + } + + /* submit URBs */ + for (i = 0; i < num_urbs; i++) { + int r = ioctl(dpriv->fd, IOCTL_USBFS_SUBMITURB, urbs[i]); + if (r < 0) { + if (errno == ENODEV) { + r = LIBUSB_ERROR_NO_DEVICE; + } else { + usbi_err(TRANSFER_CTX(transfer), + "submiturb failed error %d errno=%d", r, errno); + r = LIBUSB_ERROR_IO; + } + + /* if the first URB submission fails, we can simply free up and + * return failure immediately. */ + if (i == 0) { + usbi_dbg("first URB failed, easy peasy"); + free_iso_urbs(tpriv); + return r; + } + + /* if it's not the first URB that failed, the situation is a bit + * tricky. we must discard all previous URBs. there are + * complications: + * - discarding is asynchronous - discarded urbs will be reaped + * later. the user must not have freed the transfer when the + * discarded URBs are reaped, otherwise libusb will be using + * freed memory. + * - the earlier URBs may have completed successfully and we do + * not want to throw away any data. + * so, in this case we discard all the previous URBs BUT we report + * that the transfer was submitted successfully. then later when + * the final discard completes we can report error to the user. + */ + tpriv->reap_action = SUBMIT_FAILED; + + /* The URBs we haven't submitted yet we count as already + * retired. */ + tpriv->num_retired = num_urbs - i; + discard_urbs(itransfer, 0, i); + + usbi_dbg("reporting successful submission but waiting for %d " + "discards before reporting error", i); + return 0; + } + } + + return 0; +} + +static int submit_control_transfer(struct usbi_transfer *itransfer) +{ + struct linux_transfer_priv *tpriv = usbi_transfer_get_os_priv(itransfer); + struct libusb_transfer *transfer = + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct linux_device_handle_priv *dpriv = + _device_handle_priv(transfer->dev_handle); + struct usbfs_urb *urb; + int r; + + if (tpriv->urbs) + return LIBUSB_ERROR_BUSY; + + if (transfer->length - LIBUSB_CONTROL_SETUP_SIZE > MAX_CTRL_BUFFER_LENGTH) + return LIBUSB_ERROR_INVALID_PARAM; + + urb = malloc(sizeof(struct usbfs_urb)); + if (!urb) + return LIBUSB_ERROR_NO_MEM; + memset(urb, 0, sizeof(struct usbfs_urb)); + tpriv->urbs = urb; + tpriv->num_urbs = 1; + tpriv->reap_action = NORMAL; + + urb->usercontext = itransfer; + urb->type = USBFS_URB_TYPE_CONTROL; + urb->endpoint = transfer->endpoint; + urb->buffer = transfer->buffer; + urb->buffer_length = transfer->length; + + r = ioctl(dpriv->fd, IOCTL_USBFS_SUBMITURB, urb); + if (r < 0) { + free(urb); + tpriv->urbs = NULL; + if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_err(TRANSFER_CTX(transfer), + "submiturb failed error %d errno=%d", r, errno); + return LIBUSB_ERROR_IO; + } + return 0; +} + +static int op_submit_transfer(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + + switch (transfer->type) { + case LIBUSB_TRANSFER_TYPE_CONTROL: + return submit_control_transfer(itransfer); + case LIBUSB_TRANSFER_TYPE_BULK: + return submit_bulk_transfer(itransfer, USBFS_URB_TYPE_BULK); + case LIBUSB_TRANSFER_TYPE_INTERRUPT: + return submit_bulk_transfer(itransfer, USBFS_URB_TYPE_INTERRUPT); + case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS: + return submit_iso_transfer(itransfer); + default: + usbi_err(TRANSFER_CTX(transfer), + "unknown endpoint type %d", transfer->type); + return LIBUSB_ERROR_INVALID_PARAM; + } +} + +static int op_cancel_transfer(struct usbi_transfer *itransfer) +{ + struct linux_transfer_priv *tpriv = usbi_transfer_get_os_priv(itransfer); + struct libusb_transfer *transfer = + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + + switch (transfer->type) { + case LIBUSB_TRANSFER_TYPE_BULK: + if (tpriv->reap_action == ERROR) + break; + /* else, fall through */ + case LIBUSB_TRANSFER_TYPE_CONTROL: + case LIBUSB_TRANSFER_TYPE_INTERRUPT: + case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS: + tpriv->reap_action = CANCELLED; + break; + default: + usbi_err(TRANSFER_CTX(transfer), + "unknown endpoint type %d", transfer->type); + return LIBUSB_ERROR_INVALID_PARAM; + } + + if (!tpriv->urbs) + return LIBUSB_ERROR_NOT_FOUND; + + return discard_urbs(itransfer, 0, tpriv->num_urbs); +} + +static void op_clear_transfer_priv(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct linux_transfer_priv *tpriv = usbi_transfer_get_os_priv(itransfer); + + /* urbs can be freed also in submit_transfer so lock mutex first */ + switch (transfer->type) { + case LIBUSB_TRANSFER_TYPE_CONTROL: + case LIBUSB_TRANSFER_TYPE_BULK: + case LIBUSB_TRANSFER_TYPE_INTERRUPT: + usbi_mutex_lock(&itransfer->lock); + if (tpriv->urbs) + free(tpriv->urbs); + tpriv->urbs = NULL; + usbi_mutex_unlock(&itransfer->lock); + break; + case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS: + usbi_mutex_lock(&itransfer->lock); + if (tpriv->iso_urbs) + free_iso_urbs(tpriv); + usbi_mutex_unlock(&itransfer->lock); + break; + default: + usbi_err(TRANSFER_CTX(transfer), + "unknown endpoint type %d", transfer->type); + } +} + +static int handle_bulk_completion(struct usbi_transfer *itransfer, + struct usbfs_urb *urb) +{ + struct linux_transfer_priv *tpriv = usbi_transfer_get_os_priv(itransfer); + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + int urb_idx = urb - tpriv->urbs; + + usbi_mutex_lock(&itransfer->lock); + usbi_dbg("handling completion status %d of bulk urb %d/%d", urb->status, + urb_idx + 1, tpriv->num_urbs); + + tpriv->num_retired++; + + if (tpriv->reap_action != NORMAL) { + /* cancelled, submit_fail, or completed early */ + usbi_dbg("abnormal reap: urb status %d", urb->status); + + /* even though we're in the process of cancelling, it's possible that + * we may receive some data in these URBs that we don't want to lose. + * examples: + * 1. while the kernel is cancelling all the packets that make up an + * URB, a few of them might complete. so we get back a successful + * cancellation *and* some data. + * 2. we receive a short URB which marks the early completion condition, + * so we start cancelling the remaining URBs. however, we're too + * slow and another URB completes (or at least completes partially). + * (this can't happen since we always use BULK_CONTINUATION.) + * + * When this happens, our objectives are not to lose any "surplus" data, + * and also to stick it at the end of the previously-received data + * (closing any holes), so that libusb reports the total amount of + * transferred data and presents it in a contiguous chunk. + */ + if (urb->actual_length > 0) { + unsigned char *target = transfer->buffer + itransfer->transferred; + usbi_dbg("received %d bytes of surplus data", urb->actual_length); + if (urb->buffer != target) { + usbi_dbg("moving surplus data from offset %d to offset %d", + (unsigned char *) urb->buffer - transfer->buffer, + target - transfer->buffer); + memmove(target, urb->buffer, urb->actual_length); + } + itransfer->transferred += urb->actual_length; + } + + if (tpriv->num_retired == tpriv->num_urbs) { + usbi_dbg("abnormal reap: last URB handled, reporting"); + if (tpriv->reap_action != COMPLETED_EARLY && + tpriv->reap_status == LIBUSB_TRANSFER_COMPLETED) + tpriv->reap_status = LIBUSB_TRANSFER_ERROR; + goto completed; + } + goto out_unlock; + } + + itransfer->transferred += urb->actual_length; + + /* Many of these errors can occur on *any* urb of a multi-urb + * transfer. When they do, we tear down the rest of the transfer. + */ + switch (urb->status) { + case 0: + break; + case -EREMOTEIO: /* short transfer */ + break; + case -ENOENT: /* cancelled */ + case -ECONNRESET: + break; + case -ENODEV: + case -ESHUTDOWN: + usbi_dbg("device removed"); + tpriv->reap_status = LIBUSB_TRANSFER_NO_DEVICE; + goto cancel_remaining; + case -EPIPE: + usbi_dbg("detected endpoint stall"); + if (tpriv->reap_status == LIBUSB_TRANSFER_COMPLETED) + tpriv->reap_status = LIBUSB_TRANSFER_STALL; + goto cancel_remaining; + case -EOVERFLOW: + /* overflow can only ever occur in the last urb */ + usbi_dbg("overflow, actual_length=%d", urb->actual_length); + if (tpriv->reap_status == LIBUSB_TRANSFER_COMPLETED) + tpriv->reap_status = LIBUSB_TRANSFER_OVERFLOW; + goto completed; + case -ETIME: + case -EPROTO: + case -EILSEQ: + case -ECOMM: + case -ENOSR: + usbi_dbg("low level error %d", urb->status); + tpriv->reap_action = ERROR; + goto cancel_remaining; + default: + usbi_warn(ITRANSFER_CTX(itransfer), + "unrecognised urb status %d", urb->status); + tpriv->reap_action = ERROR; + goto cancel_remaining; + } + + /* if we're the last urb or we got less data than requested then we're + * done */ + if (urb_idx == tpriv->num_urbs - 1) { + usbi_dbg("last URB in transfer --> complete!"); + goto completed; + } else if (urb->actual_length < urb->buffer_length) { + usbi_dbg("short transfer %d/%d --> complete!", + urb->actual_length, urb->buffer_length); + if (tpriv->reap_action == NORMAL) + tpriv->reap_action = COMPLETED_EARLY; + } else + goto out_unlock; + +cancel_remaining: + if (ERROR == tpriv->reap_action && LIBUSB_TRANSFER_COMPLETED == tpriv->reap_status) + tpriv->reap_status = LIBUSB_TRANSFER_ERROR; + + if (tpriv->num_retired == tpriv->num_urbs) /* nothing to cancel */ + goto completed; + + /* cancel remaining urbs and wait for their completion before + * reporting results */ + discard_urbs(itransfer, urb_idx + 1, tpriv->num_urbs); + +out_unlock: + usbi_mutex_unlock(&itransfer->lock); + return 0; + +completed: + free(tpriv->urbs); + tpriv->urbs = NULL; + usbi_mutex_unlock(&itransfer->lock); + return CANCELLED == tpriv->reap_action ? + usbi_handle_transfer_cancellation(itransfer) : + usbi_handle_transfer_completion(itransfer, tpriv->reap_status); +} + +static int handle_iso_completion(struct usbi_transfer *itransfer, + struct usbfs_urb *urb) +{ + struct libusb_transfer *transfer = + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct linux_transfer_priv *tpriv = usbi_transfer_get_os_priv(itransfer); + int num_urbs = tpriv->num_urbs; + int urb_idx = 0; + int i; + enum libusb_transfer_status status = LIBUSB_TRANSFER_COMPLETED; + + usbi_mutex_lock(&itransfer->lock); + for (i = 0; i < num_urbs; i++) { + if (urb == tpriv->iso_urbs[i]) { + urb_idx = i + 1; + break; + } + } + if (urb_idx == 0) { + usbi_err(TRANSFER_CTX(transfer), "could not locate urb!"); + usbi_mutex_unlock(&itransfer->lock); + return LIBUSB_ERROR_NOT_FOUND; + } + + usbi_dbg("handling completion status %d of iso urb %d/%d", urb->status, + urb_idx, num_urbs); + + /* copy isochronous results back in */ + + for (i = 0; i < urb->number_of_packets; i++) { + struct usbfs_iso_packet_desc *urb_desc = &urb->iso_frame_desc[i]; + struct libusb_iso_packet_descriptor *lib_desc = + &transfer->iso_packet_desc[tpriv->iso_packet_offset++]; + lib_desc->status = LIBUSB_TRANSFER_COMPLETED; + switch (urb_desc->status) { + case 0: + break; + case -ENOENT: /* cancelled */ + case -ECONNRESET: + break; + case -ENODEV: + case -ESHUTDOWN: + usbi_dbg("device removed"); + lib_desc->status = LIBUSB_TRANSFER_NO_DEVICE; + break; + case -EPIPE: + usbi_dbg("detected endpoint stall"); + lib_desc->status = LIBUSB_TRANSFER_STALL; + break; + case -EOVERFLOW: + usbi_dbg("overflow error"); + lib_desc->status = LIBUSB_TRANSFER_OVERFLOW; + break; + case -ETIME: + case -EPROTO: + case -EILSEQ: + case -ECOMM: + case -ENOSR: + case -EXDEV: + usbi_dbg("low-level USB error %d", urb_desc->status); + lib_desc->status = LIBUSB_TRANSFER_ERROR; + break; + default: + usbi_warn(TRANSFER_CTX(transfer), + "unrecognised urb status %d", urb_desc->status); + lib_desc->status = LIBUSB_TRANSFER_ERROR; + break; + } + lib_desc->actual_length = urb_desc->actual_length; + } + + tpriv->num_retired++; + + if (tpriv->reap_action != NORMAL) { /* cancelled or submit_fail */ + usbi_dbg("CANCEL: urb status %d", urb->status); + + if (tpriv->num_retired == num_urbs) { + usbi_dbg("CANCEL: last URB handled, reporting"); + free_iso_urbs(tpriv); + if (tpriv->reap_action == CANCELLED) { + usbi_mutex_unlock(&itransfer->lock); + return usbi_handle_transfer_cancellation(itransfer); + } else { + usbi_mutex_unlock(&itransfer->lock); + return usbi_handle_transfer_completion(itransfer, + LIBUSB_TRANSFER_ERROR); + } + } + goto out; + } + + switch (urb->status) { + case 0: + break; + case -ENOENT: /* cancelled */ + case -ECONNRESET: + break; + case -ESHUTDOWN: + usbi_dbg("device removed"); + status = LIBUSB_TRANSFER_NO_DEVICE; + break; + default: + usbi_warn(TRANSFER_CTX(transfer), + "unrecognised urb status %d", urb->status); + status = LIBUSB_TRANSFER_ERROR; + break; + } + + /* if we're the last urb then we're done */ + if (urb_idx == num_urbs) { + usbi_dbg("last URB in transfer --> complete!"); + free_iso_urbs(tpriv); + usbi_mutex_unlock(&itransfer->lock); + return usbi_handle_transfer_completion(itransfer, status); + } + +out: + usbi_mutex_unlock(&itransfer->lock); + return 0; +} + +static int handle_control_completion(struct usbi_transfer *itransfer, + struct usbfs_urb *urb) +{ + struct linux_transfer_priv *tpriv = usbi_transfer_get_os_priv(itransfer); + int status; + + usbi_mutex_lock(&itransfer->lock); + usbi_dbg("handling completion status %d", urb->status); + + itransfer->transferred += urb->actual_length; + + if (tpriv->reap_action == CANCELLED) { + if (urb->status != 0 && urb->status != -ENOENT) + usbi_warn(ITRANSFER_CTX(itransfer), + "cancel: unrecognised urb status %d", urb->status); + free(tpriv->urbs); + tpriv->urbs = NULL; + usbi_mutex_unlock(&itransfer->lock); + return usbi_handle_transfer_cancellation(itransfer); + } + + switch (urb->status) { + case 0: + status = LIBUSB_TRANSFER_COMPLETED; + break; + case -ENOENT: /* cancelled */ + status = LIBUSB_TRANSFER_CANCELLED; + break; + case -ENODEV: + case -ESHUTDOWN: + usbi_dbg("device removed"); + status = LIBUSB_TRANSFER_NO_DEVICE; + break; + case -EPIPE: + usbi_dbg("unsupported control request"); + status = LIBUSB_TRANSFER_STALL; + break; + case -EOVERFLOW: + usbi_dbg("control overflow error"); + status = LIBUSB_TRANSFER_OVERFLOW; + break; + case -ETIME: + case -EPROTO: + case -EILSEQ: + case -ECOMM: + case -ENOSR: + usbi_dbg("low-level bus error occurred"); + status = LIBUSB_TRANSFER_ERROR; + break; + default: + usbi_warn(ITRANSFER_CTX(itransfer), + "unrecognised urb status %d", urb->status); + status = LIBUSB_TRANSFER_ERROR; + break; + } + + free(tpriv->urbs); + tpriv->urbs = NULL; + usbi_mutex_unlock(&itransfer->lock); + return usbi_handle_transfer_completion(itransfer, status); +} + +static int reap_for_handle(struct libusb_device_handle *handle) +{ + struct linux_device_handle_priv *hpriv = _device_handle_priv(handle); + int r; + struct usbfs_urb *urb; + struct usbi_transfer *itransfer; + struct libusb_transfer *transfer; + + r = ioctl(hpriv->fd, IOCTL_USBFS_REAPURBNDELAY, &urb); + if (r == -1 && errno == EAGAIN) + return 1; + if (r < 0) { + if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_err(HANDLE_CTX(handle), "reap failed error %d errno=%d", + r, errno); + return LIBUSB_ERROR_IO; + } + + itransfer = urb->usercontext; + transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + + usbi_dbg("urb type=%d status=%d transferred=%d", urb->type, urb->status, + urb->actual_length); + + switch (transfer->type) { + case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS: + return handle_iso_completion(itransfer, urb); + case LIBUSB_TRANSFER_TYPE_BULK: + case LIBUSB_TRANSFER_TYPE_INTERRUPT: + return handle_bulk_completion(itransfer, urb); + case LIBUSB_TRANSFER_TYPE_CONTROL: + return handle_control_completion(itransfer, urb); + default: + usbi_err(HANDLE_CTX(handle), "unrecognised endpoint type %x", + transfer->type); + return LIBUSB_ERROR_OTHER; + } +} + +static int op_handle_events(struct libusb_context *ctx, + struct pollfd *fds, POLL_NFDS_TYPE nfds, int num_ready) +{ + int r; + unsigned int i = 0; + + usbi_mutex_lock(&ctx->open_devs_lock); + for (i = 0; i < nfds && num_ready > 0; i++) { + struct pollfd *pollfd = &fds[i]; + struct libusb_device_handle *handle; + struct linux_device_handle_priv *hpriv = NULL; + + if (!pollfd->revents) + continue; + + num_ready--; + list_for_each_entry(handle, &ctx->open_devs, list, struct libusb_device_handle) { + hpriv = _device_handle_priv(handle); + if (hpriv->fd == pollfd->fd) + break; + } + + if (pollfd->revents & POLLERR) { + usbi_remove_pollfd(HANDLE_CTX(handle), hpriv->fd); + usbi_handle_disconnect(handle); + continue; + } + + do { + r = reap_for_handle(handle); + } while (r == 0); + if (r == 1 || r == LIBUSB_ERROR_NO_DEVICE) + continue; + else if (r < 0) + goto out; + } + + r = 0; +out: + usbi_mutex_unlock(&ctx->open_devs_lock); + return r; +} + +static int op_clock_gettime(int clk_id, struct timespec *tp) +{ + switch (clk_id) { + case USBI_CLOCK_MONOTONIC: + return clock_gettime(monotonic_clkid, tp); + case USBI_CLOCK_REALTIME: + return clock_gettime(CLOCK_REALTIME, tp); + default: + return LIBUSB_ERROR_INVALID_PARAM; + } +} + +#ifdef USBI_TIMERFD_AVAILABLE +static clockid_t op_get_timerfd_clockid(void) +{ + return monotonic_clkid; + +} +#endif + +const struct usbi_os_backend linux_usbfs_backend = { + .name = "Linux usbfs", + .init = op_init, + .exit = op_exit, + .get_device_list = NULL, + .get_device_descriptor = op_get_device_descriptor, + .get_active_config_descriptor = op_get_active_config_descriptor, + .get_config_descriptor = op_get_config_descriptor, + + .open = op_open, + .close = op_close, + .get_configuration = op_get_configuration, + .set_configuration = op_set_configuration, + .claim_interface = op_claim_interface, + .release_interface = op_release_interface, + + .set_interface_altsetting = op_set_interface, + .clear_halt = op_clear_halt, + .reset_device = op_reset_device, + + .kernel_driver_active = op_kernel_driver_active, + .detach_kernel_driver = op_detach_kernel_driver, + .attach_kernel_driver = op_attach_kernel_driver, + + .destroy_device = op_destroy_device, + + .submit_transfer = op_submit_transfer, + .cancel_transfer = op_cancel_transfer, + .clear_transfer_priv = op_clear_transfer_priv, + + .handle_events = op_handle_events, + + .clock_gettime = op_clock_gettime, + +#ifdef USBI_TIMERFD_AVAILABLE + .get_timerfd_clockid = op_get_timerfd_clockid, +#endif + + .device_priv_size = sizeof(struct linux_device_priv), + .device_handle_priv_size = sizeof(struct linux_device_handle_priv), + .transfer_priv_size = sizeof(struct linux_transfer_priv), + .add_iso_packet_size = 0, +}; + diff --git a/compat/libusb-1.0/libusb/os/linux_usbfs.h b/compat/libusb-1.0/libusb/os/linux_usbfs.h new file mode 100644 index 0000000..661a9c3 --- /dev/null +++ b/compat/libusb-1.0/libusb/os/linux_usbfs.h @@ -0,0 +1,168 @@ +/* + * usbfs header structures + * Copyright (C) 2007 Daniel Drake + * Copyright (c) 2001 Johannes Erdfelt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef LIBUSB_USBFS_H +#define LIBUSB_USBFS_H + +#include + +#define SYSFS_DEVICE_PATH "/sys/bus/usb/devices" + +struct usbfs_ctrltransfer { + /* keep in sync with usbdevice_fs.h:usbdevfs_ctrltransfer */ + uint8_t bmRequestType; + uint8_t bRequest; + uint16_t wValue; + uint16_t wIndex; + uint16_t wLength; + + uint32_t timeout; /* in milliseconds */ + + /* pointer to data */ + void *data; +}; + +struct usbfs_bulktransfer { + /* keep in sync with usbdevice_fs.h:usbdevfs_bulktransfer */ + unsigned int ep; + unsigned int len; + unsigned int timeout; /* in milliseconds */ + + /* pointer to data */ + void *data; +}; + +struct usbfs_setinterface { + /* keep in sync with usbdevice_fs.h:usbdevfs_setinterface */ + unsigned int interface; + unsigned int altsetting; +}; + +#define USBFS_MAXDRIVERNAME 255 + +struct usbfs_getdriver { + unsigned int interface; + char driver[USBFS_MAXDRIVERNAME + 1]; +}; + +#define USBFS_URB_SHORT_NOT_OK 0x01 +#define USBFS_URB_ISO_ASAP 0x02 +#define USBFS_URB_BULK_CONTINUATION 0x04 +#define USBFS_URB_QUEUE_BULK 0x10 +#define USBFS_URB_ZERO_PACKET 0x40 + +enum usbfs_urb_type { + USBFS_URB_TYPE_ISO = 0, + USBFS_URB_TYPE_INTERRUPT = 1, + USBFS_URB_TYPE_CONTROL = 2, + USBFS_URB_TYPE_BULK = 3, +}; + +struct usbfs_iso_packet_desc { + unsigned int length; + unsigned int actual_length; + unsigned int status; +}; + +#define MAX_ISO_BUFFER_LENGTH 32768 +#define MAX_BULK_BUFFER_LENGTH 16384 +#define MAX_CTRL_BUFFER_LENGTH 4096 + +struct usbfs_urb { + unsigned char type; + unsigned char endpoint; + int status; + unsigned int flags; + void *buffer; + int buffer_length; + int actual_length; + int start_frame; + int number_of_packets; + int error_count; + unsigned int signr; + void *usercontext; + struct usbfs_iso_packet_desc iso_frame_desc[0]; +}; + +struct usbfs_connectinfo { + unsigned int devnum; + unsigned char slow; +}; + +struct usbfs_ioctl { + int ifno; /* interface 0..N ; negative numbers reserved */ + int ioctl_code; /* MUST encode size + direction of data so the + * macros in give correct values */ + void *data; /* param buffer (in, or out) */ +}; + +struct usbfs_hub_portinfo { + unsigned char numports; + unsigned char port[127]; /* port to device num mapping */ +}; + +#define USBFS_CAP_ZERO_PACKET 0x01 +#define USBFS_CAP_BULK_CONTINUATION 0x02 +#define USBFS_CAP_NO_PACKET_SIZE_LIM 0x04 +#define USBFS_CAP_BULK_SCATTER_GATHER 0x08 + +#define IOCTL_USBFS_CONTROL _IOWR('U', 0, struct usbfs_ctrltransfer) +#define IOCTL_USBFS_BULK _IOWR('U', 2, struct usbfs_bulktransfer) +#define IOCTL_USBFS_RESETEP _IOR('U', 3, unsigned int) +#define IOCTL_USBFS_SETINTF _IOR('U', 4, struct usbfs_setinterface) +#define IOCTL_USBFS_SETCONFIG _IOR('U', 5, unsigned int) +#define IOCTL_USBFS_GETDRIVER _IOW('U', 8, struct usbfs_getdriver) +#define IOCTL_USBFS_SUBMITURB _IOR('U', 10, struct usbfs_urb) +#define IOCTL_USBFS_DISCARDURB _IO('U', 11) +#define IOCTL_USBFS_REAPURB _IOW('U', 12, void *) +#define IOCTL_USBFS_REAPURBNDELAY _IOW('U', 13, void *) +#define IOCTL_USBFS_CLAIMINTF _IOR('U', 15, unsigned int) +#define IOCTL_USBFS_RELEASEINTF _IOR('U', 16, unsigned int) +#define IOCTL_USBFS_CONNECTINFO _IOW('U', 17, struct usbfs_connectinfo) +#define IOCTL_USBFS_IOCTL _IOWR('U', 18, struct usbfs_ioctl) +#define IOCTL_USBFS_HUB_PORTINFO _IOR('U', 19, struct usbfs_hub_portinfo) +#define IOCTL_USBFS_RESET _IO('U', 20) +#define IOCTL_USBFS_CLEAR_HALT _IOR('U', 21, unsigned int) +#define IOCTL_USBFS_DISCONNECT _IO('U', 22) +#define IOCTL_USBFS_CONNECT _IO('U', 23) +#define IOCTL_USBFS_CLAIM_PORT _IOR('U', 24, unsigned int) +#define IOCTL_USBFS_RELEASE_PORT _IOR('U', 25, unsigned int) +#define IOCTL_USBFS_GET_CAPABILITIES _IOR('U', 26, __u32) + +#if defined(HAVE_LIBUDEV) +int linux_udev_start_event_monitor(void); +int linux_udev_stop_event_monitor(void); +int linux_udev_scan_devices(struct libusb_context *ctx); +#else +int linux_netlink_start_event_monitor(void); +int linux_netlink_stop_event_monitor(void); +#endif + +void linux_hotplug_enumerate(uint8_t busnum, uint8_t devaddr, const char *sys_name); +void linux_hotplug_disconnected(uint8_t busnum, uint8_t devaddr, const char *sys_name); + +int linux_get_device_address (struct libusb_context *ctx, int detached, + uint8_t *busnum, uint8_t *devaddr, + const char *dev_node, const char *sys_name); +int linux_enumerate_device(struct libusb_context *ctx, + uint8_t busnum, uint8_t devaddr, + const char *sysfs_dir); + +#endif diff --git a/compat/libusb-1.0/libusb/os/openbsd_usb.c b/compat/libusb-1.0/libusb/os/openbsd_usb.c new file mode 100644 index 0000000..e31941b --- /dev/null +++ b/compat/libusb-1.0/libusb/os/openbsd_usb.c @@ -0,0 +1,727 @@ +/* + * Copyright (c) 2011 Martin Pieuchot + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "libusb.h" +#include "libusbi.h" + +struct device_priv { + char devnode[16]; + int fd; + + unsigned char *cdesc; /* active config descriptor */ + usb_device_descriptor_t ddesc; /* usb device descriptor */ +}; + +struct handle_priv { + int pipe[2]; /* for event notification */ + int endpoints[USB_MAX_ENDPOINTS]; +}; + +/* + * Backend functions + */ +static int obsd_get_device_list(struct libusb_context *, + struct discovered_devs **); +static int obsd_open(struct libusb_device_handle *); +static void obsd_close(struct libusb_device_handle *); + +static int obsd_get_device_descriptor(struct libusb_device *, unsigned char *, + int *); +static int obsd_get_active_config_descriptor(struct libusb_device *, + unsigned char *, size_t, int *); +static int obsd_get_config_descriptor(struct libusb_device *, uint8_t, + unsigned char *, size_t, int *); + +static int obsd_get_configuration(struct libusb_device_handle *, int *); +static int obsd_set_configuration(struct libusb_device_handle *, int); + +static int obsd_claim_interface(struct libusb_device_handle *, int); +static int obsd_release_interface(struct libusb_device_handle *, int); + +static int obsd_set_interface_altsetting(struct libusb_device_handle *, int, + int); +static int obsd_clear_halt(struct libusb_device_handle *, unsigned char); +static int obsd_reset_device(struct libusb_device_handle *); +static void obsd_destroy_device(struct libusb_device *); + +static int obsd_submit_transfer(struct usbi_transfer *); +static int obsd_cancel_transfer(struct usbi_transfer *); +static void obsd_clear_transfer_priv(struct usbi_transfer *); +static int obsd_handle_events(struct libusb_context *ctx, struct pollfd *, + nfds_t, int); +static int obsd_clock_gettime(int, struct timespec *); + +/* + * Private functions + */ +static int _errno_to_libusb(int); +static int _cache_active_config_descriptor(struct libusb_device *, int); +static int _sync_control_transfer(struct usbi_transfer *); +static int _sync_gen_transfer(struct usbi_transfer *); +static int _access_endpoint(struct libusb_transfer *); + +const struct usbi_os_backend openbsd_backend = { + "Synchronous OpenBSD backend", + NULL, /* init() */ + NULL, /* exit() */ + obsd_get_device_list, + obsd_open, + obsd_close, + + obsd_get_device_descriptor, + obsd_get_active_config_descriptor, + obsd_get_config_descriptor, + + obsd_get_configuration, + obsd_set_configuration, + + obsd_claim_interface, + obsd_release_interface, + + obsd_set_interface_altsetting, + obsd_clear_halt, + obsd_reset_device, + + NULL, /* kernel_driver_active() */ + NULL, /* detach_kernel_driver() */ + NULL, /* attach_kernel_driver() */ + + obsd_destroy_device, + + obsd_submit_transfer, + obsd_cancel_transfer, + obsd_clear_transfer_priv, + + obsd_handle_events, + + obsd_clock_gettime, + sizeof(struct device_priv), + sizeof(struct handle_priv), + 0, /* transfer_priv_size */ + 0, /* add_iso_packet_size */ +}; + +int +obsd_get_device_list(struct libusb_context * ctx, + struct discovered_devs **discdevs) +{ + struct libusb_device *dev; + struct device_priv *dpriv; + struct usb_device_info di; + unsigned long session_id; + char devnode[16]; + int fd, err, i; + + usbi_dbg(""); + + /* Only ugen(4) is supported */ + for (i = 0; i < USB_MAX_DEVICES; i++) { + /* Control endpoint is always .00 */ + snprintf(devnode, sizeof(devnode), "/dev/ugen%d.00", i); + + if ((fd = open(devnode, O_RDONLY)) < 0) { + if (errno != ENOENT && errno != ENXIO) + usbi_err(ctx, "could not open %s", devnode); + continue; + } + + if (ioctl(fd, USB_GET_DEVICEINFO, &di) < 0) + continue; + + session_id = (di.udi_bus << 8 | di.udi_addr); + dev = usbi_get_device_by_session_id(ctx, session_id); + + if (dev == NULL) { + dev = usbi_alloc_device(ctx, session_id); + if (dev == NULL) + return (LIBUSB_ERROR_NO_MEM); + + dev->bus_number = di.udi_bus; + dev->device_address = di.udi_addr; + dev->speed = di.udi_speed; + + dpriv = (struct device_priv *)dev->os_priv; + strlcpy(dpriv->devnode, devnode, sizeof(devnode)); + dpriv->fd = -1; + + if (ioctl(fd, USB_GET_DEVICE_DESC, &dpriv->ddesc) < 0) { + err = errno; + goto error; + } + + dpriv->cdesc = NULL; + if (_cache_active_config_descriptor(dev, fd)) { + err = errno; + goto error; + } + + if ((err = usbi_sanitize_device(dev))) + goto error; + } + close(fd); + + if (discovered_devs_append(*discdevs, dev) == NULL) + return (LIBUSB_ERROR_NO_MEM); + } + + return (LIBUSB_SUCCESS); + +error: + close(fd); + libusb_unref_device(dev); + return _errno_to_libusb(err); +} + +int +obsd_open(struct libusb_device_handle *handle) +{ + struct handle_priv *hpriv = (struct handle_priv *)handle->os_priv; + struct device_priv *dpriv = (struct device_priv *)handle->dev->os_priv; + + dpriv->fd = open(dpriv->devnode, O_RDWR); + if (dpriv->fd < 0) { + dpriv->fd = open(dpriv->devnode, O_RDONLY); + if (dpriv->fd < 0) + return _errno_to_libusb(errno); + } + + usbi_dbg("open %s: fd %d", dpriv->devnode, dpriv->fd); + + if (pipe(hpriv->pipe) < 0) + return _errno_to_libusb(errno); + + return usbi_add_pollfd(HANDLE_CTX(handle), hpriv->pipe[0], POLLIN); +} + +void +obsd_close(struct libusb_device_handle *handle) +{ + struct handle_priv *hpriv = (struct handle_priv *)handle->os_priv; + struct device_priv *dpriv = (struct device_priv *)handle->dev->os_priv; + + usbi_dbg("close: fd %d", dpriv->fd); + + close(dpriv->fd); + dpriv->fd = -1; + + usbi_remove_pollfd(HANDLE_CTX(handle), hpriv->pipe[0]); + + close(hpriv->pipe[0]); + close(hpriv->pipe[1]); +} + +int +obsd_get_device_descriptor(struct libusb_device *dev, unsigned char *buf, + int *host_endian) +{ + struct device_priv *dpriv = (struct device_priv *)dev->os_priv; + + usbi_dbg(""); + + memcpy(buf, &dpriv->ddesc, DEVICE_DESC_LENGTH); + + *host_endian = 0; + + return (LIBUSB_SUCCESS); +} + +int +obsd_get_active_config_descriptor(struct libusb_device *dev, + unsigned char *buf, size_t len, int *host_endian) +{ + struct device_priv *dpriv = (struct device_priv *)dev->os_priv; + usb_config_descriptor_t *ucd; + + ucd = (usb_config_descriptor_t *) dpriv->cdesc; + len = MIN(len, UGETW(ucd->wTotalLength)); + + usbi_dbg("len %d", len); + + memcpy(buf, dpriv->cdesc, len); + + *host_endian = 0; + + return (LIBUSB_SUCCESS); +} + +int +obsd_get_config_descriptor(struct libusb_device *dev, uint8_t idx, + unsigned char *buf, size_t len, int *host_endian) +{ + struct device_priv *dpriv = (struct device_priv *)dev->os_priv; + struct usb_full_desc ufd; + int fd, err; + + usbi_dbg("index %d, len %d", idx, len); + + /* A config descriptor may be requested before opening the device */ + if (dpriv->fd >= 0) { + fd = dpriv->fd; + } else { + fd = open(dpriv->devnode, O_RDONLY); + if (fd < 0) + return _errno_to_libusb(errno); + } + + ufd.ufd_config_index = idx; + ufd.ufd_size = len; + ufd.ufd_data = buf; + + if ((ioctl(fd, USB_GET_FULL_DESC, &ufd)) < 0) { + err = errno; + if (dpriv->fd < 0) + close(fd); + return _errno_to_libusb(err); + } + + if (dpriv->fd < 0) + close(fd); + + *host_endian = 0; + + return (LIBUSB_SUCCESS); +} + +int +obsd_get_configuration(struct libusb_device_handle *handle, int *config) +{ + struct device_priv *dpriv = (struct device_priv *)handle->dev->os_priv; + + usbi_dbg(""); + + if (ioctl(dpriv->fd, USB_GET_CONFIG, config) < 0) + return _errno_to_libusb(errno); + + usbi_dbg("configuration %d", *config); + + return (LIBUSB_SUCCESS); +} + +int +obsd_set_configuration(struct libusb_device_handle *handle, int config) +{ + struct device_priv *dpriv = (struct device_priv *)handle->dev->os_priv; + + usbi_dbg("configuration %d", config); + + if (ioctl(dpriv->fd, USB_SET_CONFIG, &config) < 0) + return _errno_to_libusb(errno); + + return _cache_active_config_descriptor(handle->dev, dpriv->fd); +} + +int +obsd_claim_interface(struct libusb_device_handle *handle, int iface) +{ + struct handle_priv *hpriv = (struct handle_priv *)handle->os_priv; + int i; + + for (i = 0; i < USB_MAX_ENDPOINTS; i++) + hpriv->endpoints[i] = -1; + + return (LIBUSB_SUCCESS); +} + +int +obsd_release_interface(struct libusb_device_handle *handle, int iface) +{ + struct handle_priv *hpriv = (struct handle_priv *)handle->os_priv; + int i; + + for (i = 0; i < USB_MAX_ENDPOINTS; i++) + if (hpriv->endpoints[i] >= 0) + close(hpriv->endpoints[i]); + + return (LIBUSB_SUCCESS); +} + +int +obsd_set_interface_altsetting(struct libusb_device_handle *handle, int iface, + int altsetting) +{ + struct device_priv *dpriv = (struct device_priv *)handle->dev->os_priv; + struct usb_alt_interface intf; + + usbi_dbg("iface %d, setting %d", iface, altsetting); + + memset(&intf, 0, sizeof(intf)); + + intf.uai_interface_index = iface; + intf.uai_alt_no = altsetting; + + if (ioctl(dpriv->fd, USB_SET_ALTINTERFACE, &intf) < 0) + return _errno_to_libusb(errno); + + return (LIBUSB_SUCCESS); +} + +int +obsd_clear_halt(struct libusb_device_handle *handle, unsigned char endpoint) +{ + struct device_priv *dpriv = (struct device_priv *)handle->dev->os_priv; + struct usb_ctl_request req; + + usbi_dbg(""); + + req.ucr_request.bmRequestType = UT_WRITE_ENDPOINT; + req.ucr_request.bRequest = UR_CLEAR_FEATURE; + USETW(req.ucr_request.wValue, UF_ENDPOINT_HALT); + USETW(req.ucr_request.wIndex, endpoint); + USETW(req.ucr_request.wLength, 0); + + if (ioctl(dpriv->fd, USB_DO_REQUEST, &req) < 0) + return _errno_to_libusb(errno); + + return (LIBUSB_SUCCESS); +} + +int +obsd_reset_device(struct libusb_device_handle *handle) +{ + usbi_dbg(""); + + return (LIBUSB_ERROR_NOT_SUPPORTED); +} + +void +obsd_destroy_device(struct libusb_device *dev) +{ + struct device_priv *dpriv = (struct device_priv *)dev->os_priv; + + usbi_dbg(""); + + free(dpriv->cdesc); +} + +int +obsd_submit_transfer(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer; + struct handle_priv *hpriv; + int err = 0; + + usbi_dbg(""); + + transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + hpriv = (struct handle_priv *)transfer->dev_handle->os_priv; + + switch (transfer->type) { + case LIBUSB_TRANSFER_TYPE_CONTROL: + err = _sync_control_transfer(itransfer); + break; + case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS: + if (IS_XFEROUT(transfer)) { + /* Isochronous write is not supported */ + err = LIBUSB_ERROR_NOT_SUPPORTED; + break; + } + err = _sync_gen_transfer(itransfer); + break; + case LIBUSB_TRANSFER_TYPE_BULK: + case LIBUSB_TRANSFER_TYPE_INTERRUPT: + if (IS_XFEROUT(transfer) && + transfer->flags & LIBUSB_TRANSFER_ADD_ZERO_PACKET) { + err = LIBUSB_ERROR_NOT_SUPPORTED; + break; + } + err = _sync_gen_transfer(itransfer); + break; + } + + if (err) + return (err); + + if (write(hpriv->pipe[1], &itransfer, sizeof(itransfer)) < 0) + return _errno_to_libusb(errno); + + return (LIBUSB_SUCCESS); +} + +int +obsd_cancel_transfer(struct usbi_transfer *itransfer) +{ + usbi_dbg(""); + + return (LIBUSB_ERROR_NOT_SUPPORTED); +} + +void +obsd_clear_transfer_priv(struct usbi_transfer *itransfer) +{ + usbi_dbg(""); + + /* Nothing to do */ +} + +int +obsd_handle_events(struct libusb_context *ctx, struct pollfd *fds, nfds_t nfds, + int num_ready) +{ + struct libusb_device_handle *handle; + struct handle_priv *hpriv = NULL; + struct usbi_transfer *itransfer; + struct pollfd *pollfd; + int i, err = 0; + + usbi_dbg(""); + + pthread_mutex_lock(&ctx->open_devs_lock); + for (i = 0; i < nfds && num_ready > 0; i++) { + pollfd = &fds[i]; + + if (!pollfd->revents) + continue; + + hpriv = NULL; + num_ready--; + list_for_each_entry(handle, &ctx->open_devs, list, + struct libusb_device_handle) { + hpriv = (struct handle_priv *)handle->os_priv; + + if (hpriv->pipe[0] == pollfd->fd) + break; + + hpriv = NULL; + } + + if (NULL == hpriv) { + usbi_dbg("fd %d is not an event pipe!", pollfd->fd); + err = ENOENT; + break; + } + + if (pollfd->revents & POLLERR) { + usbi_remove_pollfd(HANDLE_CTX(handle), hpriv->pipe[0]); + usbi_handle_disconnect(handle); + continue; + } + + if (read(hpriv->pipe[0], &itransfer, sizeof(itransfer)) < 0) { + err = errno; + break; + } + + if ((err = usbi_handle_transfer_completion(itransfer, + LIBUSB_TRANSFER_COMPLETED))) + break; + } + pthread_mutex_unlock(&ctx->open_devs_lock); + + if (err) + return _errno_to_libusb(err); + + return (LIBUSB_SUCCESS); +} + +int +obsd_clock_gettime(int clkid, struct timespec *tp) +{ + usbi_dbg("clock %d", clkid); + + if (clkid == USBI_CLOCK_REALTIME) + return clock_gettime(CLOCK_REALTIME, tp); + + if (clkid == USBI_CLOCK_MONOTONIC) + return clock_gettime(CLOCK_MONOTONIC, tp); + + return (LIBUSB_ERROR_INVALID_PARAM); +} + +int +_errno_to_libusb(int err) +{ + switch (err) { + case EIO: + return (LIBUSB_ERROR_IO); + case EACCES: + return (LIBUSB_ERROR_ACCESS); + case ENOENT: + return (LIBUSB_ERROR_NO_DEVICE); + case ENOMEM: + return (LIBUSB_ERROR_NO_MEM); + } + + usbi_dbg("error: %s", strerror(err)); + + return (LIBUSB_ERROR_OTHER); +} + +int +_cache_active_config_descriptor(struct libusb_device *dev, int fd) +{ + struct device_priv *dpriv = (struct device_priv *)dev->os_priv; + struct usb_config_desc ucd; + struct usb_full_desc ufd; + unsigned char* buf; + int len; + + usbi_dbg("fd %d", fd); + + ucd.ucd_config_index = USB_CURRENT_CONFIG_INDEX; + + if ((ioctl(fd, USB_GET_CONFIG_DESC, &ucd)) < 0) + return _errno_to_libusb(errno); + + usbi_dbg("active bLength %d", ucd.ucd_desc.bLength); + + len = UGETW(ucd.ucd_desc.wTotalLength); + buf = malloc(len); + if (buf == NULL) + return (LIBUSB_ERROR_NO_MEM); + + ufd.ufd_config_index = ucd.ucd_config_index; + ufd.ufd_size = len; + ufd.ufd_data = buf; + + usbi_dbg("index %d, len %d", ufd.ufd_config_index, len); + + if ((ioctl(fd, USB_GET_FULL_DESC, &ufd)) < 0) { + free(buf); + return _errno_to_libusb(errno); + } + + if (dpriv->cdesc) + free(dpriv->cdesc); + dpriv->cdesc = buf; + + return (0); +} + +int +_sync_control_transfer(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer; + struct libusb_control_setup *setup; + struct device_priv *dpriv; + struct usb_ctl_request req; + + transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + dpriv = (struct device_priv *)transfer->dev_handle->dev->os_priv; + setup = (struct libusb_control_setup *)transfer->buffer; + + usbi_dbg("type %d request %d value %d index %d length %d timeout %d", + setup->bmRequestType, setup->bRequest, + libusb_le16_to_cpu(setup->wValue), + libusb_le16_to_cpu(setup->wIndex), + libusb_le16_to_cpu(setup->wLength), transfer->timeout); + + req.ucr_request.bmRequestType = setup->bmRequestType; + req.ucr_request.bRequest = setup->bRequest; + /* Don't use USETW, libusb already deals with the endianness */ + (*(uint16_t *)req.ucr_request.wValue) = setup->wValue; + (*(uint16_t *)req.ucr_request.wIndex) = setup->wIndex; + (*(uint16_t *)req.ucr_request.wLength) = setup->wLength; + req.ucr_data = transfer->buffer + LIBUSB_CONTROL_SETUP_SIZE; + + if ((transfer->flags & LIBUSB_TRANSFER_SHORT_NOT_OK) == 0) + req.ucr_flags = USBD_SHORT_XFER_OK; + + if ((ioctl(dpriv->fd, USB_SET_TIMEOUT, &transfer->timeout)) < 0) + return _errno_to_libusb(errno); + + if ((ioctl(dpriv->fd, USB_DO_REQUEST, &req)) < 0) + return _errno_to_libusb(errno); + + itransfer->transferred = req.ucr_actlen; + + usbi_dbg("transferred %d", itransfer->transferred); + + return (0); +} + +int +_access_endpoint(struct libusb_transfer *transfer) +{ + struct handle_priv *hpriv; + struct device_priv *dpriv; + char *s, devnode[16]; + int fd, endpt; + mode_t mode; + + hpriv = (struct handle_priv *)transfer->dev_handle->os_priv; + dpriv = (struct device_priv *)transfer->dev_handle->dev->os_priv; + + endpt = UE_GET_ADDR(transfer->endpoint); + mode = IS_XFERIN(transfer) ? O_RDONLY : O_WRONLY; + + usbi_dbg("endpoint %d mode %d", endpt, mode); + + if (hpriv->endpoints[endpt] < 0) { + /* Pick the right node given the control one */ + strlcpy(devnode, dpriv->devnode, sizeof(devnode)); + s = strchr(devnode, '.'); + snprintf(s, 4, ".%02d", endpt); + + /* We may need to read/write to the same endpoint later. */ + if (((fd = open(devnode, O_RDWR)) < 0) && (errno == ENXIO)) + if ((fd = open(devnode, mode)) < 0) + return (-1); + + hpriv->endpoints[endpt] = fd; + } + + return (hpriv->endpoints[endpt]); +} + +int +_sync_gen_transfer(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer; + int fd, nr = 1; + + transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + + /* + * Bulk, Interrupt or Isochronous transfer depends on the + * endpoint and thus the node to open. + */ + if ((fd = _access_endpoint(transfer)) < 0) + return _errno_to_libusb(errno); + + if ((ioctl(fd, USB_SET_TIMEOUT, &transfer->timeout)) < 0) + return _errno_to_libusb(errno); + + if (IS_XFERIN(transfer)) { + if ((transfer->flags & LIBUSB_TRANSFER_SHORT_NOT_OK) == 0) + if ((ioctl(fd, USB_SET_SHORT_XFER, &nr)) < 0) + return _errno_to_libusb(errno); + + nr = read(fd, transfer->buffer, transfer->length); + } else { + nr = write(fd, transfer->buffer, transfer->length); + } + + if (nr < 0) + return _errno_to_libusb(errno); + + itransfer->transferred = nr; + + return (0); +} diff --git a/compat/libusb-1.0/libusb/os/poll_posix.h b/compat/libusb-1.0/libusb/os/poll_posix.h new file mode 100644 index 0000000..0e5e7f5 --- /dev/null +++ b/compat/libusb-1.0/libusb/os/poll_posix.h @@ -0,0 +1,10 @@ +#ifndef LIBUSB_POLL_POSIX_H +#define LIBUSB_POLL_POSIX_H + +#define usbi_write write +#define usbi_read read +#define usbi_close close +#define usbi_pipe pipe +#define usbi_poll poll + +#endif /* LIBUSB_POLL_POSIX_H */ diff --git a/compat/libusb-1.0/libusb/os/poll_windows.c b/compat/libusb-1.0/libusb/os/poll_windows.c new file mode 100644 index 0000000..7f4d9c4 --- /dev/null +++ b/compat/libusb-1.0/libusb/os/poll_windows.c @@ -0,0 +1,745 @@ +/* + * poll_windows: poll compatibility wrapper for Windows + * Copyright (C) 2009-2010 Pete Batard + * With contributions from Michael Plante, Orin Eman et al. + * Parts of poll implementation from libusb-win32, by Stephan Meyer et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +/* + * poll() and pipe() Windows compatibility layer for libusb 1.0 + * + * The way this layer works is by using OVERLAPPED with async I/O transfers, as + * OVERLAPPED have an associated event which is flagged for I/O completion. + * + * For USB pollable async I/O, you would typically: + * - obtain a Windows HANDLE to a file or device that has been opened in + * OVERLAPPED mode + * - call usbi_create_fd with this handle to obtain a custom fd. + * Note that if you need simultaneous R/W access, you need to call create_fd + * twice, once in _O_RDONLY and once in _O_WRONLY mode to obtain 2 separate + * pollable fds + * - leave the core functions call the poll routine and flag POLLIN/POLLOUT + * + * The pipe pollable synchronous I/O works using the overlapped event associated + * with a fake pipe. The read/write functions are only meant to be used in that + * context. + */ +#include +#include +#include +#include +#include + +#include + +// Uncomment to debug the polling layer +//#define DEBUG_POLL_WINDOWS +#if defined(DEBUG_POLL_WINDOWS) +#define poll_dbg usbi_dbg +#else +// MSVC++ < 2005 cannot use a variadic argument and non MSVC +// compilers produce warnings if parenthesis are omitted. +#if defined(_MSC_VER) && _MSC_VER < 1400 +#define poll_dbg +#else +#define poll_dbg(...) +#endif +#endif + +#if defined(_PREFAST_) +#pragma warning(disable:28719) +#endif + +#if defined(__CYGWIN__) +// cygwin produces a warning unless these prototypes are defined +extern int _open(char* name, int flags); +extern int _close(int fd); +extern int _snprintf(char *buffer, size_t count, const char *format, ...); +#define NUL_DEVICE "/dev/null" +#else +#define NUL_DEVICE "NUL" +#endif + +#define CHECK_INIT_POLLING do {if(!is_polling_set) init_polling();} while(0) + +// public fd data +const struct winfd INVALID_WINFD = {-1, INVALID_HANDLE_VALUE, NULL, RW_NONE}; +struct winfd poll_fd[MAX_FDS]; +// internal fd data +struct { + CRITICAL_SECTION mutex; // lock for fds + // Additional variables for XP CancelIoEx partial emulation + HANDLE original_handle; + DWORD thread_id; +} _poll_fd[MAX_FDS]; + +// globals +BOOLEAN is_polling_set = FALSE; +LONG pipe_number = 0; +static volatile LONG compat_spinlock = 0; + +// CancelIoEx, available on Vista and later only, provides the ability to cancel +// a single transfer (OVERLAPPED) when used. As it may not be part of any of the +// platform headers, we hook into the Kernel32 system DLL directly to seek it. +static BOOL (__stdcall *pCancelIoEx)(HANDLE, LPOVERLAPPED) = NULL; +#define CancelIoEx_Available (pCancelIoEx != NULL) +static __inline BOOL cancel_io(int _index) +{ + if ((_index < 0) || (_index >= MAX_FDS)) { + return FALSE; + } + + if ( (poll_fd[_index].fd < 0) || (poll_fd[_index].handle == INVALID_HANDLE_VALUE) + || (poll_fd[_index].handle == 0) || (poll_fd[_index].overlapped == NULL) ) { + return TRUE; + } + if (CancelIoEx_Available) { + return (*pCancelIoEx)(poll_fd[_index].handle, poll_fd[_index].overlapped); + } + if (_poll_fd[_index].thread_id == GetCurrentThreadId()) { + return CancelIo(poll_fd[_index].handle); + } + usbi_warn(NULL, "Unable to cancel I/O that was started from another thread"); + return FALSE; +} + +// Init +void init_polling(void) +{ + int i; + + while (InterlockedExchange((LONG *)&compat_spinlock, 1) == 1) { + SleepEx(0, TRUE); + } + if (!is_polling_set) { + pCancelIoEx = (BOOL (__stdcall *)(HANDLE,LPOVERLAPPED)) + GetProcAddress(GetModuleHandleA("KERNEL32"), "CancelIoEx"); + usbi_dbg("Will use CancelIo%s for I/O cancellation", + CancelIoEx_Available?"Ex":""); + for (i=0; ihEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if(overlapped->hEvent == NULL) { + free (overlapped); + return NULL; + } + return overlapped; +} + +void free_overlapped(OVERLAPPED *overlapped) +{ + if (overlapped == NULL) + return; + + if ( (overlapped->hEvent != 0) + && (overlapped->hEvent != INVALID_HANDLE_VALUE) ) { + CloseHandle(overlapped->hEvent); + } + free(overlapped); +} + +void reset_overlapped(OVERLAPPED *overlapped) +{ + HANDLE event_handle; + if (overlapped == NULL) + return; + + event_handle = overlapped->hEvent; + if (event_handle != NULL) { + ResetEvent(event_handle); + } + memset(overlapped, 0, sizeof(OVERLAPPED)); + overlapped->hEvent = event_handle; +} + +void exit_polling(void) +{ + int i; + + while (InterlockedExchange((LONG *)&compat_spinlock, 1) == 1) { + SleepEx(0, TRUE); + } + if (is_polling_set) { + is_polling_set = FALSE; + + for (i=0; i 0) && (poll_fd[i].handle != INVALID_HANDLE_VALUE) && (poll_fd[i].handle != 0) + && (GetFileType(poll_fd[i].handle) == FILE_TYPE_UNKNOWN) ) { + _close(poll_fd[i].fd); + } + free_overlapped(poll_fd[i].overlapped); + if (!CancelIoEx_Available) { + // Close duplicate handle + if (_poll_fd[i].original_handle != INVALID_HANDLE_VALUE) { + CloseHandle(poll_fd[i].handle); + } + } + poll_fd[i] = INVALID_WINFD; + LeaveCriticalSection(&_poll_fd[i].mutex); + DeleteCriticalSection(&_poll_fd[i].mutex); + } + } + compat_spinlock = 0; +} + +/* + * Create a fake pipe. + * As libusb only uses pipes for signaling, all we need from a pipe is an + * event. To that extent, we create a single wfd and overlapped as a means + * to access that event. + */ +int usbi_pipe(int filedes[2]) +{ + int i; + OVERLAPPED* overlapped; + + CHECK_INIT_POLLING; + + overlapped = (OVERLAPPED*) calloc(1, sizeof(OVERLAPPED)); + if (overlapped == NULL) { + return -1; + } + // The overlapped must have status pending for signaling to work in poll + overlapped->Internal = STATUS_PENDING; + overlapped->InternalHigh = 0; + + // Read end of the "pipe" + filedes[0] = _open(NUL_DEVICE, _O_WRONLY); + if (filedes[0] < 0) { + usbi_err(NULL, "could not create pipe: errno %d", errno); + goto out1; + } + // We can use the same handle for both ends + filedes[1] = filedes[0]; + poll_dbg("pipe filedes = %d", filedes[0]); + + // Note: manual reset must be true (second param) as the reset occurs in read + overlapped->hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if(!overlapped->hEvent) { + goto out2; + } + + for (i=0; i= 0) { + LeaveCriticalSection(&_poll_fd[i].mutex); + continue; + } + + poll_fd[i].fd = filedes[0]; + poll_fd[i].handle = DUMMY_HANDLE; + poll_fd[i].overlapped = overlapped; + // There's no polling on the write end, so we just use READ for our needs + poll_fd[i].rw = RW_READ; + _poll_fd[i].original_handle = INVALID_HANDLE_VALUE; + LeaveCriticalSection(&_poll_fd[i].mutex); + return 0; + } + } + + CloseHandle(overlapped->hEvent); +out2: + _close(filedes[0]); +out1: + free(overlapped); + return -1; +} + +/* + * Create both an fd and an OVERLAPPED from an open Windows handle, so that + * it can be used with our polling function + * The handle MUST support overlapped transfers (usually requires CreateFile + * with FILE_FLAG_OVERLAPPED) + * Return a pollable file descriptor struct, or INVALID_WINFD on error + * + * Note that the fd returned by this function is a per-transfer fd, rather + * than a per-session fd and cannot be used for anything else but our + * custom functions (the fd itself points to the NUL: device) + * if you plan to do R/W on the same handle, you MUST create 2 fds: one for + * read and one for write. Using a single R/W fd is unsupported and will + * produce unexpected results + */ +struct winfd usbi_create_fd(HANDLE handle, int access_mode) +{ + int i, fd; + struct winfd wfd = INVALID_WINFD; + OVERLAPPED* overlapped = NULL; + + CHECK_INIT_POLLING; + + if ((handle == 0) || (handle == INVALID_HANDLE_VALUE)) { + return INVALID_WINFD; + } + + if ((access_mode != _O_RDONLY) && (access_mode != _O_WRONLY)) { + usbi_warn(NULL, "only one of _O_RDONLY or _O_WRONLY are supported.\n" + "If you want to poll for R/W simultaneously, create multiple fds from the same handle."); + return INVALID_WINFD; + } + if (access_mode == _O_RDONLY) { + wfd.rw = RW_READ; + } else { + wfd.rw = RW_WRITE; + } + + // Ensure that we get a non system conflicting unique fd, using + // the same fd attribution system as the pipe ends + fd = _open(NUL_DEVICE, _O_WRONLY); + if (fd < 0) { + return INVALID_WINFD; + } + + overlapped = create_overlapped(); + if(overlapped == NULL) { + _close(fd); + return INVALID_WINFD; + } + + for (i=0; i= 0) { + LeaveCriticalSection(&_poll_fd[i].mutex); + continue; + } + wfd.fd = fd; + // Attempt to emulate some of the CancelIoEx behaviour on platforms + // that don't have it + if (!CancelIoEx_Available) { + _poll_fd[i].thread_id = GetCurrentThreadId(); + if (!DuplicateHandle(GetCurrentProcess(), handle, GetCurrentProcess(), + &wfd.handle, 0, TRUE, DUPLICATE_SAME_ACCESS)) { + usbi_dbg("could not duplicate handle for CancelIo - using original one"); + wfd.handle = handle; + // Make sure we won't close the original handle on fd deletion then + _poll_fd[i].original_handle = INVALID_HANDLE_VALUE; + } else { + _poll_fd[i].original_handle = handle; + } + } else { + wfd.handle = handle; + } + wfd.overlapped = overlapped; + memcpy(&poll_fd[i], &wfd, sizeof(struct winfd)); + LeaveCriticalSection(&_poll_fd[i].mutex); + return wfd; + } + } + free_overlapped(overlapped); + _close(fd); + return INVALID_WINFD; +} + +void _free_index(int _index) +{ + // Cancel any async IO (Don't care about the validity of our handles for this) + cancel_io(_index); + // close fake handle for devices + if ( (poll_fd[_index].handle != INVALID_HANDLE_VALUE) && (poll_fd[_index].handle != 0) + && (GetFileType(poll_fd[_index].handle) == FILE_TYPE_UNKNOWN) ) { + _close(poll_fd[_index].fd); + } + // close the duplicate handle (if we have an actual duplicate) + if (!CancelIoEx_Available) { + if (_poll_fd[_index].original_handle != INVALID_HANDLE_VALUE) { + CloseHandle(poll_fd[_index].handle); + } + _poll_fd[_index].original_handle = INVALID_HANDLE_VALUE; + _poll_fd[_index].thread_id = 0; + } + free_overlapped(poll_fd[_index].overlapped); + poll_fd[_index] = INVALID_WINFD; +} + +/* + * Release a pollable file descriptor. + * + * Note that the associated Windows handle is not closed by this call + */ +void usbi_free_fd(int fd) +{ + int _index; + + CHECK_INIT_POLLING; + + _index = _fd_to_index_and_lock(fd); + if (_index < 0) { + return; + } + _free_index(_index); + LeaveCriticalSection(&_poll_fd[_index].mutex); +} + +/* + * The functions below perform various conversions between fd, handle and OVERLAPPED + */ +struct winfd fd_to_winfd(int fd) +{ + int i; + struct winfd wfd; + + CHECK_INIT_POLLING; + + if (fd <= 0) + return INVALID_WINFD; + + for (i=0; i= 0) { + LeaveCriticalSection(&_poll_fd[_index].mutex); + } + usbi_warn(NULL, "invalid fd"); + triggered = -1; + goto poll_exit; + } + + // IN or OUT must match our fd direction + if ((fds[i].events & POLLIN) && (poll_fd[_index].rw != RW_READ)) { + fds[i].revents |= POLLNVAL | POLLERR; + errno = EBADF; + usbi_warn(NULL, "attempted POLLIN on fd without READ access"); + LeaveCriticalSection(&_poll_fd[_index].mutex); + triggered = -1; + goto poll_exit; + } + + if ((fds[i].events & POLLOUT) && (poll_fd[_index].rw != RW_WRITE)) { + fds[i].revents |= POLLNVAL | POLLERR; + errno = EBADF; + usbi_warn(NULL, "attempted POLLOUT on fd without WRITE access"); + LeaveCriticalSection(&_poll_fd[_index].mutex); + triggered = -1; + goto poll_exit; + } + + // The following macro only works if overlapped I/O was reported pending + if ( (HasOverlappedIoCompleted(poll_fd[_index].overlapped)) + || (HasOverlappedIoCompletedSync(poll_fd[_index].overlapped)) ) { + poll_dbg(" completed"); + // checks above should ensure this works: + fds[i].revents = fds[i].events; + triggered++; + } else { + handles_to_wait_on[nb_handles_to_wait_on] = poll_fd[_index].overlapped->hEvent; + handle_to_index[nb_handles_to_wait_on] = i; + nb_handles_to_wait_on++; + } + LeaveCriticalSection(&_poll_fd[_index].mutex); + } + + // If nothing was triggered, wait on all fds that require it + if ((timeout != 0) && (triggered == 0) && (nb_handles_to_wait_on != 0)) { + if (timeout < 0) { + poll_dbg("starting infinite wait for %d handles...", (int)nb_handles_to_wait_on); + } else { + poll_dbg("starting %d ms wait for %d handles...", timeout, (int)nb_handles_to_wait_on); + } + ret = WaitForMultipleObjects(nb_handles_to_wait_on, handles_to_wait_on, + FALSE, (timeout<0)?INFINITE:(DWORD)timeout); + object_index = ret-WAIT_OBJECT_0; + if ((object_index >= 0) && ((DWORD)object_index < nb_handles_to_wait_on)) { + poll_dbg(" completed after wait"); + i = handle_to_index[object_index]; + _index = _fd_to_index_and_lock(fds[i].fd); + fds[i].revents = fds[i].events; + triggered++; + if (_index >= 0) { + LeaveCriticalSection(&_poll_fd[_index].mutex); + } + } else if (ret == WAIT_TIMEOUT) { + poll_dbg(" timed out"); + triggered = 0; // 0 = timeout + } else { + errno = EIO; + triggered = -1; // error + } + } + +poll_exit: + if (handles_to_wait_on != NULL) { + free(handles_to_wait_on); + } + if (handle_to_index != NULL) { + free(handle_to_index); + } + return triggered; +} + +/* + * close a fake pipe fd + */ +int usbi_close(int fd) +{ + int _index; + int r = -1; + + CHECK_INIT_POLLING; + + _index = _fd_to_index_and_lock(fd); + + if (_index < 0) { + errno = EBADF; + } else { + if (poll_fd[_index].overlapped != NULL) { + // Must be a different event for each end of the pipe + CloseHandle(poll_fd[_index].overlapped->hEvent); + free(poll_fd[_index].overlapped); + } + r = _close(poll_fd[_index].fd); + if (r != 0) { + errno = EIO; + } + poll_fd[_index] = INVALID_WINFD; + LeaveCriticalSection(&_poll_fd[_index].mutex); + } + return r; +} + +/* + * synchronous write for fake "pipe" signaling + */ +ssize_t usbi_write(int fd, const void *buf, size_t count) +{ + int _index; + + CHECK_INIT_POLLING; + + if (count != sizeof(unsigned char)) { + usbi_err(NULL, "this function should only used for signaling"); + return -1; + } + + _index = _fd_to_index_and_lock(fd); + + if ( (_index < 0) || (poll_fd[_index].overlapped == NULL) ) { + errno = EBADF; + if (_index >= 0) { + LeaveCriticalSection(&_poll_fd[_index].mutex); + } + return -1; + } + + poll_dbg("set pipe event (fd = %d, thread = %08X)", _index, GetCurrentThreadId()); + SetEvent(poll_fd[_index].overlapped->hEvent); + poll_fd[_index].overlapped->Internal = STATUS_WAIT_0; + // If two threads write on the pipe at the same time, we need to + // process two separate reads => use the overlapped as a counter + poll_fd[_index].overlapped->InternalHigh++; + + LeaveCriticalSection(&_poll_fd[_index].mutex); + return sizeof(unsigned char); +} + +/* + * synchronous read for fake "pipe" signaling + */ +ssize_t usbi_read(int fd, void *buf, size_t count) +{ + int _index; + ssize_t r = -1; + + CHECK_INIT_POLLING; + + if (count != sizeof(unsigned char)) { + usbi_err(NULL, "this function should only used for signaling"); + return -1; + } + + _index = _fd_to_index_and_lock(fd); + + if (_index < 0) { + errno = EBADF; + return -1; + } + + if (WaitForSingleObject(poll_fd[_index].overlapped->hEvent, INFINITE) != WAIT_OBJECT_0) { + usbi_warn(NULL, "waiting for event failed: %d", (int)GetLastError()); + errno = EIO; + goto out; + } + + poll_dbg("clr pipe event (fd = %d, thread = %08X)", _index, GetCurrentThreadId()); + poll_fd[_index].overlapped->InternalHigh--; + // Don't reset unless we don't have any more events to process + if (poll_fd[_index].overlapped->InternalHigh <= 0) { + ResetEvent(poll_fd[_index].overlapped->hEvent); + poll_fd[_index].overlapped->Internal = STATUS_PENDING; + } + + r = sizeof(unsigned char); + +out: + LeaveCriticalSection(&_poll_fd[_index].mutex); + return r; +} diff --git a/compat/libusb-1.0/libusb/os/poll_windows.h b/compat/libusb-1.0/libusb/os/poll_windows.h new file mode 100644 index 0000000..d3bda47 --- /dev/null +++ b/compat/libusb-1.0/libusb/os/poll_windows.h @@ -0,0 +1,117 @@ +/* + * Windows compat: POSIX compatibility wrapper + * Copyright (C) 2009-2010 Pete Batard + * With contributions from Michael Plante, Orin Eman et al. + * Parts of poll implementation from libusb-win32, by Stephan Meyer et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#pragma once + +#include + +#if defined(_MSC_VER) +// disable /W4 MSVC warnings that are benign +#pragma warning(disable:4127) // conditional expression is constant +#endif + +// Handle synchronous completion through the overlapped structure +#if !defined(STATUS_REPARSE) // reuse the REPARSE status code +#define STATUS_REPARSE ((LONG)0x00000104L) +#endif +#define STATUS_COMPLETED_SYNCHRONOUSLY STATUS_REPARSE +#define HasOverlappedIoCompletedSync(lpOverlapped) (((DWORD)(lpOverlapped)->Internal) == STATUS_COMPLETED_SYNCHRONOUSLY) + +#define DUMMY_HANDLE ((HANDLE)(LONG_PTR)-2) + +enum windows_version { + WINDOWS_UNSUPPORTED, + WINDOWS_XP, + WINDOWS_2003, // also includes XP 64 + WINDOWS_VISTA_AND_LATER, +}; +extern enum windows_version windows_version; + +#define MAX_FDS 256 + +#define POLLIN 0x0001 /* There is data to read */ +#define POLLPRI 0x0002 /* There is urgent data to read */ +#define POLLOUT 0x0004 /* Writing now will not block */ +#define POLLERR 0x0008 /* Error condition */ +#define POLLHUP 0x0010 /* Hung up */ +#define POLLNVAL 0x0020 /* Invalid request: fd not open */ + +struct pollfd { + int fd; /* file descriptor */ + short events; /* requested events */ + short revents; /* returned events */ +}; + +// access modes +enum rw_type { + RW_NONE, + RW_READ, + RW_WRITE, +}; + +// fd struct that can be used for polling on Windows +struct winfd { + int fd; // what's exposed to libusb core + HANDLE handle; // what we need to attach overlapped to the I/O op, so we can poll it + OVERLAPPED* overlapped; // what will report our I/O status + enum rw_type rw; // I/O transfer direction: read *XOR* write (NOT BOTH) +}; +extern const struct winfd INVALID_WINFD; + +int usbi_pipe(int pipefd[2]); +int usbi_poll(struct pollfd *fds, unsigned int nfds, int timeout); +ssize_t usbi_write(int fd, const void *buf, size_t count); +ssize_t usbi_read(int fd, void *buf, size_t count); +int usbi_close(int fd); + +void init_polling(void); +void exit_polling(void); +struct winfd usbi_create_fd(HANDLE handle, int access_mode); +void usbi_free_fd(int fd); +struct winfd fd_to_winfd(int fd); +struct winfd handle_to_winfd(HANDLE handle); +struct winfd overlapped_to_winfd(OVERLAPPED* overlapped); + +/* + * Timeval operations + */ +#if defined(DDKBUILD) +#include // defines timeval functions on DDK +#endif + +#if !defined(TIMESPEC_TO_TIMEVAL) +#define TIMESPEC_TO_TIMEVAL(tv, ts) { \ + (tv)->tv_sec = (long)(ts)->tv_sec; \ + (tv)->tv_usec = (long)(ts)->tv_nsec / 1000; \ +} +#endif +#if !defined(timersub) +#define timersub(a, b, result) \ +do { \ + (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \ + (result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \ + if ((result)->tv_usec < 0) { \ + --(result)->tv_sec; \ + (result)->tv_usec += 1000000; \ + } \ +} while (0) +#endif + diff --git a/compat/libusb-1.0/libusb/os/threads_posix.c b/compat/libusb-1.0/libusb/os/threads_posix.c new file mode 100644 index 0000000..60c57cf --- /dev/null +++ b/compat/libusb-1.0/libusb/os/threads_posix.c @@ -0,0 +1,55 @@ +/* + * libusb synchronization using POSIX Threads + * + * Copyright (C) 2011 Vitali Lovich + * Copyright (C) 2011 Peter Stuge + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef _XOPEN_SOURCE +# if _XOPEN_SOURCE < 500 +# undef _XOPEN_SOURCE +# define _XOPEN_SOURCE 500 +# endif +#else +#define _XOPEN_SOURCE 500 +#endif /* _XOPEN_SOURCE */ + +#include "threads_posix.h" + +int usbi_mutex_init_recursive(pthread_mutex_t *mutex, pthread_mutexattr_t *attr) +{ + int err; + pthread_mutexattr_t stack_attr; + if (!attr) { + attr = &stack_attr; + err = pthread_mutexattr_init(&stack_attr); + if (err != 0) + return err; + } + + err = pthread_mutexattr_settype(attr, PTHREAD_MUTEX_RECURSIVE); + if (err != 0) + goto finish; + + err = pthread_mutex_init(mutex, attr); + +finish: + if (attr == &stack_attr) + pthread_mutexattr_destroy(&stack_attr); + + return err; +} diff --git a/compat/libusb-1.0/libusb/os/threads_posix.h b/compat/libusb-1.0/libusb/os/threads_posix.h new file mode 100644 index 0000000..9752208 --- /dev/null +++ b/compat/libusb-1.0/libusb/os/threads_posix.h @@ -0,0 +1,48 @@ +/* + * libusb synchronization using POSIX Threads + * + * Copyright (C) 2010 Peter Stuge + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef LIBUSB_THREADS_POSIX_H +#define LIBUSB_THREADS_POSIX_H + +#include + +#define usbi_mutex_static_t pthread_mutex_t +#define USBI_MUTEX_INITIALIZER PTHREAD_MUTEX_INITIALIZER +#define usbi_mutex_static_lock pthread_mutex_lock +#define usbi_mutex_static_unlock pthread_mutex_unlock + +#define usbi_mutex_t pthread_mutex_t +#define usbi_mutex_init pthread_mutex_init +#define usbi_mutex_lock pthread_mutex_lock +#define usbi_mutex_unlock pthread_mutex_unlock +#define usbi_mutex_trylock pthread_mutex_trylock +#define usbi_mutex_destroy pthread_mutex_destroy + +#define usbi_cond_t pthread_cond_t +#define usbi_cond_init pthread_cond_init +#define usbi_cond_wait pthread_cond_wait +#define usbi_cond_timedwait pthread_cond_timedwait +#define usbi_cond_broadcast pthread_cond_broadcast +#define usbi_cond_destroy pthread_cond_destroy +#define usbi_cond_signal pthread_cond_signal + +extern int usbi_mutex_init_recursive(pthread_mutex_t *mutex, pthread_mutexattr_t *attr); + +#endif /* LIBUSB_THREADS_POSIX_H */ diff --git a/compat/libusb-1.0/libusb/os/threads_windows.c b/compat/libusb-1.0/libusb/os/threads_windows.c new file mode 100644 index 0000000..b92b645 --- /dev/null +++ b/compat/libusb-1.0/libusb/os/threads_windows.c @@ -0,0 +1,208 @@ +/* + * libusb synchronization on Microsoft Windows + * + * Copyright (C) 2010 Michael Plante + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include + +#include "libusbi.h" + + +int usbi_mutex_init(usbi_mutex_t *mutex, + const usbi_mutexattr_t *attr) { + if(! mutex) return ((errno=EINVAL)); + *mutex = CreateMutex(NULL, FALSE, NULL); + if(!*mutex) return ((errno=ENOMEM)); + return 0; +} +int usbi_mutex_destroy(usbi_mutex_t *mutex) { + // It is not clear if CloseHandle failure is due to failure to unlock. + // If so, this should be errno=EBUSY. + if(!mutex || !CloseHandle(*mutex)) return ((errno=EINVAL)); + *mutex = NULL; + return 0; +} +int usbi_mutex_trylock(usbi_mutex_t *mutex) { + DWORD result; + if(!mutex) return ((errno=EINVAL)); + result = WaitForSingleObject(*mutex, 0); + if(result == WAIT_OBJECT_0 || result == WAIT_ABANDONED) + return 0; // acquired (ToDo: check that abandoned is ok) + if(result == WAIT_TIMEOUT) + return ((errno=EBUSY)); + return ((errno=EINVAL)); // don't know how this would happen + // so don't know proper errno +} +int usbi_mutex_lock(usbi_mutex_t *mutex) { + DWORD result; + if(!mutex) return ((errno=EINVAL)); + result = WaitForSingleObject(*mutex, INFINITE); + if(result == WAIT_OBJECT_0 || result == WAIT_ABANDONED) + return 0; // acquired (ToDo: check that abandoned is ok) + return ((errno=EINVAL)); // don't know how this would happen + // so don't know proper errno +} +int usbi_mutex_unlock(usbi_mutex_t *mutex) { + if(!mutex) return ((errno=EINVAL)); + if(!ReleaseMutex(*mutex)) return ((errno=EPERM )); + return 0; +} + +int usbi_mutex_static_lock(usbi_mutex_static_t *mutex) { + if(!mutex) return ((errno=EINVAL)); + while (InterlockedExchange((LONG *)mutex, 1) == 1) { + SleepEx(0, TRUE); + } + return 0; +} +int usbi_mutex_static_unlock(usbi_mutex_static_t *mutex) { + if(!mutex) return ((errno=EINVAL)); + *mutex = 0; + return 0; +} + + + +int usbi_cond_init(usbi_cond_t *cond, + const usbi_condattr_t *attr) { + if(!cond) return ((errno=EINVAL)); + list_init(&cond->waiters ); + list_init(&cond->not_waiting); + return 0; +} +int usbi_cond_destroy(usbi_cond_t *cond) { + // This assumes no one is using this anymore. The check MAY NOT BE safe. + struct usbi_cond_perthread *pos, *prev_pos = NULL; + if(!cond) return ((errno=EINVAL)); + if(!list_empty(&cond->waiters)) return ((errno=EBUSY )); // (!see above!) + list_for_each_entry(pos, &cond->not_waiting, list, struct usbi_cond_perthread) { + free(prev_pos); + CloseHandle(pos->event); + list_del(&pos->list); + prev_pos = pos; + } + free(prev_pos); + + return 0; +} + +int usbi_cond_broadcast(usbi_cond_t *cond) { + // Assumes mutex is locked; this is not in keeping with POSIX spec, but + // libusb does this anyway, so we simplify by not adding more sync + // primitives to the CV definition! + int fail = 0; + struct usbi_cond_perthread *pos; + if(!cond) return ((errno=EINVAL)); + list_for_each_entry(pos, &cond->waiters, list, struct usbi_cond_perthread) { + if(!SetEvent(pos->event)) + fail = 1; + } + // The wait function will remove its respective item from the list. + return fail ? ((errno=EINVAL)) : 0; +} +int usbi_cond_signal(usbi_cond_t *cond) { + // Assumes mutex is locked; this is not in keeping with POSIX spec, but + // libusb does this anyway, so we simplify by not adding more sync + // primitives to the CV definition! + struct usbi_cond_perthread *pos; + if(!cond) return ((errno=EINVAL)); + if(list_empty(&cond->waiters)) return 0; // no one to wakeup. + pos = list_entry(&cond->waiters.next, struct usbi_cond_perthread, list); + // The wait function will remove its respective item from the list. + return SetEvent(pos->event) ? 0 : ((errno=EINVAL)); +} +static int __inline usbi_cond_intwait(usbi_cond_t *cond, + usbi_mutex_t *mutex, + DWORD timeout_ms) { + struct usbi_cond_perthread *pos; + int found = 0, r; + DWORD r2,tid = GetCurrentThreadId(); + if(!cond || !mutex) return ((errno=EINVAL)); + list_for_each_entry(pos, &cond->not_waiting, list, struct usbi_cond_perthread) { + if(tid == pos->tid) { + found = 1; + break; + } + } + if(!found) { + pos = (struct usbi_cond_perthread*) calloc(1, sizeof(struct usbi_cond_perthread)); + if(!pos) return ((errno=ENOMEM)); // This errno is not POSIX-allowed. + pos->tid = tid; + pos->event = CreateEvent(NULL, FALSE, FALSE, NULL); // auto-reset. + if(!pos->event) { + free(pos); + return ((errno=ENOMEM)); + } + list_add(&pos->list, &cond->not_waiting); + } + + list_del(&pos->list); // remove from not_waiting list. + list_add(&pos->list, &cond->waiters); + + r = usbi_mutex_unlock(mutex); + if(r) return r; + r2 = WaitForSingleObject(pos->event, timeout_ms); + r = usbi_mutex_lock(mutex); + if(r) return r; + + list_del(&pos->list); + list_add(&pos->list, &cond->not_waiting); + + if(r2 == WAIT_TIMEOUT) return ((errno=ETIMEDOUT)); + + return 0; +} +// N.B.: usbi_cond_*wait() can also return ENOMEM, even though pthread_cond_*wait cannot! +int usbi_cond_wait(usbi_cond_t *cond, usbi_mutex_t *mutex) { + return usbi_cond_intwait(cond, mutex, INFINITE); +} +int usbi_cond_timedwait(usbi_cond_t *cond, + usbi_mutex_t *mutex, + const struct timespec *abstime) { + FILETIME filetime; + ULARGE_INTEGER rtime; + struct timeval targ_time, cur_time, delta_time; + struct timespec cur_time_ns; + DWORD millis; + extern const uint64_t epoch_time; + + GetSystemTimeAsFileTime(&filetime); + rtime.LowPart = filetime.dwLowDateTime; + rtime.HighPart = filetime.dwHighDateTime; + rtime.QuadPart -= epoch_time; + cur_time_ns.tv_sec = (long)(rtime.QuadPart / 10000000); + cur_time_ns.tv_nsec = (long)((rtime.QuadPart % 10000000)*100); + TIMESPEC_TO_TIMEVAL(&cur_time, &cur_time_ns); + + TIMESPEC_TO_TIMEVAL(&targ_time, abstime); + timersub(&targ_time, &cur_time, &delta_time); + if(delta_time.tv_sec < 0) // abstime already passed? + millis = 0; + else { + millis = delta_time.tv_usec/1000; + millis += delta_time.tv_sec *1000; + if (delta_time.tv_usec % 1000) // round up to next millisecond + millis++; + } + + return usbi_cond_intwait(cond, mutex, millis); +} + diff --git a/compat/libusb-1.0/libusb/os/threads_windows.h b/compat/libusb-1.0/libusb/os/threads_windows.h new file mode 100644 index 0000000..e486df9 --- /dev/null +++ b/compat/libusb-1.0/libusb/os/threads_windows.h @@ -0,0 +1,88 @@ +/* + * libusb synchronization on Microsoft Windows + * + * Copyright (C) 2010 Michael Plante + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef LIBUSB_THREADS_WINDOWS_H +#define LIBUSB_THREADS_WINDOWS_H + +#include + +#define usbi_mutex_static_t volatile LONG +#define USBI_MUTEX_INITIALIZER 0 + +#define usbi_mutex_t HANDLE + +struct usbi_cond_perthread { + struct list_head list; + DWORD tid; + HANDLE event; +}; +struct usbi_cond_t_ { + // Every time a thread touches the CV, it winds up in one of these lists. + // It stays there until the CV is destroyed, even if the thread + // terminates. + struct list_head waiters; + struct list_head not_waiting; +}; +typedef struct usbi_cond_t_ usbi_cond_t; + +// We *were* getting timespec from pthread.h: +#if (!defined(HAVE_STRUCT_TIMESPEC) && !defined(_TIMESPEC_DEFINED)) +#define HAVE_STRUCT_TIMESPEC 1 +#define _TIMESPEC_DEFINED 1 +struct timespec { + long tv_sec; + long tv_nsec; +}; +#endif /* HAVE_STRUCT_TIMESPEC | _TIMESPEC_DEFINED */ + +// We *were* getting ETIMEDOUT from pthread.h: +#ifndef ETIMEDOUT +# define ETIMEDOUT 10060 /* This is the value in winsock.h. */ +#endif + +#define usbi_mutexattr_t void +#define usbi_condattr_t void + +// all Windows mutexes are recursive +#define usbi_mutex_init_recursive(mutex, attr) usbi_mutex_init((mutex), (attr)) + +int usbi_mutex_static_lock(usbi_mutex_static_t *mutex); +int usbi_mutex_static_unlock(usbi_mutex_static_t *mutex); + + +int usbi_mutex_init(usbi_mutex_t *mutex, + const usbi_mutexattr_t *attr); +int usbi_mutex_lock(usbi_mutex_t *mutex); +int usbi_mutex_unlock(usbi_mutex_t *mutex); +int usbi_mutex_trylock(usbi_mutex_t *mutex); +int usbi_mutex_destroy(usbi_mutex_t *mutex); + +int usbi_cond_init(usbi_cond_t *cond, + const usbi_condattr_t *attr); +int usbi_cond_destroy(usbi_cond_t *cond); +int usbi_cond_wait(usbi_cond_t *cond, usbi_mutex_t *mutex); +int usbi_cond_timedwait(usbi_cond_t *cond, + usbi_mutex_t *mutex, + const struct timespec *abstime); +int usbi_cond_broadcast(usbi_cond_t *cond); +int usbi_cond_signal(usbi_cond_t *cond); + +#endif /* LIBUSB_THREADS_WINDOWS_H */ + diff --git a/compat/libusb-1.0/libusb/os/windows_usb.c b/compat/libusb-1.0/libusb/os/windows_usb.c new file mode 100644 index 0000000..873905c --- /dev/null +++ b/compat/libusb-1.0/libusb/os/windows_usb.c @@ -0,0 +1,2998 @@ +/* + * windows backend for libusb 1.0 + * Copyright (c) 2009-2010 Pete Batard + * With contributions from Michael Plante, Orin Eman et al. + * Parts of this code adapted from libusb-win32-v1 by Stephan Meyer + * Hash table functions adapted from glibc, by Ulrich Drepper et al. + * Major code testing contribution by Xiaofan Chen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "poll_windows.h" +#include "windows_usb.h" + +// The following prevents "banned API" errors when using the MS's WDK OACR/Prefast +#if defined(_PREFAST_) +#pragma warning(disable:28719) +#endif + +// The 2 macros below are used in conjunction with safe loops. +#define LOOP_CHECK(fcall) { r=fcall; if (r != LIBUSB_SUCCESS) continue; } +#define LOOP_BREAK(err) { r=err; continue; } + +extern void usbi_fd_notification(struct libusb_context *ctx); + +// Helper prototypes +static int windows_get_active_config_descriptor(struct libusb_device *dev, unsigned char *buffer, size_t len, int *host_endian); +static int windows_clock_gettime(int clk_id, struct timespec *tp); +unsigned __stdcall windows_clock_gettime_threaded(void* param); +// WinUSB API prototypes +static int winusb_init(struct libusb_context *ctx); +static int winusb_exit(void); +static int winusb_open(struct libusb_device_handle *dev_handle); +static void winusb_close(struct libusb_device_handle *dev_handle); +static int winusb_configure_endpoints(struct libusb_device_handle *dev_handle, int iface); +static int winusb_claim_interface(struct libusb_device_handle *dev_handle, int iface); +static int winusb_release_interface(struct libusb_device_handle *dev_handle, int iface); +static int winusb_submit_control_transfer(struct usbi_transfer *itransfer); +static int winusb_set_interface_altsetting(struct libusb_device_handle *dev_handle, int iface, int altsetting); +static int winusb_submit_bulk_transfer(struct usbi_transfer *itransfer); +static int winusb_clear_halt(struct libusb_device_handle *dev_handle, unsigned char endpoint); +static int winusb_abort_transfers(struct usbi_transfer *itransfer); +static int winusb_abort_control(struct usbi_transfer *itransfer); +static int winusb_reset_device(struct libusb_device_handle *dev_handle); +static int winusb_copy_transfer_data(struct usbi_transfer *itransfer, uint32_t io_size); +// Composite API prototypes +static int composite_init(struct libusb_context *ctx); +static int composite_exit(void); +static int composite_open(struct libusb_device_handle *dev_handle); +static void composite_close(struct libusb_device_handle *dev_handle); +static int composite_claim_interface(struct libusb_device_handle *dev_handle, int iface); +static int composite_set_interface_altsetting(struct libusb_device_handle *dev_handle, int iface, int altsetting); +static int composite_release_interface(struct libusb_device_handle *dev_handle, int iface); +static int composite_submit_control_transfer(struct usbi_transfer *itransfer); +static int composite_submit_bulk_transfer(struct usbi_transfer *itransfer); +static int composite_submit_iso_transfer(struct usbi_transfer *itransfer); +static int composite_clear_halt(struct libusb_device_handle *dev_handle, unsigned char endpoint); +static int composite_abort_transfers(struct usbi_transfer *itransfer); +static int composite_abort_control(struct usbi_transfer *itransfer); +static int composite_reset_device(struct libusb_device_handle *dev_handle); +static int composite_copy_transfer_data(struct usbi_transfer *itransfer, uint32_t io_size); + + +// Global variables +uint64_t hires_frequency, hires_ticks_to_ps; +const uint64_t epoch_time = UINT64_C(116444736000000000); // 1970.01.01 00:00:000 in MS Filetime +enum windows_version windows_version = WINDOWS_UNSUPPORTED; +// Concurrency +static int concurrent_usage = -1; +usbi_mutex_t autoclaim_lock; +// Timer thread +// NB: index 0 is for monotonic and 1 is for the thread exit event +HANDLE timer_thread = NULL; +HANDLE timer_mutex = NULL; +struct timespec timer_tp; +volatile LONG request_count[2] = {0, 1}; // last one must be > 0 +HANDLE timer_request[2] = { NULL, NULL }; +HANDLE timer_response = NULL; +// API globals +bool api_winusb_available = false; +#define CHECK_WINUSB_AVAILABLE do { if (!api_winusb_available) return LIBUSB_ERROR_ACCESS; } while (0) + +static inline BOOLEAN guid_eq(const GUID *guid1, const GUID *guid2) { + if ((guid1 != NULL) && (guid2 != NULL)) { + return (memcmp(guid1, guid2, sizeof(GUID)) == 0); + } + return false; +} + +#if defined(ENABLE_DEBUG_LOGGING) || (defined(_MSC_VER) && _MSC_VER < 1400) +static char* guid_to_string(const GUID* guid) +{ + static char guid_string[MAX_GUID_STRING_LENGTH]; + + if (guid == NULL) return NULL; + sprintf(guid_string, "{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", + (unsigned int)guid->Data1, guid->Data2, guid->Data3, + guid->Data4[0], guid->Data4[1], guid->Data4[2], guid->Data4[3], + guid->Data4[4], guid->Data4[5], guid->Data4[6], guid->Data4[7]); + return guid_string; +} +#endif + +/* + * Converts a windows error to human readable string + * uses retval as errorcode, or, if 0, use GetLastError() + */ +static char *windows_error_str(uint32_t retval) +{ +static char err_string[ERR_BUFFER_SIZE]; + + DWORD size; + size_t i; + uint32_t error_code, format_error; + + error_code = retval?retval:GetLastError(); + + safe_sprintf(err_string, ERR_BUFFER_SIZE, "[%d] ", error_code); + + size = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, NULL, error_code, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), &err_string[safe_strlen(err_string)], + ERR_BUFFER_SIZE - (DWORD)safe_strlen(err_string), NULL); + if (size == 0) { + format_error = GetLastError(); + if (format_error) + safe_sprintf(err_string, ERR_BUFFER_SIZE, + "Windows error code %u (FormatMessage error code %u)", error_code, format_error); + else + safe_sprintf(err_string, ERR_BUFFER_SIZE, "Unknown error code %u", error_code); + } else { + // Remove CR/LF terminators + for (i=safe_strlen(err_string)-1; ((err_string[i]==0x0A) || (err_string[i]==0x0D)); i--) { + err_string[i] = 0; + } + } + return err_string; +} + +/* + * Sanitize Microsoft's paths: convert to uppercase, add prefix and fix backslashes. + * Return an allocated sanitized string or NULL on error. + */ +static char* sanitize_path(const char* path) +{ + const char root_prefix[] = "\\\\.\\"; + size_t j, size, root_size; + char* ret_path = NULL; + size_t add_root = 0; + + if (path == NULL) + return NULL; + + size = safe_strlen(path)+1; + root_size = sizeof(root_prefix)-1; + + // Microsoft indiscriminatly uses '\\?\', '\\.\', '##?#" or "##.#" for root prefixes. + if (!((size > 3) && (((path[0] == '\\') && (path[1] == '\\') && (path[3] == '\\')) || + ((path[0] == '#') && (path[1] == '#') && (path[3] == '#'))))) { + add_root = root_size; + size += add_root; + } + + if ((ret_path = (char*)calloc(size, 1)) == NULL) + return NULL; + + safe_strcpy(&ret_path[add_root], size-add_root, path); + + // Ensure consistancy with root prefix + for (j=0; jcbSize = sizeof(SP_DEVINFO_DATA); + if (!pSetupDiEnumDeviceInfo(*dev_info, _index, dev_info_data)) { + if (GetLastError() != ERROR_NO_MORE_ITEMS) { + usbi_err(ctx, "Could not obtain device info data for index %u: %s", + _index, windows_error_str(0)); + } + pSetupDiDestroyDeviceInfoList(*dev_info); + *dev_info = INVALID_HANDLE_VALUE; + return false; + } + return true; +} + +/* + * enumerate interfaces for a specific GUID + * + * Parameters: + * dev_info: a pointer to a dev_info list + * dev_info_data: a pointer to an SP_DEVINFO_DATA to be filled (or NULL if not needed) + * guid: the GUID for which to retrieve interface details + * index: zero based index of the interface in the device info list + * + * Note: it is the responsibility of the caller to free the DEVICE_INTERFACE_DETAIL_DATA + * structure returned and call this function repeatedly using the same guid (with an + * incremented index starting at zero) until all interfaces have been returned. + */ +static SP_DEVICE_INTERFACE_DETAIL_DATA_A *get_interface_details(struct libusb_context *ctx, + HDEVINFO *dev_info, SP_DEVINFO_DATA *dev_info_data, const GUID* guid, unsigned _index) +{ + SP_DEVICE_INTERFACE_DATA dev_interface_data; + SP_DEVICE_INTERFACE_DETAIL_DATA_A *dev_interface_details = NULL; + DWORD size; + + if (_index <= 0) { + *dev_info = pSetupDiGetClassDevsA(guid, NULL, NULL, DIGCF_PRESENT|DIGCF_DEVICEINTERFACE); + } + + if (dev_info_data != NULL) { + dev_info_data->cbSize = sizeof(SP_DEVINFO_DATA); + if (!pSetupDiEnumDeviceInfo(*dev_info, _index, dev_info_data)) { + if (GetLastError() != ERROR_NO_MORE_ITEMS) { + usbi_err(ctx, "Could not obtain device info data for index %u: %s", + _index, windows_error_str(0)); + } + pSetupDiDestroyDeviceInfoList(*dev_info); + *dev_info = INVALID_HANDLE_VALUE; + return NULL; + } + } + + dev_interface_data.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + if (!pSetupDiEnumDeviceInterfaces(*dev_info, NULL, guid, _index, &dev_interface_data)) { + if (GetLastError() != ERROR_NO_MORE_ITEMS) { + usbi_err(ctx, "Could not obtain interface data for index %u: %s", + _index, windows_error_str(0)); + } + pSetupDiDestroyDeviceInfoList(*dev_info); + *dev_info = INVALID_HANDLE_VALUE; + return NULL; + } + + // Read interface data (dummy + actual) to access the device path + if (!pSetupDiGetDeviceInterfaceDetailA(*dev_info, &dev_interface_data, NULL, 0, &size, NULL)) { + // The dummy call should fail with ERROR_INSUFFICIENT_BUFFER + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + usbi_err(ctx, "could not access interface data (dummy) for index %u: %s", + _index, windows_error_str(0)); + goto err_exit; + } + } else { + usbi_err(ctx, "program assertion failed - http://msdn.microsoft.com/en-us/library/ms792901.aspx is wrong."); + goto err_exit; + } + + if ((dev_interface_details = (SP_DEVICE_INTERFACE_DETAIL_DATA_A*) calloc(size, 1)) == NULL) { + usbi_err(ctx, "could not allocate interface data for index %u.", _index); + goto err_exit; + } + + dev_interface_details->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_A); + if (!pSetupDiGetDeviceInterfaceDetailA(*dev_info, &dev_interface_data, + dev_interface_details, size, &size, NULL)) { + usbi_err(ctx, "could not access interface data (actual) for index %u: %s", + _index, windows_error_str(0)); + } + + return dev_interface_details; + +err_exit: + pSetupDiDestroyDeviceInfoList(*dev_info); + *dev_info = INVALID_HANDLE_VALUE; + return NULL; +} + +/* Hash table functions - modified From glibc 2.3.2: + [Aho,Sethi,Ullman] Compilers: Principles, Techniques and Tools, 1986 + [Knuth] The Art of Computer Programming, part 3 (6.4) */ +typedef struct htab_entry { + unsigned long used; + char* str; +} htab_entry; +htab_entry* htab_table = NULL; +usbi_mutex_t htab_write_mutex = NULL; +unsigned long htab_size, htab_filled; + +/* For the used double hash method the table size has to be a prime. To + correct the user given table size we need a prime test. This trivial + algorithm is adequate because the code is called only during init and + the number is likely to be small */ +static int isprime(unsigned long number) +{ + // no even number will be passed + unsigned int divider = 3; + + while((divider * divider < number) && (number % divider != 0)) + divider += 2; + + return (number % divider != 0); +} + +/* Before using the hash table we must allocate memory for it. + We allocate one element more as the found prime number says. + This is done for more effective indexing as explained in the + comment for the hash function. */ +static int htab_create(struct libusb_context *ctx, unsigned long nel) +{ + if (htab_table != NULL) { + usbi_err(ctx, "hash table already allocated"); + } + + // Create a mutex + usbi_mutex_init(&htab_write_mutex, NULL); + + // Change nel to the first prime number not smaller as nel. + nel |= 1; + while(!isprime(nel)) + nel += 2; + + htab_size = nel; + usbi_dbg("using %d entries hash table", nel); + htab_filled = 0; + + // allocate memory and zero out. + htab_table = (htab_entry*)calloc(htab_size + 1, sizeof(htab_entry)); + if (htab_table == NULL) { + usbi_err(ctx, "could not allocate space for hash table"); + return 0; + } + + return 1; +} + +/* After using the hash table it has to be destroyed. */ +static void htab_destroy(void) +{ + size_t i; + if (htab_table == NULL) { + return; + } + + for (i=0; i New entry + + // If the table is full return an error + if (htab_filled >= htab_size) { + usbi_err(NULL, "hash table is full (%d entries)", htab_size); + return 0; + } + + // Concurrent threads might be storing the same entry at the same time + // (eg. "simultaneous" enums from different threads) => use a mutex + usbi_mutex_lock(&htab_write_mutex); + // Just free any previously allocated string (which should be the same as + // new one). The possibility of concurrent threads storing a collision + // string (same hash, different string) at the same time is extremely low + safe_free(htab_table[idx].str); + htab_table[idx].used = hval; + htab_table[idx].str = (char*) calloc(1, safe_strlen(str)+1); + if (htab_table[idx].str == NULL) { + usbi_err(NULL, "could not duplicate string for hash table"); + usbi_mutex_unlock(&htab_write_mutex); + return 0; + } + memcpy(htab_table[idx].str, str, safe_strlen(str)+1); + ++htab_filled; + usbi_mutex_unlock(&htab_write_mutex); + + return idx; +} + +/* + * Returns the session ID of a device's nth level ancestor + * If there's no device at the nth level, return 0 + */ +static unsigned long get_ancestor_session_id(DWORD devinst, unsigned level) +{ + DWORD parent_devinst; + unsigned long session_id = 0; + char* sanitized_path = NULL; + char path[MAX_PATH_LENGTH]; + unsigned i; + + if (level < 1) return 0; + for (i = 0; idev); + struct libusb_config_descriptor *conf_desc; + const struct libusb_interface_descriptor *if_desc; + struct libusb_context *ctx = DEVICE_CTX(dev_handle->dev); + + r = libusb_get_config_descriptor(dev_handle->dev, 0, &conf_desc); + if (r != LIBUSB_SUCCESS) { + usbi_warn(ctx, "could not read config descriptor: error %d", r); + return r; + } + + if_desc = &conf_desc->interface[iface].altsetting[altsetting]; + safe_free(priv->usb_interface[iface].endpoint); + + if (if_desc->bNumEndpoints == 0) { + usbi_dbg("no endpoints found for interface %d", iface); + return LIBUSB_SUCCESS; + } + + priv->usb_interface[iface].endpoint = (uint8_t*) calloc(1, if_desc->bNumEndpoints); + if (priv->usb_interface[iface].endpoint == NULL) { + return LIBUSB_ERROR_NO_MEM; + } + + priv->usb_interface[iface].nb_endpoints = if_desc->bNumEndpoints; + for (i=0; ibNumEndpoints; i++) { + priv->usb_interface[iface].endpoint[i] = if_desc->endpoint[i].bEndpointAddress; + usbi_dbg("(re)assigned endpoint %02X to interface %d", priv->usb_interface[iface].endpoint[i], iface); + } + libusb_free_config_descriptor(conf_desc); + + // Extra init is required for WinUSB endpoints + if (priv->apib->id == USB_API_WINUSB) { + return winusb_configure_endpoints(dev_handle, iface); + } + + return LIBUSB_SUCCESS; +} + +// Lookup for a match in the list of API driver names +static bool is_api_driver(char* driver, uint8_t api) +{ + uint8_t i; + const char sep_str[2] = {LIST_SEPARATOR, 0}; + char *tok, *tmp_str; + size_t len = safe_strlen(driver); + + if (len == 0) return false; + tmp_str = (char*) calloc(1, len+1); + if (tmp_str == NULL) return false; + memcpy(tmp_str, driver, len+1); + tok = strtok(tmp_str, sep_str); + while (tok != NULL) { + for (i=0; idev_handle->dev); + struct windows_device_handle_priv *handle_priv = _device_handle_priv( + transfer->dev_handle); + struct windows_device_priv *priv = _device_priv(transfer->dev_handle->dev); + int current_interface = *interface_number; + int r = LIBUSB_SUCCESS; + + usbi_mutex_lock(&autoclaim_lock); + if (current_interface < 0) // No serviceable interface was found + { + for (current_interface=0; current_interfaceusb_interface[current_interface].apib->id == api_type) + && (libusb_claim_interface(transfer->dev_handle, current_interface) == LIBUSB_SUCCESS) ) { + usbi_dbg("auto-claimed interface %d for control request", current_interface); + if (handle_priv->autoclaim_count[current_interface] != 0) { + usbi_warn(ctx, "program assertion failed - autoclaim_count was nonzero"); + } + handle_priv->autoclaim_count[current_interface]++; + break; + } + } + if (current_interface == USB_MAXINTERFACES) { + usbi_err(ctx, "could not auto-claim any interface"); + r = LIBUSB_ERROR_NOT_FOUND; + } + } else { + // If we have a valid interface that was autoclaimed, we must increment + // its autoclaim count so that we can prevent an early release. + if (handle_priv->autoclaim_count[current_interface] != 0) { + handle_priv->autoclaim_count[current_interface]++; + } + } + usbi_mutex_unlock(&autoclaim_lock); + + *interface_number = current_interface; + return r; + +} + +static void auto_release(struct usbi_transfer *itransfer) +{ + struct windows_transfer_priv *transfer_priv = (struct windows_transfer_priv*)usbi_transfer_get_os_priv(itransfer); + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + libusb_device_handle *dev_handle = transfer->dev_handle; + struct windows_device_handle_priv* handle_priv = _device_handle_priv(dev_handle); + int r; + + usbi_mutex_lock(&autoclaim_lock); + if (handle_priv->autoclaim_count[transfer_priv->interface_number] > 0) { + handle_priv->autoclaim_count[transfer_priv->interface_number]--; + if (handle_priv->autoclaim_count[transfer_priv->interface_number] == 0) { + r = libusb_release_interface(dev_handle, transfer_priv->interface_number); + if (r == LIBUSB_SUCCESS) { + usbi_dbg("auto-released interface %d", transfer_priv->interface_number); + } else { + usbi_dbg("failed to auto-release interface %d (%s)", + transfer_priv->interface_number, libusb_error_name((enum libusb_error)r)); + } + } + } + usbi_mutex_unlock(&autoclaim_lock); +} + +/* + * init: libusb backend init function + * + * This function enumerates the HCDs (Host Controller Drivers) and populates our private HCD list + * In our implementation, we equate Windows' "HCD" to LibUSB's "bus". Note that bus is zero indexed. + * HCDs are not expected to change after init (might not hold true for hot pluggable USB PCI card?) + */ +static int windows_init(struct libusb_context *ctx) +{ + int i, r = LIBUSB_ERROR_OTHER; + OSVERSIONINFO os_version; + HANDLE semaphore; + char sem_name[11+1+8]; // strlen(libusb_init)+'\0'+(32-bit hex PID) + + sprintf(sem_name, "libusb_init%08X", (unsigned int)GetCurrentProcessId()&0xFFFFFFFF); + semaphore = CreateSemaphoreA(NULL, 1, 1, sem_name); + if (semaphore == NULL) { + usbi_err(ctx, "could not create semaphore: %s", windows_error_str(0)); + return LIBUSB_ERROR_NO_MEM; + } + + // A successful wait brings our semaphore count to 0 (unsignaled) + // => any concurent wait stalls until the semaphore's release + if (WaitForSingleObject(semaphore, INFINITE) != WAIT_OBJECT_0) { + usbi_err(ctx, "failure to access semaphore: %s", windows_error_str(0)); + CloseHandle(semaphore); + return LIBUSB_ERROR_NO_MEM; + } + + // NB: concurrent usage supposes that init calls are equally balanced with + // exit calls. If init is called more than exit, we will not exit properly + if ( ++concurrent_usage == 0 ) { // First init? + // Detect OS version + memset(&os_version, 0, sizeof(OSVERSIONINFO)); + os_version.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + windows_version = WINDOWS_UNSUPPORTED; + if ((GetVersionEx(&os_version) != 0) && (os_version.dwPlatformId == VER_PLATFORM_WIN32_NT)) { + if ((os_version.dwMajorVersion == 5) && (os_version.dwMinorVersion == 1)) { + windows_version = WINDOWS_XP; + } else if ((os_version.dwMajorVersion == 5) && (os_version.dwMinorVersion == 2)) { + windows_version = WINDOWS_2003; // also includes XP 64 + } else if (os_version.dwMajorVersion >= 6) { + windows_version = WINDOWS_VISTA_AND_LATER; + } + } + if (windows_version == WINDOWS_UNSUPPORTED) { + usbi_err(ctx, "This version of Windows is NOT supported"); + r = LIBUSB_ERROR_NOT_SUPPORTED; + goto init_exit; + } + + // We need a lock for proper auto-release + usbi_mutex_init(&autoclaim_lock, NULL); + + // Initialize pollable file descriptors + init_polling(); + + // Load DLL imports + if (init_dlls() != LIBUSB_SUCCESS) { + usbi_err(ctx, "could not resolve DLL functions"); + return LIBUSB_ERROR_NOT_FOUND; + } + + // Initialize the low level APIs (we don't care about errors at this stage) + for (i=0; inum_configurations = 1; + priv->dev_descriptor.bLength = sizeof(USB_DEVICE_DESCRIPTOR); + priv->dev_descriptor.bDescriptorType = USB_DEVICE_DESCRIPTOR_TYPE; + priv->dev_descriptor.bNumConfigurations = 1; + priv->active_config = 1; + + if (priv->parent_dev == NULL) { + usbi_err(ctx, "program assertion failed - HCD hub has no parent"); + return LIBUSB_ERROR_NO_DEVICE; + } + parent_priv = _device_priv(priv->parent_dev); + if (sscanf(parent_priv->path, "\\\\.\\PCI#VEN_%04x&DEV_%04x%*s", &vid, &pid) == 2) { + priv->dev_descriptor.idVendor = (uint16_t)vid; + priv->dev_descriptor.idProduct = (uint16_t)pid; + } else { + usbi_warn(ctx, "could not infer VID/PID of HCD hub from '%s'", parent_priv->path); + priv->dev_descriptor.idVendor = 0x1d6b; // Linux Foundation root hub + priv->dev_descriptor.idProduct = 1; + } + return LIBUSB_SUCCESS; +} + +/* + * fetch and cache all the config descriptors through I/O + */ +static int cache_config_descriptors(struct libusb_device *dev, HANDLE hub_handle, char* device_id) +{ + DWORD size, ret_size; + struct libusb_context *ctx = DEVICE_CTX(dev); + struct windows_device_priv *priv = _device_priv(dev); + int r; + uint8_t i; + + USB_CONFIGURATION_DESCRIPTOR_SHORT cd_buf_short; // dummy request + PUSB_DESCRIPTOR_REQUEST cd_buf_actual = NULL; // actual request + PUSB_CONFIGURATION_DESCRIPTOR cd_data = NULL; + + if (dev->num_configurations == 0) + return LIBUSB_ERROR_INVALID_PARAM; + + priv->config_descriptor = (unsigned char**) calloc(dev->num_configurations, sizeof(PUSB_CONFIGURATION_DESCRIPTOR)); + if (priv->config_descriptor == NULL) + return LIBUSB_ERROR_NO_MEM; + for (i=0; inum_configurations; i++) + priv->config_descriptor[i] = NULL; + + for (i=0, r=LIBUSB_SUCCESS; ; i++) + { + // safe loop: release all dynamic resources + safe_free(cd_buf_actual); + + // safe loop: end of loop condition + if ((i >= dev->num_configurations) || (r != LIBUSB_SUCCESS)) + break; + + size = sizeof(USB_CONFIGURATION_DESCRIPTOR_SHORT); + memset(&cd_buf_short, 0, size); + + cd_buf_short.req.ConnectionIndex = (ULONG)priv->port; + cd_buf_short.req.SetupPacket.bmRequest = LIBUSB_ENDPOINT_IN; + cd_buf_short.req.SetupPacket.bRequest = USB_REQUEST_GET_DESCRIPTOR; + cd_buf_short.req.SetupPacket.wValue = (USB_CONFIGURATION_DESCRIPTOR_TYPE << 8) | i; + cd_buf_short.req.SetupPacket.wIndex = i; + cd_buf_short.req.SetupPacket.wLength = (USHORT)(size - sizeof(USB_DESCRIPTOR_REQUEST)); + + // Dummy call to get the required data size + if (!DeviceIoControl(hub_handle, IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION, &cd_buf_short, size, + &cd_buf_short, size, &ret_size, NULL)) { + usbi_err(ctx, "could not access configuration descriptor (dummy) for '%s': %s", device_id, windows_error_str(0)); + LOOP_BREAK(LIBUSB_ERROR_IO); + } + + if ((ret_size != size) || (cd_buf_short.data.wTotalLength < sizeof(USB_CONFIGURATION_DESCRIPTOR))) { + usbi_err(ctx, "unexpected configuration descriptor size (dummy) for '%s'.", device_id); + LOOP_BREAK(LIBUSB_ERROR_IO); + } + + size = sizeof(USB_DESCRIPTOR_REQUEST) + cd_buf_short.data.wTotalLength; + if ((cd_buf_actual = (PUSB_DESCRIPTOR_REQUEST) calloc(1, size)) == NULL) { + usbi_err(ctx, "could not allocate configuration descriptor buffer for '%s'.", device_id); + LOOP_BREAK(LIBUSB_ERROR_NO_MEM); + } + memset(cd_buf_actual, 0, size); + + // Actual call + cd_buf_actual->ConnectionIndex = (ULONG)priv->port; + cd_buf_actual->SetupPacket.bmRequest = LIBUSB_ENDPOINT_IN; + cd_buf_actual->SetupPacket.bRequest = USB_REQUEST_GET_DESCRIPTOR; + cd_buf_actual->SetupPacket.wValue = (USB_CONFIGURATION_DESCRIPTOR_TYPE << 8) | i; + cd_buf_actual->SetupPacket.wIndex = i; + cd_buf_actual->SetupPacket.wLength = (USHORT)(size - sizeof(USB_DESCRIPTOR_REQUEST)); + + if (!DeviceIoControl(hub_handle, IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION, cd_buf_actual, size, + cd_buf_actual, size, &ret_size, NULL)) { + usbi_err(ctx, "could not access configuration descriptor (actual) for '%s': %s", device_id, windows_error_str(0)); + LOOP_BREAK(LIBUSB_ERROR_IO); + } + + cd_data = (PUSB_CONFIGURATION_DESCRIPTOR)((UCHAR*)cd_buf_actual+sizeof(USB_DESCRIPTOR_REQUEST)); + + if ((size != ret_size) || (cd_data->wTotalLength != cd_buf_short.data.wTotalLength)) { + usbi_err(ctx, "unexpected configuration descriptor size (actual) for '%s'.", device_id); + LOOP_BREAK(LIBUSB_ERROR_IO); + } + + if (cd_data->bDescriptorType != USB_CONFIGURATION_DESCRIPTOR_TYPE) { + usbi_err(ctx, "not a configuration descriptor for '%s'", device_id); + LOOP_BREAK(LIBUSB_ERROR_IO); + } + + usbi_dbg("cached config descriptor %d (bConfigurationValue=%d, %d bytes)", + i, cd_data->bConfigurationValue, cd_data->wTotalLength); + + // Cache the descriptor + priv->config_descriptor[i] = (unsigned char*) calloc(1, cd_data->wTotalLength); + if (priv->config_descriptor[i] == NULL) + return LIBUSB_ERROR_NO_MEM; + memcpy(priv->config_descriptor[i], cd_data, cd_data->wTotalLength); + } + return LIBUSB_SUCCESS; +} + +/* + * Populate a libusb device structure + */ +static int init_device(struct libusb_device* dev, struct libusb_device* parent_dev, + uint8_t port_number, char* device_id, DWORD devinst) +{ + HANDLE handle; + DWORD size; + USB_NODE_CONNECTION_INFORMATION_EX conn_info; + struct windows_device_priv *priv, *parent_priv; + struct libusb_context *ctx = DEVICE_CTX(dev); + struct libusb_device* tmp_dev; + unsigned i; + + if ((dev == NULL) || (parent_dev == NULL)) { + return LIBUSB_ERROR_NOT_FOUND; + } + priv = _device_priv(dev); + parent_priv = _device_priv(parent_dev); + if (parent_priv->apib->id != USB_API_HUB) { + usbi_warn(ctx, "parent for device '%s' is not a hub", device_id); + return LIBUSB_ERROR_NOT_FOUND; + } + + // It is possible for the parent hub not to have been initialized yet + // If that's the case, lookup the ancestors to set the bus number + if (parent_dev->bus_number == 0) { + for (i=2; ; i++) { + tmp_dev = usbi_get_device_by_session_id(ctx, get_ancestor_session_id(devinst, i)); + if (tmp_dev == NULL) break; + if (tmp_dev->bus_number != 0) { + usbi_dbg("got bus number from ancestor #%d", i); + parent_dev->bus_number = tmp_dev->bus_number; + break; + } + } + } + if (parent_dev->bus_number == 0) { + usbi_err(ctx, "program assertion failed: unable to find ancestor bus number for '%s'", device_id); + return LIBUSB_ERROR_NOT_FOUND; + } + dev->bus_number = parent_dev->bus_number; + priv->port = port_number; + priv->depth = parent_priv->depth + 1; + priv->parent_dev = parent_dev; + + // If the device address is already set, we can stop here + if (dev->device_address != 0) { + return LIBUSB_SUCCESS; + } + memset(&conn_info, 0, sizeof(conn_info)); + if (priv->depth != 0) { // Not a HCD hub + handle = CreateFileA(parent_priv->path, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, NULL); + if (handle == INVALID_HANDLE_VALUE) { + usbi_warn(ctx, "could not open hub %s: %s", parent_priv->path, windows_error_str(0)); + return LIBUSB_ERROR_ACCESS; + } + size = sizeof(conn_info); + conn_info.ConnectionIndex = (ULONG)port_number; + if (!DeviceIoControl(handle, IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX, &conn_info, size, + &conn_info, size, &size, NULL)) { + usbi_warn(ctx, "could not get node connection information for device '%s': %s", + device_id, windows_error_str(0)); + safe_closehandle(handle); + return LIBUSB_ERROR_NO_DEVICE; + } + if (conn_info.ConnectionStatus == NoDeviceConnected) { + usbi_err(ctx, "device '%s' is no longer connected!", device_id); + safe_closehandle(handle); + return LIBUSB_ERROR_NO_DEVICE; + } + memcpy(&priv->dev_descriptor, &(conn_info.DeviceDescriptor), sizeof(USB_DEVICE_DESCRIPTOR)); + dev->num_configurations = priv->dev_descriptor.bNumConfigurations; + priv->active_config = conn_info.CurrentConfigurationValue; + usbi_dbg("found %d configurations (active conf: %d)", dev->num_configurations, priv->active_config); + // If we can't read the config descriptors, just set the number of confs to zero + if (cache_config_descriptors(dev, handle, device_id) != LIBUSB_SUCCESS) { + dev->num_configurations = 0; + priv->dev_descriptor.bNumConfigurations = 0; + } + safe_closehandle(handle); + + if (conn_info.DeviceAddress > UINT8_MAX) { + usbi_err(ctx, "program assertion failed: device address overflow"); + } + dev->device_address = (uint8_t)conn_info.DeviceAddress; + switch (conn_info.Speed) { + case 0: dev->speed = LIBUSB_SPEED_LOW; break; + case 1: dev->speed = LIBUSB_SPEED_FULL; break; + case 2: dev->speed = LIBUSB_SPEED_HIGH; break; + case 3: dev->speed = LIBUSB_SPEED_SUPER; break; + default: + usbi_warn(ctx, "Got unknown device speed %d", conn_info.Speed); + break; + } + } else { + dev->device_address = UINT8_MAX; // Hubs from HCD have a devaddr of 255 + force_hcd_device_descriptor(dev); + } + + usbi_sanitize_device(dev); + + usbi_dbg("(bus: %d, addr: %d, depth: %d, port: %d): '%s'", + dev->bus_number, dev->device_address, priv->depth, priv->port, device_id); + + return LIBUSB_SUCCESS; +} + +// Returns the api type, or 0 if not found/unsupported +static uint8_t get_api_type(struct libusb_context *ctx, + HDEVINFO *dev_info, SP_DEVINFO_DATA *dev_info_data) +{ + // Precedence for filter drivers vs driver is in the order of this array + struct driver_lookup lookup[3] = { + {"\0\0", SPDRP_SERVICE, "driver"}, + {"\0\0", SPDRP_UPPERFILTERS, "upper filter driver"}, + {"\0\0", SPDRP_LOWERFILTERS, "lower filter driver"} + }; + DWORD size, reg_type; + unsigned k, l; + uint8_t api; + + // Check the service & filter names to know the API we should use + for (k=0; k<3; k++) { + if (pSetupDiGetDeviceRegistryPropertyA(*dev_info, dev_info_data, lookup[k].reg_prop, + ®_type, (BYTE*)lookup[k].list, MAX_KEY_LENGTH, &size)) { + // Turn the REG_SZ SPDRP_SERVICE into REG_MULTI_SZ + if (lookup[k].reg_prop == SPDRP_SERVICE) { + // our buffers are MAX_KEY_LENGTH+1 so we can overflow if needed + lookup[k].list[safe_strlen(lookup[k].list)+1] = 0; + } + // MULTI_SZ is a pain to work with. Turn it into something much more manageable + // NB: none of the driver names we check against contain LIST_SEPARATOR, + // (currently ';'), so even if an unsuported one does, it's not an issue + for (l=0; (lookup[k].list[l] != 0) || (lookup[k].list[l+1] != 0); l++) { + if (lookup[k].list[l] == 0) { + lookup[k].list[l] = LIST_SEPARATOR; + } + } + upperize(lookup[k].list); + usbi_dbg("%s(s): %s", lookup[k].designation, lookup[k].list); + } else { + if (GetLastError() != ERROR_INVALID_DATA) { + usbi_dbg("could not access %s: %s", lookup[k].designation, windows_error_str(0)); + } + lookup[k].list[0] = 0; + } + } + + for (api=1; api= 3) continue; + return api; + } + return 0; +} + +static int set_composite_interface(struct libusb_context* ctx, struct libusb_device* dev, + char* dev_interface_path, char* device_id, uint8_t api) +{ + unsigned i; + struct windows_device_priv *priv = _device_priv(dev); + int interface_number; + + if (priv->apib->id != USB_API_COMPOSITE) { + usbi_err(ctx, "program assertion failed: '%s' is not composite", device_id); + return LIBUSB_ERROR_NO_DEVICE; + } + + // Because MI_## are not necessarily in sequential order (some composite + // devices will have only MI_00 & MI_03 for instance), we retrieve the actual + // interface number from the path's MI value + interface_number = 0; + for (i=0; device_id[i] != 0; ) { + if ( (device_id[i++] == 'M') && (device_id[i++] == 'I') + && (device_id[i++] == '_') ) { + interface_number = (device_id[i++] - '0')*10; + interface_number += device_id[i] - '0'; + break; + } + } + + if (device_id[i] == 0) { + usbi_warn(ctx, "failure to read interface number for %s. Using default value %d", + device_id, interface_number); + } + + if (priv->usb_interface[interface_number].path != NULL) { + usbi_warn(ctx, "interface[%d] already set - ignoring: %s", interface_number, device_id); + return LIBUSB_ERROR_ACCESS; + } + + usbi_dbg("interface[%d] = %s", interface_number, dev_interface_path); + priv->usb_interface[interface_number].path = dev_interface_path; + priv->usb_interface[interface_number].apib = &usb_api_backend[api]; + priv->composite_api_flags |= 1<DevicePath); + if (dev_interface_path == NULL) { + usbi_warn(ctx, "could not sanitize device interface path for '%s'", dev_interface_details->DevicePath); + continue; + } + } + } else { + // Workaround for a Nec/Renesas USB 3.0 driver bug where root hubs are + // being listed under the "NUSB3" PnP Symbolic Name rather than "USB" + while ( (class_index < 2) && + (!(b = get_devinfo_data(ctx, &dev_info, &dev_info_data, usb_class[class_index], i))) ) { + class_index++; + i = 0; + } + if (!b) break; + } + + // Read the Device ID path. This is what we'll use as UID + // Note that if the device is plugged in a different port or hub, the Device ID changes + if (CM_Get_Device_IDA(dev_info_data.DevInst, path, sizeof(path), 0) != CR_SUCCESS) { + usbi_warn(ctx, "could not read the device id path for devinst %X, skipping", + dev_info_data.DevInst); + continue; + } + dev_id_path = sanitize_path(path); + if (dev_id_path == NULL) { + usbi_warn(ctx, "could not sanitize device id path for devinst %X, skipping", + dev_info_data.DevInst); + continue; + } +#ifdef ENUM_DEBUG + usbi_dbg("PRO: %s", dev_id_path); +#endif + + // The SPDRP_ADDRESS for USB devices is the device port number on the hub + port_nr = 0; + if ((pass >= HUB_PASS) && (pass <= GEN_PASS)) { + if ( (!pSetupDiGetDeviceRegistryPropertyA(dev_info, &dev_info_data, SPDRP_ADDRESS, + ®_type, (BYTE*)&port_nr, 4, &size)) + || (size != 4) ) { + usbi_warn(ctx, "could not retrieve port number for device '%s', skipping: %s", + dev_id_path, windows_error_str(0)); + continue; + } + } + + // Set API to use or get additional data from generic pass + api = USB_API_UNSUPPORTED; + switch (pass) { + case HCD_PASS: + break; + case GEN_PASS: + // We use the GEN pass to detect driverless devices... + size = sizeof(strbuf); + if (!pSetupDiGetDeviceRegistryPropertyA(dev_info, &dev_info_data, SPDRP_DRIVER, + ®_type, (BYTE*)strbuf, size, &size)) { + usbi_info(ctx, "The following device has no driver: '%s'", dev_id_path); + usbi_info(ctx, "libusb will not be able to access it."); + } + // ...and to add the additional device interface GUIDs + key = pSetupDiOpenDevRegKey(dev_info, &dev_info_data, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ); + if (key != INVALID_HANDLE_VALUE) { + size = sizeof(guid_string_w); + s = pRegQueryValueExW(key, L"DeviceInterfaceGUIDs", NULL, ®_type, + (BYTE*)guid_string_w, &size); + pRegCloseKey(key); + if (s == ERROR_SUCCESS) { + if (nb_guids >= MAX_ENUM_GUIDS) { + // If this assert is ever reported, grow a GUID table dynamically + usbi_err(ctx, "program assertion failed: too many GUIDs"); + LOOP_BREAK(LIBUSB_ERROR_OVERFLOW); + } + if_guid = (GUID*) calloc(1, sizeof(GUID)); + pCLSIDFromString(guid_string_w, if_guid); + guid[nb_guids++] = if_guid; + usbi_dbg("extra GUID: %s", guid_to_string(if_guid)); + } + } + break; + default: + // Get the API type (after checking that the driver installation is OK) + if ( (!pSetupDiGetDeviceRegistryPropertyA(dev_info, &dev_info_data, SPDRP_INSTALL_STATE, + ®_type, (BYTE*)&install_state, 4, &size)) + || (size != 4) ){ + usbi_warn(ctx, "could not detect installation state of driver for '%s': %s", + dev_id_path, windows_error_str(0)); + } else if (install_state != 0) { + usbi_warn(ctx, "driver for device '%s' is reporting an issue (code: %d) - skipping", + dev_id_path, install_state); + continue; + } + api = get_api_type(ctx, &dev_info, &dev_info_data); + break; + } + + // Find parent device (for the passes that need it) + switch (pass) { + case HCD_PASS: + case DEV_PASS: + case HUB_PASS: + break; + default: + // Go through the ancestors until we see a face we recognize + parent_dev = NULL; + for (ancestor = 1; parent_dev == NULL; ancestor++) { + session_id = get_ancestor_session_id(dev_info_data.DevInst, ancestor); + if (session_id == 0) { + break; + } + parent_dev = usbi_get_device_by_session_id(ctx, session_id); + } + if (parent_dev == NULL) { + usbi_dbg("unlisted ancestor for '%s' (newly connected, etc.) - ignoring", dev_id_path); + continue; + } + parent_priv = _device_priv(parent_dev); + // virtual USB devices are also listed during GEN - don't process these yet + if ( (pass == GEN_PASS) && (parent_priv->apib->id != USB_API_HUB) ) { + continue; + } + break; + } + + // Create new or match existing device, using the (hashed) device_id as session id + if (pass <= DEV_PASS) { // For subsequent passes, we'll lookup the parent + // These are the passes that create "new" devices + session_id = htab_hash(dev_id_path); + dev = usbi_get_device_by_session_id(ctx, session_id); + if (dev == NULL) { + if (pass == DEV_PASS) { + // This can occur if the OS only reports a newly plugged device after we started enum + usbi_warn(ctx, "'%s' was only detected in late pass (newly connected device?)" + " - ignoring", dev_id_path); + continue; + } + usbi_dbg("allocating new device for session [%X]", session_id); + if ((dev = usbi_alloc_device(ctx, session_id)) == NULL) { + LOOP_BREAK(LIBUSB_ERROR_NO_MEM); + } + windows_device_priv_init(dev); + // Keep track of devices that need unref + unref_list[unref_cur++] = dev; + if (unref_cur >= unref_size) { + unref_size += 64; + unref_list = realloc(unref_list, unref_size*sizeof(libusb_device*)); + if (unref_list == NULL) { + usbi_err(ctx, "could not realloc list for unref - aborting."); + LOOP_BREAK(LIBUSB_ERROR_NO_MEM); + } + } + } else { + usbi_dbg("found existing device for session [%X] (%d.%d)", + session_id, dev->bus_number, dev->device_address); + } + priv = _device_priv(dev); + } + + // Setup device + switch (pass) { + case HCD_PASS: + dev->bus_number = (uint8_t)(i + 1); // bus 0 is reserved for disconnected + dev->device_address = 0; + dev->num_configurations = 0; + priv->apib = &usb_api_backend[USB_API_HUB]; + priv->depth = UINT8_MAX; // Overflow to 0 for HCD Hubs + priv->path = dev_interface_path; dev_interface_path = NULL; + break; + case HUB_PASS: + case DEV_PASS: + // If the device has already been setup, don't do it again + if (priv->path != NULL) + break; + // Take care of API initialization + priv->path = dev_interface_path; dev_interface_path = NULL; + priv->apib = &usb_api_backend[api]; + switch(api) { + case USB_API_COMPOSITE: + case USB_API_HUB: + break; + default: + // For other devices, the first interface is the same as the device + priv->usb_interface[0].path = (char*) calloc(safe_strlen(priv->path)+1, 1); + if (priv->usb_interface[0].path != NULL) { + safe_strcpy(priv->usb_interface[0].path, safe_strlen(priv->path)+1, priv->path); + } else { + usbi_warn(ctx, "could not duplicate interface path '%s'", priv->path); + } + // The following is needed if we want API calls to work for both simple + // and composite devices. + for(j=0; jusb_interface[j].apib = &usb_api_backend[api]; + } + break; + } + break; + case GEN_PASS: + r = init_device(dev, parent_dev, (uint8_t)port_nr, dev_id_path, dev_info_data.DevInst); + if (r == LIBUSB_SUCCESS) { + // Append device to the list of discovered devices + discdevs = discovered_devs_append(*_discdevs, dev); + if (!discdevs) { + LOOP_BREAK(LIBUSB_ERROR_NO_MEM); + } + *_discdevs = discdevs; + } else if (r == LIBUSB_ERROR_NO_DEVICE) { + // This can occur if the device was disconnected but Windows hasn't + // refreshed its enumeration yet - in that case, we ignore the device + r = LIBUSB_SUCCESS; + } + break; + default: // later passes + if (parent_priv->apib->id == USB_API_COMPOSITE) { + usbi_dbg("setting composite interface for [%lX]:", parent_dev->session_data); + switch (set_composite_interface(ctx, parent_dev, dev_interface_path, dev_id_path, api)) { + case LIBUSB_SUCCESS: + dev_interface_path = NULL; + break; + case LIBUSB_ERROR_ACCESS: + // interface has already been set => make sure dev_interface_path is freed then + break; + default: + LOOP_BREAK(r); + break; + } + } + break; + } + } + } + + // Free any additional GUIDs + for (pass = DEV_PASS+1; pass < nb_guids; pass++) { + safe_free(guid[pass]); + } + + // Unref newly allocated devs + for (i=0; i any concurent wait stalls until the semaphore release + if (WaitForSingleObject(semaphore, INFINITE) != WAIT_OBJECT_0) { + CloseHandle(semaphore); + return; + } + + // Only works if exits and inits are balanced exactly + if (--concurrent_usage < 0) { // Last exit + for (i=0; idev_descriptor), DEVICE_DESC_LENGTH); + *host_endian = 0; + + return LIBUSB_SUCCESS; +} + +static int windows_get_config_descriptor(struct libusb_device *dev, uint8_t config_index, unsigned char *buffer, size_t len, int *host_endian) +{ + struct windows_device_priv *priv = _device_priv(dev); + PUSB_CONFIGURATION_DESCRIPTOR config_header; + size_t size; + + // config index is zero based + if (config_index >= dev->num_configurations) + return LIBUSB_ERROR_INVALID_PARAM; + + if ((priv->config_descriptor == NULL) || (priv->config_descriptor[config_index] == NULL)) + return LIBUSB_ERROR_NOT_FOUND; + + config_header = (PUSB_CONFIGURATION_DESCRIPTOR)priv->config_descriptor[config_index]; + + size = min(config_header->wTotalLength, len); + memcpy(buffer, priv->config_descriptor[config_index], size); + + return LIBUSB_SUCCESS; +} + +/* + * return the cached copy of the active config descriptor + */ +static int windows_get_active_config_descriptor(struct libusb_device *dev, unsigned char *buffer, size_t len, int *host_endian) +{ + struct windows_device_priv *priv = _device_priv(dev); + + if (priv->active_config == 0) + return LIBUSB_ERROR_NOT_FOUND; + + // config index is zero based + return windows_get_config_descriptor(dev, (uint8_t)(priv->active_config-1), buffer, len, host_endian); +} + +static int windows_open(struct libusb_device_handle *dev_handle) +{ + struct windows_device_priv *priv = _device_priv(dev_handle->dev); + struct libusb_context *ctx = DEVICE_CTX(dev_handle->dev); + + if (priv->apib == NULL) { + usbi_err(ctx, "program assertion failed - device is not initialized"); + return LIBUSB_ERROR_NO_DEVICE; + } + + return priv->apib->open(dev_handle); +} + +static void windows_close(struct libusb_device_handle *dev_handle) +{ + struct windows_device_priv *priv = _device_priv(dev_handle->dev); + + priv->apib->close(dev_handle); +} + +static int windows_get_configuration(struct libusb_device_handle *dev_handle, int *config) +{ + struct windows_device_priv *priv = _device_priv(dev_handle->dev); + + if (priv->active_config == 0) { + *config = 0; + return LIBUSB_ERROR_NOT_FOUND; + } + + *config = priv->active_config; + return LIBUSB_SUCCESS; +} + +/* + * from http://msdn.microsoft.com/en-us/library/ms793522.aspx: "The port driver + * does not currently expose a service that allows higher-level drivers to set + * the configuration." + */ +static int windows_set_configuration(struct libusb_device_handle *dev_handle, int config) +{ + struct windows_device_priv *priv = _device_priv(dev_handle->dev); + int r = LIBUSB_SUCCESS; + + if (config >= USB_MAXCONFIG) + return LIBUSB_ERROR_INVALID_PARAM; + + r = libusb_control_transfer(dev_handle, LIBUSB_ENDPOINT_OUT | + LIBUSB_REQUEST_TYPE_STANDARD | LIBUSB_RECIPIENT_DEVICE, + LIBUSB_REQUEST_SET_CONFIGURATION, (uint16_t)config, + 0, NULL, 0, 1000); + + if (r == LIBUSB_SUCCESS) { + priv->active_config = (uint8_t)config; + } + return r; +} + +static int windows_claim_interface(struct libusb_device_handle *dev_handle, int iface) +{ + int r = LIBUSB_SUCCESS; + struct windows_device_priv *priv = _device_priv(dev_handle->dev); + + if (iface >= USB_MAXINTERFACES) + return LIBUSB_ERROR_INVALID_PARAM; + + safe_free(priv->usb_interface[iface].endpoint); + priv->usb_interface[iface].nb_endpoints= 0; + + r = priv->apib->claim_interface(dev_handle, iface); + + if (r == LIBUSB_SUCCESS) { + r = windows_assign_endpoints(dev_handle, iface, 0); + } + + return r; +} + +static int windows_set_interface_altsetting(struct libusb_device_handle *dev_handle, int iface, int altsetting) +{ + int r = LIBUSB_SUCCESS; + struct windows_device_priv *priv = _device_priv(dev_handle->dev); + + safe_free(priv->usb_interface[iface].endpoint); + priv->usb_interface[iface].nb_endpoints= 0; + + r = priv->apib->set_interface_altsetting(dev_handle, iface, altsetting); + + if (r == LIBUSB_SUCCESS) { + r = windows_assign_endpoints(dev_handle, iface, altsetting); + } + + return r; +} + +static int windows_release_interface(struct libusb_device_handle *dev_handle, int iface) +{ + struct windows_device_priv *priv = _device_priv(dev_handle->dev); + + return priv->apib->release_interface(dev_handle, iface); +} + +static int windows_clear_halt(struct libusb_device_handle *dev_handle, unsigned char endpoint) +{ + struct windows_device_priv *priv = _device_priv(dev_handle->dev); + return priv->apib->clear_halt(dev_handle, endpoint); +} + +static int windows_reset_device(struct libusb_device_handle *dev_handle) +{ + struct windows_device_priv *priv = _device_priv(dev_handle->dev); + return priv->apib->reset_device(dev_handle); +} + +// The 3 functions below are unlikely to ever get supported on Windows +static int windows_kernel_driver_active(struct libusb_device_handle *dev_handle, int iface) +{ + return LIBUSB_ERROR_NOT_SUPPORTED; +} + +static int windows_attach_kernel_driver(struct libusb_device_handle *dev_handle, int iface) +{ + return LIBUSB_ERROR_NOT_SUPPORTED; +} + +static int windows_detach_kernel_driver(struct libusb_device_handle *dev_handle, int iface) +{ + return LIBUSB_ERROR_NOT_SUPPORTED; +} + +static void windows_destroy_device(struct libusb_device *dev) +{ + windows_device_priv_release(dev); +} + +static void windows_clear_transfer_priv(struct usbi_transfer *itransfer) +{ + struct windows_transfer_priv *transfer_priv = (struct windows_transfer_priv*)usbi_transfer_get_os_priv(itransfer); + + usbi_free_fd(transfer_priv->pollable_fd.fd); + // When auto claim is in use, attempt to release the auto-claimed interface + auto_release(itransfer); +} + +static int submit_bulk_transfer(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct libusb_context *ctx = DEVICE_CTX(transfer->dev_handle->dev); + struct windows_transfer_priv *transfer_priv = (struct windows_transfer_priv*)usbi_transfer_get_os_priv(itransfer); + struct windows_device_priv *priv = _device_priv(transfer->dev_handle->dev); + int r; + + r = priv->apib->submit_bulk_transfer(itransfer); + if (r != LIBUSB_SUCCESS) { + return r; + } + + usbi_add_pollfd(ctx, transfer_priv->pollable_fd.fd, + (short)(IS_XFERIN(transfer) ? POLLIN : POLLOUT)); + + itransfer->flags |= USBI_TRANSFER_UPDATED_FDS; + return LIBUSB_SUCCESS; +} + +static int submit_iso_transfer(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct libusb_context *ctx = DEVICE_CTX(transfer->dev_handle->dev); + struct windows_transfer_priv *transfer_priv = (struct windows_transfer_priv*)usbi_transfer_get_os_priv(itransfer); + struct windows_device_priv *priv = _device_priv(transfer->dev_handle->dev); + int r; + + r = priv->apib->submit_iso_transfer(itransfer); + if (r != LIBUSB_SUCCESS) { + return r; + } + + usbi_add_pollfd(ctx, transfer_priv->pollable_fd.fd, + (short)(IS_XFERIN(transfer) ? POLLIN : POLLOUT)); + + itransfer->flags |= USBI_TRANSFER_UPDATED_FDS; + return LIBUSB_SUCCESS; +} + +static int submit_control_transfer(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct libusb_context *ctx = DEVICE_CTX(transfer->dev_handle->dev); + struct windows_transfer_priv *transfer_priv = (struct windows_transfer_priv*)usbi_transfer_get_os_priv(itransfer); + struct windows_device_priv *priv = _device_priv(transfer->dev_handle->dev); + int r; + + r = priv->apib->submit_control_transfer(itransfer); + if (r != LIBUSB_SUCCESS) { + return r; + } + + usbi_add_pollfd(ctx, transfer_priv->pollable_fd.fd, POLLIN); + + itransfer->flags |= USBI_TRANSFER_UPDATED_FDS; + return LIBUSB_SUCCESS; + +} + +static int windows_submit_transfer(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + + switch (transfer->type) { + case LIBUSB_TRANSFER_TYPE_CONTROL: + return submit_control_transfer(itransfer); + case LIBUSB_TRANSFER_TYPE_BULK: + case LIBUSB_TRANSFER_TYPE_INTERRUPT: + return submit_bulk_transfer(itransfer); + case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS: + return submit_iso_transfer(itransfer); + default: + usbi_err(TRANSFER_CTX(transfer), "unknown endpoint type %d", transfer->type); + return LIBUSB_ERROR_INVALID_PARAM; + } +} + +static int windows_abort_control(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct windows_device_priv *priv = _device_priv(transfer->dev_handle->dev); + + return priv->apib->abort_control(itransfer); +} + +static int windows_abort_transfers(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct windows_device_priv *priv = _device_priv(transfer->dev_handle->dev); + + return priv->apib->abort_transfers(itransfer); +} + +static int windows_cancel_transfer(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + + switch (transfer->type) { + case LIBUSB_TRANSFER_TYPE_CONTROL: + return windows_abort_control(itransfer); + case LIBUSB_TRANSFER_TYPE_BULK: + case LIBUSB_TRANSFER_TYPE_INTERRUPT: + case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS: + return windows_abort_transfers(itransfer); + default: + usbi_err(ITRANSFER_CTX(itransfer), "unknown endpoint type %d", transfer->type); + return LIBUSB_ERROR_INVALID_PARAM; + } +} + +static void windows_transfer_callback(struct usbi_transfer *itransfer, uint32_t io_result, uint32_t io_size) +{ + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct windows_device_priv *priv = _device_priv(transfer->dev_handle->dev); + int status; + + usbi_dbg("handling I/O completion with errcode %d", io_result); + + switch(io_result) { + case NO_ERROR: + status = priv->apib->copy_transfer_data(itransfer, io_size); + break; + case ERROR_GEN_FAILURE: + usbi_dbg("detected endpoint stall"); + status = LIBUSB_TRANSFER_STALL; + break; + case ERROR_SEM_TIMEOUT: + usbi_dbg("detected semaphore timeout"); + status = LIBUSB_TRANSFER_TIMED_OUT; + break; + case ERROR_OPERATION_ABORTED: + if (itransfer->flags & USBI_TRANSFER_TIMED_OUT) { + usbi_dbg("detected timeout"); + status = LIBUSB_TRANSFER_TIMED_OUT; + } else { + usbi_dbg("detected operation aborted"); + status = LIBUSB_TRANSFER_CANCELLED; + } + break; + default: + usbi_err(ITRANSFER_CTX(itransfer), "detected I/O error: %s", windows_error_str(0)); + status = LIBUSB_TRANSFER_ERROR; + break; + } + windows_clear_transfer_priv(itransfer); // Cancel polling + usbi_handle_transfer_completion(itransfer, (enum libusb_transfer_status)status); +} + +static void windows_handle_callback (struct usbi_transfer *itransfer, uint32_t io_result, uint32_t io_size) +{ + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + + switch (transfer->type) { + case LIBUSB_TRANSFER_TYPE_CONTROL: + case LIBUSB_TRANSFER_TYPE_BULK: + case LIBUSB_TRANSFER_TYPE_INTERRUPT: + case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS: + windows_transfer_callback (itransfer, io_result, io_size); + break; + default: + usbi_err(ITRANSFER_CTX(itransfer), "unknown endpoint type %d", transfer->type); + } +} + +static int windows_handle_events(struct libusb_context *ctx, struct pollfd *fds, POLL_NFDS_TYPE nfds, int num_ready) +{ + struct windows_transfer_priv* transfer_priv = NULL; + POLL_NFDS_TYPE i = 0; + bool found = false; + struct usbi_transfer *transfer; + DWORD io_size, io_result; + + usbi_mutex_lock(&ctx->open_devs_lock); + for (i = 0; i < nfds && num_ready > 0; i++) { + + usbi_dbg("checking fd %d with revents = %04x", fds[i].fd, fds[i].revents); + + if (!fds[i].revents) { + continue; + } + + num_ready--; + + // Because a Windows OVERLAPPED is used for poll emulation, + // a pollable fd is created and stored with each transfer + usbi_mutex_lock(&ctx->flying_transfers_lock); + list_for_each_entry(transfer, &ctx->flying_transfers, list, struct usbi_transfer) { + transfer_priv = usbi_transfer_get_os_priv(transfer); + if (transfer_priv->pollable_fd.fd == fds[i].fd) { + found = true; + break; + } + } + usbi_mutex_unlock(&ctx->flying_transfers_lock); + + if (found) { + // Handle async requests that completed synchronously first + if (HasOverlappedIoCompletedSync(transfer_priv->pollable_fd.overlapped)) { + io_result = NO_ERROR; + io_size = (DWORD)transfer_priv->pollable_fd.overlapped->InternalHigh; + // Regular async overlapped + } else if (GetOverlappedResult(transfer_priv->pollable_fd.handle, + transfer_priv->pollable_fd.overlapped, &io_size, false)) { + io_result = NO_ERROR; + } else { + io_result = GetLastError(); + } + usbi_remove_pollfd(ctx, transfer_priv->pollable_fd.fd); + // let handle_callback free the event using the transfer wfd + // If you don't use the transfer wfd, you run a risk of trying to free a + // newly allocated wfd that took the place of the one from the transfer. + windows_handle_callback(transfer, io_result, io_size); + } else { + usbi_err(ctx, "could not find a matching transfer for fd %x", fds[i]); + return LIBUSB_ERROR_NOT_FOUND; + } + } + + usbi_mutex_unlock(&ctx->open_devs_lock); + return LIBUSB_SUCCESS; +} + +/* + * Monotonic and real time functions + */ +unsigned __stdcall windows_clock_gettime_threaded(void* param) +{ + LARGE_INTEGER hires_counter, li_frequency; + LONG nb_responses; + int timer_index; + + // Init - find out if we have access to a monotonic (hires) timer + if (!QueryPerformanceFrequency(&li_frequency)) { + usbi_dbg("no hires timer available on this platform"); + hires_frequency = 0; + hires_ticks_to_ps = UINT64_C(0); + } else { + hires_frequency = li_frequency.QuadPart; + // The hires frequency can go as high as 4 GHz, so we'll use a conversion + // to picoseconds to compute the tv_nsecs part in clock_gettime + hires_ticks_to_ps = UINT64_C(1000000000000) / hires_frequency; + usbi_dbg("hires timer available (Frequency: %I64u Hz)", hires_frequency); + } + + // Main loop - wait for requests + while (1) { + timer_index = WaitForMultipleObjects(2, timer_request, FALSE, INFINITE) - WAIT_OBJECT_0; + if ( (timer_index != 0) && (timer_index != 1) ) { + usbi_dbg("failure to wait on requests: %s", windows_error_str(0)); + continue; + } + if (request_count[timer_index] == 0) { + // Request already handled + ResetEvent(timer_request[timer_index]); + // There's still a possiblity that a thread sends a request between the + // time we test request_count[] == 0 and we reset the event, in which case + // the request would be ignored. The simple solution to that is to test + // request_count again and process requests if non zero. + if (request_count[timer_index] == 0) + continue; + } + switch (timer_index) { + case 0: + WaitForSingleObject(timer_mutex, INFINITE); + // Requests to this thread are for hires always + if (QueryPerformanceCounter(&hires_counter) != 0) { + timer_tp.tv_sec = (long)(hires_counter.QuadPart / hires_frequency); + timer_tp.tv_nsec = (long)(((hires_counter.QuadPart % hires_frequency)/1000) * hires_ticks_to_ps); + } else { + // Fallback to real-time if we can't get monotonic value + // Note that real-time clock does not wait on the mutex or this thread. + windows_clock_gettime(USBI_CLOCK_REALTIME, &timer_tp); + } + ReleaseMutex(timer_mutex); + + nb_responses = InterlockedExchange((LONG*)&request_count[0], 0); + if ( (nb_responses) + && (ReleaseSemaphore(timer_response, nb_responses, NULL) == 0) ) { + usbi_dbg("unable to release timer semaphore %d: %s", windows_error_str(0)); + } + continue; + case 1: // time to quit + usbi_dbg("timer thread quitting"); + return 0; + } + } + usbi_dbg("ERROR: broken timer thread"); + return 1; +} + +static int windows_clock_gettime(int clk_id, struct timespec *tp) +{ + FILETIME filetime; + ULARGE_INTEGER rtime; + DWORD r; + switch(clk_id) { + case USBI_CLOCK_MONOTONIC: + if (hires_frequency != 0) { + while (1) { + InterlockedIncrement((LONG*)&request_count[0]); + SetEvent(timer_request[0]); + r = WaitForSingleObject(timer_response, TIMER_REQUEST_RETRY_MS); + switch(r) { + case WAIT_OBJECT_0: + WaitForSingleObject(timer_mutex, INFINITE); + *tp = timer_tp; + ReleaseMutex(timer_mutex); + return LIBUSB_SUCCESS; + case WAIT_TIMEOUT: + usbi_dbg("could not obtain a timer value within reasonable timeframe - too much load?"); + break; // Retry until successful + default: + usbi_dbg("WaitForSingleObject failed: %s", windows_error_str(0)); + return LIBUSB_ERROR_OTHER; + } + } + } + // Fall through and return real-time if monotonic was not detected @ timer init + case USBI_CLOCK_REALTIME: + // We follow http://msdn.microsoft.com/en-us/library/ms724928%28VS.85%29.aspx + // with a predef epoch_time to have an epoch that starts at 1970.01.01 00:00 + // Note however that our resolution is bounded by the Windows system time + // functions and is at best of the order of 1 ms (or, usually, worse) + GetSystemTimeAsFileTime(&filetime); + rtime.LowPart = filetime.dwLowDateTime; + rtime.HighPart = filetime.dwHighDateTime; + rtime.QuadPart -= epoch_time; + tp->tv_sec = (long)(rtime.QuadPart / 10000000); + tp->tv_nsec = (long)((rtime.QuadPart % 10000000)*100); + return LIBUSB_SUCCESS; + default: + return LIBUSB_ERROR_INVALID_PARAM; + } +} + + +// NB: MSVC6 does not support named initializers. +const struct usbi_os_backend windows_backend = { + "Windows", + windows_init, + windows_exit, + + windows_get_device_list, + windows_open, + windows_close, + + windows_get_device_descriptor, + windows_get_active_config_descriptor, + windows_get_config_descriptor, + + windows_get_configuration, + windows_set_configuration, + windows_claim_interface, + windows_release_interface, + + windows_set_interface_altsetting, + windows_clear_halt, + windows_reset_device, + + windows_kernel_driver_active, + windows_detach_kernel_driver, + windows_attach_kernel_driver, + + windows_destroy_device, + + windows_submit_transfer, + windows_cancel_transfer, + windows_clear_transfer_priv, + + windows_handle_events, + + windows_clock_gettime, +#if defined(USBI_TIMERFD_AVAILABLE) + NULL, +#endif + sizeof(struct windows_device_priv), + sizeof(struct windows_device_handle_priv), + sizeof(struct windows_transfer_priv), + 0, +}; + + +/* + * USB API backends + */ +static int unsupported_init(struct libusb_context *ctx) { + return LIBUSB_SUCCESS; +} +static int unsupported_exit(void) { + return LIBUSB_SUCCESS; +} +static int unsupported_open(struct libusb_device_handle *dev_handle) { + PRINT_UNSUPPORTED_API(open); +} +static void unsupported_close(struct libusb_device_handle *dev_handle) { + usbi_dbg("unsupported API call for 'close'"); +} +static int unsupported_claim_interface(struct libusb_device_handle *dev_handle, int iface) { + PRINT_UNSUPPORTED_API(claim_interface); +} +static int unsupported_set_interface_altsetting(struct libusb_device_handle *dev_handle, int iface, int altsetting) { + PRINT_UNSUPPORTED_API(set_interface_altsetting); +} +static int unsupported_release_interface(struct libusb_device_handle *dev_handle, int iface) { + PRINT_UNSUPPORTED_API(release_interface); +} +static int unsupported_clear_halt(struct libusb_device_handle *dev_handle, unsigned char endpoint) { + PRINT_UNSUPPORTED_API(clear_halt); +} +static int unsupported_reset_device(struct libusb_device_handle *dev_handle) { + PRINT_UNSUPPORTED_API(reset_device); +} +static int unsupported_submit_bulk_transfer(struct usbi_transfer *itransfer) { + PRINT_UNSUPPORTED_API(submit_bulk_transfer); +} +static int unsupported_submit_iso_transfer(struct usbi_transfer *itransfer) { + PRINT_UNSUPPORTED_API(submit_iso_transfer); +} +static int unsupported_submit_control_transfer(struct usbi_transfer *itransfer) { + PRINT_UNSUPPORTED_API(submit_control_transfer); +} +static int unsupported_abort_control(struct usbi_transfer *itransfer) { + PRINT_UNSUPPORTED_API(abort_control); +} +static int unsupported_abort_transfers(struct usbi_transfer *itransfer) { + PRINT_UNSUPPORTED_API(abort_transfers); +} +static int unsupported_copy_transfer_data(struct usbi_transfer *itransfer, uint32_t io_size) { + PRINT_UNSUPPORTED_API(copy_transfer_data); +} + +// These names must be uppercase +const char* hub_driver_names[] = {"USBHUB", "USBHUB3", "USB3HUB", "NUSB3HUB", "RUSB3HUB", "FLXHCIH", "TIHUB3", "ETRONHUB3", "VIAHUB3", "ASMTHUB3", "IUSB3HUB", "VUSB3HUB", "AMDHUB30"}; +const char* composite_driver_names[] = {"USBCCGP"}; +const char* winusb_driver_names[] = {"WINUSB"}; +const struct windows_usb_api_backend usb_api_backend[USB_API_MAX] = { + { + USB_API_UNSUPPORTED, + "Unsupported API", + &CLASS_GUID_UNSUPPORTED, + NULL, + 0, + unsupported_init, + unsupported_exit, + unsupported_open, + unsupported_close, + unsupported_claim_interface, + unsupported_set_interface_altsetting, + unsupported_release_interface, + unsupported_clear_halt, + unsupported_reset_device, + unsupported_submit_bulk_transfer, + unsupported_submit_iso_transfer, + unsupported_submit_control_transfer, + unsupported_abort_control, + unsupported_abort_transfers, + unsupported_copy_transfer_data, + }, { + USB_API_HUB, + "HUB API", + &CLASS_GUID_UNSUPPORTED, + hub_driver_names, + sizeof(hub_driver_names)/sizeof(hub_driver_names[0]), + unsupported_init, + unsupported_exit, + unsupported_open, + unsupported_close, + unsupported_claim_interface, + unsupported_set_interface_altsetting, + unsupported_release_interface, + unsupported_clear_halt, + unsupported_reset_device, + unsupported_submit_bulk_transfer, + unsupported_submit_iso_transfer, + unsupported_submit_control_transfer, + unsupported_abort_control, + unsupported_abort_transfers, + unsupported_copy_transfer_data, + }, { + USB_API_COMPOSITE, + "Composite API", + &CLASS_GUID_COMPOSITE, + composite_driver_names, + sizeof(composite_driver_names)/sizeof(composite_driver_names[0]), + composite_init, + composite_exit, + composite_open, + composite_close, + composite_claim_interface, + composite_set_interface_altsetting, + composite_release_interface, + composite_clear_halt, + composite_reset_device, + composite_submit_bulk_transfer, + composite_submit_iso_transfer, + composite_submit_control_transfer, + composite_abort_control, + composite_abort_transfers, + composite_copy_transfer_data, + }, { + USB_API_WINUSB, + "WinUSB API", + &CLASS_GUID_LIBUSB_WINUSB, + winusb_driver_names, + sizeof(winusb_driver_names)/sizeof(winusb_driver_names[0]), + winusb_init, + winusb_exit, + winusb_open, + winusb_close, + winusb_claim_interface, + winusb_set_interface_altsetting, + winusb_release_interface, + winusb_clear_halt, + winusb_reset_device, + winusb_submit_bulk_transfer, + unsupported_submit_iso_transfer, + winusb_submit_control_transfer, + winusb_abort_control, + winusb_abort_transfers, + winusb_copy_transfer_data, + }, +}; + + +/* + * WinUSB API functions + */ +static int winusb_init(struct libusb_context *ctx) +{ + DLL_LOAD(winusb.dll, WinUsb_Initialize, TRUE); + DLL_LOAD(winusb.dll, WinUsb_Free, TRUE); + DLL_LOAD(winusb.dll, WinUsb_GetAssociatedInterface, TRUE); + DLL_LOAD(winusb.dll, WinUsb_GetDescriptor, TRUE); + DLL_LOAD(winusb.dll, WinUsb_QueryInterfaceSettings, TRUE); + DLL_LOAD(winusb.dll, WinUsb_QueryDeviceInformation, TRUE); + DLL_LOAD(winusb.dll, WinUsb_SetCurrentAlternateSetting, TRUE); + DLL_LOAD(winusb.dll, WinUsb_GetCurrentAlternateSetting, TRUE); + DLL_LOAD(winusb.dll, WinUsb_QueryPipe, TRUE); + DLL_LOAD(winusb.dll, WinUsb_SetPipePolicy, TRUE); + DLL_LOAD(winusb.dll, WinUsb_GetPipePolicy, TRUE); + DLL_LOAD(winusb.dll, WinUsb_ReadPipe, TRUE); + DLL_LOAD(winusb.dll, WinUsb_WritePipe, TRUE); + DLL_LOAD(winusb.dll, WinUsb_ControlTransfer, TRUE); + DLL_LOAD(winusb.dll, WinUsb_ResetPipe, TRUE); + DLL_LOAD(winusb.dll, WinUsb_AbortPipe, TRUE); + DLL_LOAD(winusb.dll, WinUsb_FlushPipe, TRUE); + + api_winusb_available = true; + return LIBUSB_SUCCESS; +} + +static int winusb_exit(void) +{ + return LIBUSB_SUCCESS; +} + +// NB: open and close must ensure that they only handle interface of +// the right API type, as these functions can be called wholesale from +// composite_open(), with interfaces belonging to different APIs +static int winusb_open(struct libusb_device_handle *dev_handle) +{ + struct libusb_context *ctx = DEVICE_CTX(dev_handle->dev); + struct windows_device_priv *priv = _device_priv(dev_handle->dev); + struct windows_device_handle_priv *handle_priv = _device_handle_priv(dev_handle); + + HANDLE file_handle; + int i; + + CHECK_WINUSB_AVAILABLE; + + // WinUSB requires a seperate handle for each interface + for (i = 0; i < USB_MAXINTERFACES; i++) { + if ( (priv->usb_interface[i].path != NULL) + && (priv->usb_interface[i].apib->id == USB_API_WINUSB) ) { + file_handle = CreateFileA(priv->usb_interface[i].path, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_WRITE | FILE_SHARE_READ, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL); + if (file_handle == INVALID_HANDLE_VALUE) { + usbi_err(ctx, "could not open device %s (interface %d): %s", priv->usb_interface[i].path, i, windows_error_str(0)); + switch(GetLastError()) { + case ERROR_FILE_NOT_FOUND: // The device was disconnected + return LIBUSB_ERROR_NO_DEVICE; + case ERROR_ACCESS_DENIED: + return LIBUSB_ERROR_ACCESS; + default: + return LIBUSB_ERROR_IO; + } + } + handle_priv->interface_handle[i].dev_handle = file_handle; + } + } + + return LIBUSB_SUCCESS; +} + +static void winusb_close(struct libusb_device_handle *dev_handle) +{ + struct windows_device_handle_priv *handle_priv = _device_handle_priv(dev_handle); + struct windows_device_priv *priv = _device_priv(dev_handle->dev); + HANDLE file_handle; + int i; + + if (!api_winusb_available) + return; + + for (i = 0; i < USB_MAXINTERFACES; i++) { + if (priv->usb_interface[i].apib->id == USB_API_WINUSB) { + file_handle = handle_priv->interface_handle[i].dev_handle; + if ( (file_handle != 0) && (file_handle != INVALID_HANDLE_VALUE)) { + CloseHandle(file_handle); + } + } + } +} + +static int winusb_configure_endpoints(struct libusb_device_handle *dev_handle, int iface) +{ + struct windows_device_handle_priv *handle_priv = _device_handle_priv(dev_handle); + struct windows_device_priv *priv = _device_priv(dev_handle->dev); + HANDLE winusb_handle = handle_priv->interface_handle[iface].api_handle; + UCHAR policy; + ULONG timeout = 0; + uint8_t endpoint_address; + int i; + + CHECK_WINUSB_AVAILABLE; + + // With handle and enpoints set (in parent), we can setup the default pipe properties + // see http://download.microsoft.com/download/D/1/D/D1DD7745-426B-4CC3-A269-ABBBE427C0EF/DVC-T705_DDC08.pptx + for (i=-1; iusb_interface[iface].nb_endpoints; i++) { + endpoint_address =(i==-1)?0:priv->usb_interface[iface].endpoint[i]; + if (!WinUsb_SetPipePolicy(winusb_handle, endpoint_address, + PIPE_TRANSFER_TIMEOUT, sizeof(ULONG), &timeout)) { + usbi_dbg("failed to set PIPE_TRANSFER_TIMEOUT for control endpoint %02X", endpoint_address); + } + if (i == -1) + continue; // Other policies don't apply to control endpoint + policy = true; + if (!WinUsb_SetPipePolicy(winusb_handle, endpoint_address, + AUTO_CLEAR_STALL, sizeof(UCHAR), &policy)) { + usbi_dbg("failed to enable AUTO_CLEAR_STALL for endpoint %02X", endpoint_address); + } + } + + return LIBUSB_SUCCESS; +} + +static int winusb_claim_interface(struct libusb_device_handle *dev_handle, int iface) +{ + struct libusb_context *ctx = DEVICE_CTX(dev_handle->dev); + struct windows_device_handle_priv *handle_priv = _device_handle_priv(dev_handle); + struct windows_device_priv *priv = _device_priv(dev_handle->dev); + bool is_using_usbccgp = (priv->apib->id == USB_API_COMPOSITE); + HANDLE file_handle, winusb_handle; + + CHECK_WINUSB_AVAILABLE; + + // If the device is composite, but using the default Windows composite parent driver (usbccgp) + // or if it's the first WinUSB interface, we get a handle through WinUsb_Initialize(). + if ((is_using_usbccgp) || (iface == 0)) { + // composite device (independent interfaces) or interface 0 + file_handle = handle_priv->interface_handle[iface].dev_handle; + if ((file_handle == 0) || (file_handle == INVALID_HANDLE_VALUE)) { + return LIBUSB_ERROR_NOT_FOUND; + } + + if (!WinUsb_Initialize(file_handle, &winusb_handle)) { + usbi_err(ctx, "could not access interface %d: %s", iface, windows_error_str(0)); + handle_priv->interface_handle[iface].api_handle = INVALID_HANDLE_VALUE; + + switch(GetLastError()) { + case ERROR_BAD_COMMAND: // The device was disconnected + return LIBUSB_ERROR_NO_DEVICE; + default: + usbi_err(ctx, "could not claim interface %d: %s", iface, windows_error_str(0)); + return LIBUSB_ERROR_ACCESS; + } + } + handle_priv->interface_handle[iface].api_handle = winusb_handle; + } else { + // For all other interfaces, use WinUsb_GetAssociatedInterface() + winusb_handle = handle_priv->interface_handle[0].api_handle; + // It is a requirement for multiple interface devices using WinUSB that you + // must first claim the first interface before you claim any other + if ((winusb_handle == 0) || (winusb_handle == INVALID_HANDLE_VALUE)) { + file_handle = handle_priv->interface_handle[0].dev_handle; + if (WinUsb_Initialize(file_handle, &winusb_handle)) { + handle_priv->interface_handle[0].api_handle = winusb_handle; + usbi_warn(ctx, "auto-claimed interface 0 (required to claim %d with WinUSB)", iface); + } else { + usbi_warn(ctx, "failed to auto-claim interface 0 (required to claim %d with WinUSB)", iface); + return LIBUSB_ERROR_ACCESS; + } + } + if (!WinUsb_GetAssociatedInterface(winusb_handle, (UCHAR)(iface-1), + &handle_priv->interface_handle[iface].api_handle)) { + handle_priv->interface_handle[iface].api_handle = INVALID_HANDLE_VALUE; + switch(GetLastError()) { + case ERROR_NO_MORE_ITEMS: // invalid iface + return LIBUSB_ERROR_NOT_FOUND; + case ERROR_BAD_COMMAND: // The device was disconnected + return LIBUSB_ERROR_NO_DEVICE; + case ERROR_ALREADY_EXISTS: // already claimed + return LIBUSB_ERROR_BUSY; + default: + usbi_err(ctx, "could not claim interface %d: %s", iface, windows_error_str(0)); + return LIBUSB_ERROR_ACCESS; + } + } + } + usbi_dbg("claimed interface %d", iface); + handle_priv->active_interface = iface; + + return LIBUSB_SUCCESS; +} + +static int winusb_release_interface(struct libusb_device_handle *dev_handle, int iface) +{ + struct windows_device_handle_priv *handle_priv = _device_handle_priv(dev_handle); + HANDLE winusb_handle; + + CHECK_WINUSB_AVAILABLE; + + winusb_handle = handle_priv->interface_handle[iface].api_handle; + if ((winusb_handle == 0) || (winusb_handle == INVALID_HANDLE_VALUE)) { + return LIBUSB_ERROR_NOT_FOUND; + } + + WinUsb_Free(winusb_handle); + handle_priv->interface_handle[iface].api_handle = INVALID_HANDLE_VALUE; + + return LIBUSB_SUCCESS; +} + +/* + * Return the first valid interface (of the same API type), for control transfers + */ +static int winusb_get_valid_interface(struct libusb_device_handle *dev_handle) +{ + struct windows_device_handle_priv *handle_priv = _device_handle_priv(dev_handle); + int i; + + for (i=0; iinterface_handle[i].dev_handle != 0) + && (handle_priv->interface_handle[i].dev_handle != INVALID_HANDLE_VALUE) + && (handle_priv->interface_handle[i].api_handle != 0) + && (handle_priv->interface_handle[i].api_handle != INVALID_HANDLE_VALUE) ) { + return i; + } + } + return -1; +} + +/* + * Lookup interface by endpoint address. -1 if not found + */ +static int interface_by_endpoint(struct windows_device_priv *priv, + struct windows_device_handle_priv *handle_priv, uint8_t endpoint_address) +{ + int i, j; + for (i=0; iinterface_handle[i].api_handle == INVALID_HANDLE_VALUE) + continue; + if (handle_priv->interface_handle[i].api_handle == 0) + continue; + if (priv->usb_interface[i].endpoint == NULL) + continue; + for (j=0; jusb_interface[i].nb_endpoints; j++) { + if (priv->usb_interface[i].endpoint[j] == endpoint_address) { + return i; + } + } + } + return -1; +} + +static int winusb_submit_control_transfer(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct libusb_context *ctx = DEVICE_CTX(transfer->dev_handle->dev); + struct windows_device_priv *priv = _device_priv(transfer->dev_handle->dev); + struct windows_transfer_priv *transfer_priv = (struct windows_transfer_priv*)usbi_transfer_get_os_priv(itransfer); + struct windows_device_handle_priv *handle_priv = _device_handle_priv( + transfer->dev_handle); + WINUSB_SETUP_PACKET *setup = (WINUSB_SETUP_PACKET *) transfer->buffer; + ULONG size; + HANDLE winusb_handle; + int current_interface; + struct winfd wfd; + + CHECK_WINUSB_AVAILABLE; + + transfer_priv->pollable_fd = INVALID_WINFD; + size = transfer->length - LIBUSB_CONTROL_SETUP_SIZE; + + if (size > MAX_CTRL_BUFFER_LENGTH) + return LIBUSB_ERROR_INVALID_PARAM; + + current_interface = winusb_get_valid_interface(transfer->dev_handle); + if (current_interface < 0) { + if (auto_claim(transfer, ¤t_interface, USB_API_WINUSB) != LIBUSB_SUCCESS) { + return LIBUSB_ERROR_NOT_FOUND; + } + } + + usbi_dbg("will use interface %d", current_interface); + winusb_handle = handle_priv->interface_handle[current_interface].api_handle; + + wfd = usbi_create_fd(winusb_handle, _O_RDONLY); + // Always use the handle returned from usbi_create_fd (wfd.handle) + if (wfd.fd < 0) { + return LIBUSB_ERROR_NO_MEM; + } + + // Sending of set configuration control requests from WinUSB creates issues + if ( ((setup->request_type & (0x03 << 5)) == LIBUSB_REQUEST_TYPE_STANDARD) + && (setup->request == LIBUSB_REQUEST_SET_CONFIGURATION) ) { + if (setup->value != priv->active_config) { + usbi_warn(ctx, "cannot set configuration other than the default one"); + usbi_free_fd(wfd.fd); + return LIBUSB_ERROR_INVALID_PARAM; + } + wfd.overlapped->Internal = STATUS_COMPLETED_SYNCHRONOUSLY; + wfd.overlapped->InternalHigh = 0; + } else { + if (!WinUsb_ControlTransfer(wfd.handle, *setup, transfer->buffer + LIBUSB_CONTROL_SETUP_SIZE, size, NULL, wfd.overlapped)) { + if(GetLastError() != ERROR_IO_PENDING) { + usbi_err(ctx, "WinUsb_ControlTransfer failed: %s", windows_error_str(0)); + usbi_free_fd(wfd.fd); + return LIBUSB_ERROR_IO; + } + } else { + wfd.overlapped->Internal = STATUS_COMPLETED_SYNCHRONOUSLY; + wfd.overlapped->InternalHigh = (DWORD)size; + } + } + + // Use priv_transfer to store data needed for async polling + transfer_priv->pollable_fd = wfd; + transfer_priv->interface_number = (uint8_t)current_interface; + + return LIBUSB_SUCCESS; +} + +static int winusb_set_interface_altsetting(struct libusb_device_handle *dev_handle, int iface, int altsetting) +{ + struct libusb_context *ctx = DEVICE_CTX(dev_handle->dev); + struct windows_device_handle_priv *handle_priv = _device_handle_priv(dev_handle); + HANDLE winusb_handle; + + CHECK_WINUSB_AVAILABLE; + + if (altsetting > 255) { + return LIBUSB_ERROR_INVALID_PARAM; + } + + winusb_handle = handle_priv->interface_handle[iface].api_handle; + if ((winusb_handle == 0) || (winusb_handle == INVALID_HANDLE_VALUE)) { + usbi_err(ctx, "interface must be claimed first"); + return LIBUSB_ERROR_NOT_FOUND; + } + + if (!WinUsb_SetCurrentAlternateSetting(winusb_handle, (UCHAR)altsetting)) { + usbi_err(ctx, "WinUsb_SetCurrentAlternateSetting failed: %s", windows_error_str(0)); + return LIBUSB_ERROR_IO; + } + + return LIBUSB_SUCCESS; +} + +static int winusb_submit_bulk_transfer(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct libusb_context *ctx = DEVICE_CTX(transfer->dev_handle->dev); + struct windows_transfer_priv *transfer_priv = (struct windows_transfer_priv*)usbi_transfer_get_os_priv(itransfer); + struct windows_device_handle_priv *handle_priv = _device_handle_priv(transfer->dev_handle); + struct windows_device_priv *priv = _device_priv(transfer->dev_handle->dev); + HANDLE winusb_handle; + bool ret; + int current_interface; + struct winfd wfd; + ULONG ppolicy = sizeof(UCHAR); + UCHAR policy; + + CHECK_WINUSB_AVAILABLE; + + transfer_priv->pollable_fd = INVALID_WINFD; + + current_interface = interface_by_endpoint(priv, handle_priv, transfer->endpoint); + if (current_interface < 0) { + usbi_err(ctx, "unable to match endpoint to an open interface - cancelling transfer"); + return LIBUSB_ERROR_NOT_FOUND; + } + + usbi_dbg("matched endpoint %02X with interface %d", transfer->endpoint, current_interface); + + winusb_handle = handle_priv->interface_handle[current_interface].api_handle; + + wfd = usbi_create_fd(winusb_handle, IS_XFERIN(transfer) ? _O_RDONLY : _O_WRONLY); + // Always use the handle returned from usbi_create_fd (wfd.handle) + if (wfd.fd < 0) { + return LIBUSB_ERROR_NO_MEM; + } + + if (IS_XFERIN(transfer)) { + WinUsb_GetPipePolicy(wfd.handle, transfer->endpoint, AUTO_CLEAR_STALL, &ppolicy, &policy); + if (!policy) { + policy = TRUE; + WinUsb_SetPipePolicy(wfd.handle, transfer->endpoint, AUTO_CLEAR_STALL, ppolicy, &policy); + } + ret = WinUsb_ReadPipe(wfd.handle, transfer->endpoint, transfer->buffer, transfer->length, NULL, wfd.overlapped); + } else { + if (transfer->flags & LIBUSB_TRANSFER_ADD_ZERO_PACKET) { + WinUsb_GetPipePolicy(wfd.handle, transfer->endpoint, SHORT_PACKET_TERMINATE, &ppolicy, &policy); + if (!policy) { + policy = TRUE; + WinUsb_SetPipePolicy(wfd.handle, transfer->endpoint, SHORT_PACKET_TERMINATE, ppolicy, &policy); + } + } + usbi_dbg("writing %d bytes", transfer->length); + ret = WinUsb_WritePipe(wfd.handle, transfer->endpoint, transfer->buffer, transfer->length, NULL, wfd.overlapped); + } + if (!ret) { + if(GetLastError() != ERROR_IO_PENDING) { + usbi_err(ctx, "WinUsb_Pipe Transfer failed: %s", windows_error_str(0)); + usbi_free_fd(wfd.fd); + return LIBUSB_ERROR_IO; + } + } else { + wfd.overlapped->Internal = STATUS_COMPLETED_SYNCHRONOUSLY; + wfd.overlapped->InternalHigh = (DWORD)transfer->length; + } + + transfer_priv->pollable_fd = wfd; + transfer_priv->interface_number = (uint8_t)current_interface; + + return LIBUSB_SUCCESS; +} + +static int winusb_clear_halt(struct libusb_device_handle *dev_handle, unsigned char endpoint) +{ + struct libusb_context *ctx = DEVICE_CTX(dev_handle->dev); + struct windows_device_handle_priv *handle_priv = _device_handle_priv(dev_handle); + struct windows_device_priv *priv = _device_priv(dev_handle->dev); + HANDLE winusb_handle; + int current_interface; + + CHECK_WINUSB_AVAILABLE; + + current_interface = interface_by_endpoint(priv, handle_priv, endpoint); + if (current_interface < 0) { + usbi_err(ctx, "unable to match endpoint to an open interface - cannot clear"); + return LIBUSB_ERROR_NOT_FOUND; + } + + usbi_dbg("matched endpoint %02X with interface %d", endpoint, current_interface); + winusb_handle = handle_priv->interface_handle[current_interface].api_handle; + + if (!WinUsb_ResetPipe(winusb_handle, endpoint)) { + usbi_err(ctx, "WinUsb_ResetPipe failed: %s", windows_error_str(0)); + return LIBUSB_ERROR_NO_DEVICE; + } + + return LIBUSB_SUCCESS; +} + +/* + * from http://www.winvistatips.com/winusb-bugchecks-t335323.html (confirmed + * through testing as well): + * "You can not call WinUsb_AbortPipe on control pipe. You can possibly cancel + * the control transfer using CancelIo" + */ +static int winusb_abort_control(struct usbi_transfer *itransfer) +{ + // Cancelling of the I/O is done in the parent + return LIBUSB_SUCCESS; +} + +static int winusb_abort_transfers(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct libusb_context *ctx = DEVICE_CTX(transfer->dev_handle->dev); + struct windows_device_handle_priv *handle_priv = _device_handle_priv(transfer->dev_handle); + struct windows_transfer_priv *transfer_priv = usbi_transfer_get_os_priv(itransfer); + HANDLE winusb_handle; + int current_interface; + + CHECK_WINUSB_AVAILABLE; + + current_interface = transfer_priv->interface_number; + if ((current_interface < 0) || (current_interface >= USB_MAXINTERFACES)) { + usbi_err(ctx, "program assertion failed: invalid interface_number"); + return LIBUSB_ERROR_NOT_FOUND; + } + usbi_dbg("will use interface %d", current_interface); + + winusb_handle = handle_priv->interface_handle[current_interface].api_handle; + + if (!WinUsb_AbortPipe(winusb_handle, transfer->endpoint)) { + usbi_err(ctx, "WinUsb_AbortPipe failed: %s", windows_error_str(0)); + return LIBUSB_ERROR_NO_DEVICE; + } + + return LIBUSB_SUCCESS; +} + +/* + * from the "How to Use WinUSB to Communicate with a USB Device" Microsoft white paper + * (http://www.microsoft.com/whdc/connect/usb/winusb_howto.mspx): + * "WinUSB does not support host-initiated reset port and cycle port operations" and + * IOCTL_INTERNAL_USB_CYCLE_PORT is only available in kernel mode and the + * IOCTL_USB_HUB_CYCLE_PORT ioctl was removed from Vista => the best we can do is + * cycle the pipes (and even then, the control pipe can not be reset using WinUSB) + */ +// TODO (post hotplug): see if we can force eject the device and redetect it (reuse hotplug?) +static int winusb_reset_device(struct libusb_device_handle *dev_handle) +{ + struct libusb_context *ctx = DEVICE_CTX(dev_handle->dev); + struct windows_device_handle_priv *handle_priv = _device_handle_priv(dev_handle); + struct windows_device_priv *priv = _device_priv(dev_handle->dev); + struct winfd wfd; + HANDLE winusb_handle; + int i, j; + + CHECK_WINUSB_AVAILABLE; + + // Reset any available pipe (except control) + for (i=0; iinterface_handle[i].api_handle; + for (wfd = handle_to_winfd(winusb_handle); wfd.fd > 0;) + { + // Cancel any pollable I/O + usbi_remove_pollfd(ctx, wfd.fd); + usbi_free_fd(wfd.fd); + wfd = handle_to_winfd(winusb_handle); + } + + if ( (winusb_handle != 0) && (winusb_handle != INVALID_HANDLE_VALUE)) { + for (j=0; jusb_interface[i].nb_endpoints; j++) { + usbi_dbg("resetting ep %02X", priv->usb_interface[i].endpoint[j]); + if (!WinUsb_AbortPipe(winusb_handle, priv->usb_interface[i].endpoint[j])) { + usbi_err(ctx, "WinUsb_AbortPipe (pipe address %02X) failed: %s", + priv->usb_interface[i].endpoint[j], windows_error_str(0)); + } + // FlushPipe seems to fail on OUT pipes + if (IS_EPIN(priv->usb_interface[i].endpoint[j]) + && (!WinUsb_FlushPipe(winusb_handle, priv->usb_interface[i].endpoint[j])) ) { + usbi_err(ctx, "WinUsb_FlushPipe (pipe address %02X) failed: %s", + priv->usb_interface[i].endpoint[j], windows_error_str(0)); + } + if (!WinUsb_ResetPipe(winusb_handle, priv->usb_interface[i].endpoint[j])) { + usbi_err(ctx, "WinUsb_ResetPipe (pipe address %02X) failed: %s", + priv->usb_interface[i].endpoint[j], windows_error_str(0)); + } + } + } + } + + return LIBUSB_SUCCESS; +} + +static int winusb_copy_transfer_data(struct usbi_transfer *itransfer, uint32_t io_size) +{ + itransfer->transferred += io_size; + return LIBUSB_TRANSFER_COMPLETED; +} + + +/* + * Composite API functions + */ +static int composite_init(struct libusb_context *ctx) +{ + return LIBUSB_SUCCESS; +} + +static int composite_exit(void) +{ + return LIBUSB_SUCCESS; +} + +static int composite_open(struct libusb_device_handle *dev_handle) +{ + struct windows_device_priv *priv = _device_priv(dev_handle->dev); + unsigned api; + int r; + uint8_t flag = 1<composite_api_flags & flag) { + r = usb_api_backend[api].open(dev_handle); + if (r != LIBUSB_SUCCESS) { + return r; + } + } + flag <<= 1; + } + return LIBUSB_SUCCESS; +} + +static void composite_close(struct libusb_device_handle *dev_handle) +{ + struct windows_device_priv *priv = _device_priv(dev_handle->dev); + unsigned api; + uint8_t flag = 1<composite_api_flags & flag) { + usb_api_backend[api].close(dev_handle); + } + flag <<= 1; + } +} + +static int composite_claim_interface(struct libusb_device_handle *dev_handle, int iface) +{ + struct windows_device_priv *priv = _device_priv(dev_handle->dev); + return priv->usb_interface[iface].apib->claim_interface(dev_handle, iface); +} + +static int composite_set_interface_altsetting(struct libusb_device_handle *dev_handle, int iface, int altsetting) +{ + struct windows_device_priv *priv = _device_priv(dev_handle->dev); + return priv->usb_interface[iface].apib->set_interface_altsetting(dev_handle, iface, altsetting); +} + +static int composite_release_interface(struct libusb_device_handle *dev_handle, int iface) +{ + struct windows_device_priv *priv = _device_priv(dev_handle->dev); + return priv->usb_interface[iface].apib->release_interface(dev_handle, iface); +} + +static int composite_submit_control_transfer(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct libusb_context *ctx = DEVICE_CTX(transfer->dev_handle->dev); + struct windows_device_priv *priv = _device_priv(transfer->dev_handle->dev); + int i; + + for (i=0; iusb_interface[i].path != NULL) { + usbi_dbg("using interface %d", i); + return priv->usb_interface[i].apib->submit_control_transfer(itransfer); + } + } + + usbi_err(ctx, "no libusb supported interfaces to complete request"); + return LIBUSB_ERROR_NOT_FOUND; +} + +static int composite_submit_bulk_transfer(struct usbi_transfer *itransfer) { + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct libusb_context *ctx = DEVICE_CTX(transfer->dev_handle->dev); + struct windows_device_handle_priv *handle_priv = _device_handle_priv(transfer->dev_handle); + struct windows_device_priv *priv = _device_priv(transfer->dev_handle->dev); + int current_interface; + + current_interface = interface_by_endpoint(priv, handle_priv, transfer->endpoint); + if (current_interface < 0) { + usbi_err(ctx, "unable to match endpoint to an open interface - cancelling transfer"); + return LIBUSB_ERROR_NOT_FOUND; + } + + return priv->usb_interface[current_interface].apib->submit_bulk_transfer(itransfer); +} + +static int composite_submit_iso_transfer(struct usbi_transfer *itransfer) { + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct libusb_context *ctx = DEVICE_CTX(transfer->dev_handle->dev); + struct windows_device_handle_priv *handle_priv = _device_handle_priv(transfer->dev_handle); + struct windows_device_priv *priv = _device_priv(transfer->dev_handle->dev); + int current_interface; + + current_interface = interface_by_endpoint(priv, handle_priv, transfer->endpoint); + if (current_interface < 0) { + usbi_err(ctx, "unable to match endpoint to an open interface - cancelling transfer"); + return LIBUSB_ERROR_NOT_FOUND; + } + + return priv->usb_interface[current_interface].apib->submit_iso_transfer(itransfer); +} + +static int composite_clear_halt(struct libusb_device_handle *dev_handle, unsigned char endpoint) +{ + struct libusb_context *ctx = DEVICE_CTX(dev_handle->dev); + struct windows_device_handle_priv *handle_priv = _device_handle_priv(dev_handle); + struct windows_device_priv *priv = _device_priv(dev_handle->dev); + int current_interface; + + current_interface = interface_by_endpoint(priv, handle_priv, endpoint); + if (current_interface < 0) { + usbi_err(ctx, "unable to match endpoint to an open interface - cannot clear"); + return LIBUSB_ERROR_NOT_FOUND; + } + + return priv->usb_interface[current_interface].apib->clear_halt(dev_handle, endpoint); +} + +static int composite_abort_control(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct windows_transfer_priv *transfer_priv = usbi_transfer_get_os_priv(itransfer); + struct windows_device_priv *priv = _device_priv(transfer->dev_handle->dev); + + return priv->usb_interface[transfer_priv->interface_number].apib->abort_control(itransfer); +} + +static int composite_abort_transfers(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct windows_transfer_priv *transfer_priv = usbi_transfer_get_os_priv(itransfer); + struct windows_device_priv *priv = _device_priv(transfer->dev_handle->dev); + + return priv->usb_interface[transfer_priv->interface_number].apib->abort_transfers(itransfer); +} + +static int composite_reset_device(struct libusb_device_handle *dev_handle) +{ + struct windows_device_priv *priv = _device_priv(dev_handle->dev); + unsigned api; + int r; + uint8_t flag = 1<composite_api_flags & flag) { + r = usb_api_backend[api].reset_device(dev_handle); + if (r != LIBUSB_SUCCESS) { + return r; + } + } + flag <<= 1; + } + return LIBUSB_SUCCESS; +} + +static int composite_copy_transfer_data(struct usbi_transfer *itransfer, uint32_t io_size) +{ + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct windows_transfer_priv *transfer_priv = usbi_transfer_get_os_priv(itransfer); + struct windows_device_priv *priv = _device_priv(transfer->dev_handle->dev); + + return priv->usb_interface[transfer_priv->interface_number].apib->copy_transfer_data(itransfer, io_size); +} diff --git a/compat/libusb-1.0/libusb/os/windows_usb.h b/compat/libusb-1.0/libusb/os/windows_usb.h new file mode 100644 index 0000000..ddbd680 --- /dev/null +++ b/compat/libusb-1.0/libusb/os/windows_usb.h @@ -0,0 +1,608 @@ +/* + * Windows backend for libusb 1.0 + * Copyright (C) 2009-2010 Pete Batard + * With contributions from Michael Plante, Orin Eman et al. + * Parts of this code adapted from libusb-win32-v1 by Stephan Meyer + * Major code testing contribution by Xiaofan Chen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#pragma once + +#if defined(_MSC_VER) +// disable /W4 MSVC warnings that are benign +#pragma warning(disable:4127) // conditional expression is constant +#pragma warning(disable:4100) // unreferenced formal parameter +#pragma warning(disable:4214) // bit field types other than int +#pragma warning(disable:4201) // nameless struct/union +#endif + +// Windows API default is uppercase - ugh! +#if !defined(bool) +#define bool BOOL +#endif +#if !defined(true) +#define true TRUE +#endif +#if !defined(false) +#define false FALSE +#endif + +// Missing from MSVC6 setupapi.h +#if !defined(SPDRP_ADDRESS) +#define SPDRP_ADDRESS 28 +#endif +#if !defined(SPDRP_INSTALL_STATE) +#define SPDRP_INSTALL_STATE 34 +#endif + +#if defined(__CYGWIN__ ) +// cygwin produces a warning unless these prototypes are defined +extern int _snprintf(char *buffer, size_t count, const char *format, ...); +extern char *_strdup(const char *strSource); +// _beginthreadex is MSVCRT => unavailable for cygwin. Fallback to using CreateThread +#define _beginthreadex(a, b, c, d, e, f) CreateThread(a, b, (LPTHREAD_START_ROUTINE)c, d, e, f) +#endif +#define safe_free(p) do {if (p != NULL) {free((void*)p); p = NULL;}} while(0) +#define safe_closehandle(h) do {if (h != INVALID_HANDLE_VALUE) {CloseHandle(h); h = INVALID_HANDLE_VALUE;}} while(0) +#define safe_min(a, b) min((size_t)(a), (size_t)(b)) +#define safe_strcp(dst, dst_max, src, count) do {memcpy(dst, src, safe_min(count, dst_max)); \ + ((char*)dst)[safe_min(count, dst_max)-1] = 0;} while(0) +#define safe_strcpy(dst, dst_max, src) safe_strcp(dst, dst_max, src, safe_strlen(src)+1) +#define safe_strncat(dst, dst_max, src, count) strncat(dst, src, safe_min(count, dst_max - safe_strlen(dst) - 1)) +#define safe_strcat(dst, dst_max, src) safe_strncat(dst, dst_max, src, safe_strlen(src)+1) +#define safe_strcmp(str1, str2) strcmp(((str1==NULL)?"":str1), ((str2==NULL)?"":str2)) +#define safe_strncmp(str1, str2, count) strncmp(((str1==NULL)?"":str1), ((str2==NULL)?"":str2), count) +#define safe_strlen(str) ((str==NULL)?0:strlen(str)) +#define safe_sprintf _snprintf +#define safe_unref_device(dev) do {if (dev != NULL) {libusb_unref_device(dev); dev = NULL;}} while(0) +#define wchar_to_utf8_ms(wstr, str, strlen) WideCharToMultiByte(CP_UTF8, 0, wstr, -1, str, strlen, NULL, NULL) +static inline void upperize(char* str) { + size_t i; + if (str == NULL) return; + for (i=0; ios_priv; +} + +static inline void windows_device_priv_init(libusb_device* dev) { + struct windows_device_priv* p = _device_priv(dev); + int i; + p->depth = 0; + p->port = 0; + p->parent_dev = NULL; + p->path = NULL; + p->apib = &usb_api_backend[USB_API_UNSUPPORTED]; + p->composite_api_flags = 0; + p->active_config = 0; + p->config_descriptor = NULL; + memset(&(p->dev_descriptor), 0, sizeof(USB_DEVICE_DESCRIPTOR)); + for (i=0; iusb_interface[i].path = NULL; + p->usb_interface[i].apib = &usb_api_backend[USB_API_UNSUPPORTED]; + p->usb_interface[i].nb_endpoints = 0; + p->usb_interface[i].endpoint = NULL; + } +} + +static inline void windows_device_priv_release(libusb_device* dev) { + struct windows_device_priv* p = _device_priv(dev); + int i; + safe_free(p->path); + if ((dev->num_configurations > 0) && (p->config_descriptor != NULL)) { + for (i=0; i < dev->num_configurations; i++) + safe_free(p->config_descriptor[i]); + } + safe_free(p->config_descriptor); + for (i=0; iusb_interface[i].path); + safe_free(p->usb_interface[i].endpoint); + } +} + +struct interface_handle_t { + HANDLE dev_handle; // WinUSB needs an extra handle for the file + HANDLE api_handle; // used by the API to communicate with the device +}; + +struct windows_device_handle_priv { + int active_interface; + struct interface_handle_t interface_handle[USB_MAXINTERFACES]; + int autoclaim_count[USB_MAXINTERFACES]; // For auto-release +}; + +static inline struct windows_device_handle_priv *_device_handle_priv( + struct libusb_device_handle *handle) +{ + return (struct windows_device_handle_priv *) handle->os_priv; +} + +// used for async polling functions +struct windows_transfer_priv { + struct winfd pollable_fd; + uint8_t interface_number; +}; + +// used to match a device driver (including filter drivers) against a supported API +struct driver_lookup { + char list[MAX_KEY_LENGTH+1];// REG_MULTI_SZ list of services (driver) names + const DWORD reg_prop; // SPDRP registry key to use to retreive list + const char* designation; // internal designation (for debug output) +}; + +/* + * API macros - from libusb-win32 1.x + */ +#define DLL_DECLARE_PREFIXNAME(api, ret, prefixname, name, args) \ + typedef ret (api * __dll_##name##_t)args; \ + static __dll_##name##_t prefixname = NULL + +#define DLL_LOAD_PREFIXNAME(dll, prefixname, name, ret_on_failure) \ + do { \ + HMODULE h = GetModuleHandleA(#dll); \ + if (!h) \ + h = LoadLibraryA(#dll); \ + if (!h) { \ + if (ret_on_failure) { return LIBUSB_ERROR_NOT_FOUND; }\ + else { break; } \ + } \ + prefixname = (__dll_##name##_t)GetProcAddress(h, #name); \ + if (prefixname) break; \ + prefixname = (__dll_##name##_t)GetProcAddress(h, #name "A"); \ + if (prefixname) break; \ + prefixname = (__dll_##name##_t)GetProcAddress(h, #name "W"); \ + if (prefixname) break; \ + if(ret_on_failure) \ + return LIBUSB_ERROR_NOT_FOUND; \ + } while(0) + +#define DLL_DECLARE(api, ret, name, args) DLL_DECLARE_PREFIXNAME(api, ret, name, name, args) +#define DLL_LOAD(dll, name, ret_on_failure) DLL_LOAD_PREFIXNAME(dll, name, name, ret_on_failure) +#define DLL_DECLARE_PREFIXED(api, ret, prefix, name, args) DLL_DECLARE_PREFIXNAME(api, ret, prefix##name, name, args) +#define DLL_LOAD_PREFIXED(dll, prefix, name, ret_on_failure) DLL_LOAD_PREFIXNAME(dll, prefix##name, name, ret_on_failure) + +/* OLE32 dependency */ +DLL_DECLARE_PREFIXED(WINAPI, HRESULT, p, CLSIDFromString, (LPCOLESTR, LPCLSID)); + +/* SetupAPI dependencies */ +DLL_DECLARE_PREFIXED(WINAPI, HDEVINFO, p, SetupDiGetClassDevsA, (const GUID*, PCSTR, HWND, DWORD)); +DLL_DECLARE_PREFIXED(WINAPI, BOOL, p, SetupDiEnumDeviceInfo, (HDEVINFO, DWORD, PSP_DEVINFO_DATA)); +DLL_DECLARE_PREFIXED(WINAPI, BOOL, p, SetupDiEnumDeviceInterfaces, (HDEVINFO, PSP_DEVINFO_DATA, + const GUID*, DWORD, PSP_DEVICE_INTERFACE_DATA)); +DLL_DECLARE_PREFIXED(WINAPI, BOOL, p, SetupDiGetDeviceInterfaceDetailA, (HDEVINFO, PSP_DEVICE_INTERFACE_DATA, + PSP_DEVICE_INTERFACE_DETAIL_DATA_A, DWORD, PDWORD, PSP_DEVINFO_DATA)); +DLL_DECLARE_PREFIXED(WINAPI, BOOL, p, SetupDiDestroyDeviceInfoList, (HDEVINFO)); +DLL_DECLARE_PREFIXED(WINAPI, HKEY, p, SetupDiOpenDevRegKey, (HDEVINFO, PSP_DEVINFO_DATA, DWORD, DWORD, DWORD, REGSAM)); +DLL_DECLARE_PREFIXED(WINAPI, BOOL, p, SetupDiGetDeviceRegistryPropertyA, (HDEVINFO, + PSP_DEVINFO_DATA, DWORD, PDWORD, PBYTE, DWORD, PDWORD)); +DLL_DECLARE_PREFIXED(WINAPI, LONG, p, RegQueryValueExW, (HKEY, LPCWSTR, LPDWORD, LPDWORD, LPBYTE, LPDWORD)); +DLL_DECLARE_PREFIXED(WINAPI, LONG, p, RegCloseKey, (HKEY)); + +/* + * Windows DDK API definitions. Most of it copied from MinGW's includes + */ +typedef DWORD DEVNODE, DEVINST; +typedef DEVNODE *PDEVNODE, *PDEVINST; +typedef DWORD RETURN_TYPE; +typedef RETURN_TYPE CONFIGRET; + +#define CR_SUCCESS 0x00000000 +#define CR_NO_SUCH_DEVNODE 0x0000000D + +#define USB_DEVICE_DESCRIPTOR_TYPE LIBUSB_DT_DEVICE +#define USB_CONFIGURATION_DESCRIPTOR_TYPE LIBUSB_DT_CONFIG +#define USB_STRING_DESCRIPTOR_TYPE LIBUSB_DT_STRING +#define USB_INTERFACE_DESCRIPTOR_TYPE LIBUSB_DT_INTERFACE +#define USB_ENDPOINT_DESCRIPTOR_TYPE LIBUSB_DT_ENDPOINT + +#define USB_REQUEST_GET_STATUS LIBUSB_REQUEST_GET_STATUS +#define USB_REQUEST_CLEAR_FEATURE LIBUSB_REQUEST_CLEAR_FEATURE +#define USB_REQUEST_SET_FEATURE LIBUSB_REQUEST_SET_FEATURE +#define USB_REQUEST_SET_ADDRESS LIBUSB_REQUEST_SET_ADDRESS +#define USB_REQUEST_GET_DESCRIPTOR LIBUSB_REQUEST_GET_DESCRIPTOR +#define USB_REQUEST_SET_DESCRIPTOR LIBUSB_REQUEST_SET_DESCRIPTOR +#define USB_REQUEST_GET_CONFIGURATION LIBUSB_REQUEST_GET_CONFIGURATION +#define USB_REQUEST_SET_CONFIGURATION LIBUSB_REQUEST_SET_CONFIGURATION +#define USB_REQUEST_GET_INTERFACE LIBUSB_REQUEST_GET_INTERFACE +#define USB_REQUEST_SET_INTERFACE LIBUSB_REQUEST_SET_INTERFACE +#define USB_REQUEST_SYNC_FRAME LIBUSB_REQUEST_SYNCH_FRAME + +#define USB_GET_NODE_INFORMATION 258 +#define USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION 260 +#define USB_GET_NODE_CONNECTION_NAME 261 +#define USB_GET_HUB_CAPABILITIES 271 +#if !defined(USB_GET_NODE_CONNECTION_INFORMATION_EX) +#define USB_GET_NODE_CONNECTION_INFORMATION_EX 274 +#endif +#if !defined(USB_GET_HUB_CAPABILITIES_EX) +#define USB_GET_HUB_CAPABILITIES_EX 276 +#endif + +#ifndef METHOD_BUFFERED +#define METHOD_BUFFERED 0 +#endif +#ifndef FILE_ANY_ACCESS +#define FILE_ANY_ACCESS 0x00000000 +#endif +#ifndef FILE_DEVICE_UNKNOWN +#define FILE_DEVICE_UNKNOWN 0x00000022 +#endif +#ifndef FILE_DEVICE_USB +#define FILE_DEVICE_USB FILE_DEVICE_UNKNOWN +#endif + +#ifndef CTL_CODE +#define CTL_CODE(DeviceType, Function, Method, Access)( \ + ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method)) +#endif + +typedef enum USB_CONNECTION_STATUS { + NoDeviceConnected, + DeviceConnected, + DeviceFailedEnumeration, + DeviceGeneralFailure, + DeviceCausedOvercurrent, + DeviceNotEnoughPower, + DeviceNotEnoughBandwidth, + DeviceHubNestedTooDeeply, + DeviceInLegacyHub +} USB_CONNECTION_STATUS, *PUSB_CONNECTION_STATUS; + +typedef enum USB_HUB_NODE { + UsbHub, + UsbMIParent +} USB_HUB_NODE; + +/* Cfgmgr32.dll interface */ +DLL_DECLARE(WINAPI, CONFIGRET, CM_Get_Parent, (PDEVINST, DEVINST, ULONG)); +DLL_DECLARE(WINAPI, CONFIGRET, CM_Get_Child, (PDEVINST, DEVINST, ULONG)); +DLL_DECLARE(WINAPI, CONFIGRET, CM_Get_Sibling, (PDEVINST, DEVINST, ULONG)); +DLL_DECLARE(WINAPI, CONFIGRET, CM_Get_Device_IDA, (DEVINST, PCHAR, ULONG, ULONG)); + +#define IOCTL_USB_GET_HUB_CAPABILITIES_EX \ + CTL_CODE( FILE_DEVICE_USB, USB_GET_HUB_CAPABILITIES_EX, METHOD_BUFFERED, FILE_ANY_ACCESS) + +#define IOCTL_USB_GET_HUB_CAPABILITIES \ + CTL_CODE(FILE_DEVICE_USB, USB_GET_HUB_CAPABILITIES, METHOD_BUFFERED, FILE_ANY_ACCESS) + +#define IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION \ + CTL_CODE(FILE_DEVICE_USB, USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION, METHOD_BUFFERED, FILE_ANY_ACCESS) + +#define IOCTL_USB_GET_ROOT_HUB_NAME \ + CTL_CODE(FILE_DEVICE_USB, HCD_GET_ROOT_HUB_NAME, METHOD_BUFFERED, FILE_ANY_ACCESS) + +#define IOCTL_USB_GET_NODE_INFORMATION \ + CTL_CODE(FILE_DEVICE_USB, USB_GET_NODE_INFORMATION, METHOD_BUFFERED, FILE_ANY_ACCESS) + +#define IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX \ + CTL_CODE(FILE_DEVICE_USB, USB_GET_NODE_CONNECTION_INFORMATION_EX, METHOD_BUFFERED, FILE_ANY_ACCESS) + +#define IOCTL_USB_GET_NODE_CONNECTION_ATTRIBUTES \ + CTL_CODE(FILE_DEVICE_USB, USB_GET_NODE_CONNECTION_ATTRIBUTES, METHOD_BUFFERED, FILE_ANY_ACCESS) + +#define IOCTL_USB_GET_NODE_CONNECTION_NAME \ + CTL_CODE(FILE_DEVICE_USB, USB_GET_NODE_CONNECTION_NAME, METHOD_BUFFERED, FILE_ANY_ACCESS) + +// Most of the structures below need to be packed +#pragma pack(push, 1) + +typedef struct USB_INTERFACE_DESCRIPTOR { + UCHAR bLength; + UCHAR bDescriptorType; + UCHAR bInterfaceNumber; + UCHAR bAlternateSetting; + UCHAR bNumEndpoints; + UCHAR bInterfaceClass; + UCHAR bInterfaceSubClass; + UCHAR bInterfaceProtocol; + UCHAR iInterface; +} USB_INTERFACE_DESCRIPTOR, *PUSB_INTERFACE_DESCRIPTOR; + +typedef struct USB_CONFIGURATION_DESCRIPTOR { + UCHAR bLength; + UCHAR bDescriptorType; + USHORT wTotalLength; + UCHAR bNumInterfaces; + UCHAR bConfigurationValue; + UCHAR iConfiguration; + UCHAR bmAttributes; + UCHAR MaxPower; +} USB_CONFIGURATION_DESCRIPTOR, *PUSB_CONFIGURATION_DESCRIPTOR; + +typedef struct USB_CONFIGURATION_DESCRIPTOR_SHORT { + struct { + ULONG ConnectionIndex; + struct { + UCHAR bmRequest; + UCHAR bRequest; + USHORT wValue; + USHORT wIndex; + USHORT wLength; + } SetupPacket; + } req; + USB_CONFIGURATION_DESCRIPTOR data; +} USB_CONFIGURATION_DESCRIPTOR_SHORT; + +typedef struct USB_ENDPOINT_DESCRIPTOR { + UCHAR bLength; + UCHAR bDescriptorType; + UCHAR bEndpointAddress; + UCHAR bmAttributes; + USHORT wMaxPacketSize; + UCHAR bInterval; +} USB_ENDPOINT_DESCRIPTOR, *PUSB_ENDPOINT_DESCRIPTOR; + +typedef struct USB_DESCRIPTOR_REQUEST { + ULONG ConnectionIndex; + struct { + UCHAR bmRequest; + UCHAR bRequest; + USHORT wValue; + USHORT wIndex; + USHORT wLength; + } SetupPacket; +// UCHAR Data[0]; +} USB_DESCRIPTOR_REQUEST, *PUSB_DESCRIPTOR_REQUEST; + +typedef struct USB_HUB_DESCRIPTOR { + UCHAR bDescriptorLength; + UCHAR bDescriptorType; + UCHAR bNumberOfPorts; + USHORT wHubCharacteristics; + UCHAR bPowerOnToPowerGood; + UCHAR bHubControlCurrent; + UCHAR bRemoveAndPowerMask[64]; +} USB_HUB_DESCRIPTOR, *PUSB_HUB_DESCRIPTOR; + +typedef struct USB_ROOT_HUB_NAME { + ULONG ActualLength; + WCHAR RootHubName[1]; +} USB_ROOT_HUB_NAME, *PUSB_ROOT_HUB_NAME; + +typedef struct USB_ROOT_HUB_NAME_FIXED { + ULONG ActualLength; + WCHAR RootHubName[MAX_PATH_LENGTH]; +} USB_ROOT_HUB_NAME_FIXED; + +typedef struct USB_NODE_CONNECTION_NAME { + ULONG ConnectionIndex; + ULONG ActualLength; + WCHAR NodeName[1]; +} USB_NODE_CONNECTION_NAME, *PUSB_NODE_CONNECTION_NAME; + +typedef struct USB_NODE_CONNECTION_NAME_FIXED { + ULONG ConnectionIndex; + ULONG ActualLength; + WCHAR NodeName[MAX_PATH_LENGTH]; +} USB_NODE_CONNECTION_NAME_FIXED; + +typedef struct USB_HUB_NAME_FIXED { + union { + USB_ROOT_HUB_NAME_FIXED root; + USB_NODE_CONNECTION_NAME_FIXED node; + } u; +} USB_HUB_NAME_FIXED; + +typedef struct USB_HUB_INFORMATION { + USB_HUB_DESCRIPTOR HubDescriptor; + BOOLEAN HubIsBusPowered; +} USB_HUB_INFORMATION, *PUSB_HUB_INFORMATION; + +typedef struct USB_MI_PARENT_INFORMATION { + ULONG NumberOfInterfaces; +} USB_MI_PARENT_INFORMATION, *PUSB_MI_PARENT_INFORMATION; + +typedef struct USB_NODE_INFORMATION { + USB_HUB_NODE NodeType; + union { + USB_HUB_INFORMATION HubInformation; + USB_MI_PARENT_INFORMATION MiParentInformation; + } u; +} USB_NODE_INFORMATION, *PUSB_NODE_INFORMATION; + +typedef struct USB_PIPE_INFO { + USB_ENDPOINT_DESCRIPTOR EndpointDescriptor; + ULONG ScheduleOffset; +} USB_PIPE_INFO, *PUSB_PIPE_INFO; + +typedef struct USB_NODE_CONNECTION_INFORMATION_EX { + ULONG ConnectionIndex; + USB_DEVICE_DESCRIPTOR DeviceDescriptor; + UCHAR CurrentConfigurationValue; + UCHAR Speed; + BOOLEAN DeviceIsHub; + USHORT DeviceAddress; + ULONG NumberOfOpenPipes; + USB_CONNECTION_STATUS ConnectionStatus; +// USB_PIPE_INFO PipeList[0]; +} USB_NODE_CONNECTION_INFORMATION_EX, *PUSB_NODE_CONNECTION_INFORMATION_EX; + +typedef struct USB_HUB_CAP_FLAGS { + ULONG HubIsHighSpeedCapable:1; + ULONG HubIsHighSpeed:1; + ULONG HubIsMultiTtCapable:1; + ULONG HubIsMultiTt:1; + ULONG HubIsRoot:1; + ULONG HubIsArmedWakeOnConnect:1; + ULONG ReservedMBZ:26; +} USB_HUB_CAP_FLAGS, *PUSB_HUB_CAP_FLAGS; + +typedef struct USB_HUB_CAPABILITIES { + ULONG HubIs2xCapable : 1; +} USB_HUB_CAPABILITIES, *PUSB_HUB_CAPABILITIES; + +typedef struct USB_HUB_CAPABILITIES_EX { + USB_HUB_CAP_FLAGS CapabilityFlags; +} USB_HUB_CAPABILITIES_EX, *PUSB_HUB_CAPABILITIES_EX; + +#pragma pack(pop) + +/* winusb.dll interface */ + +#define SHORT_PACKET_TERMINATE 0x01 +#define AUTO_CLEAR_STALL 0x02 +#define PIPE_TRANSFER_TIMEOUT 0x03 +#define IGNORE_SHORT_PACKETS 0x04 +#define ALLOW_PARTIAL_READS 0x05 +#define AUTO_FLUSH 0x06 +#define RAW_IO 0x07 +#define MAXIMUM_TRANSFER_SIZE 0x08 +#define AUTO_SUSPEND 0x81 +#define SUSPEND_DELAY 0x83 +#define DEVICE_SPEED 0x01 +#define LowSpeed 0x01 +#define FullSpeed 0x02 +#define HighSpeed 0x03 + +typedef enum USBD_PIPE_TYPE { + UsbdPipeTypeControl, + UsbdPipeTypeIsochronous, + UsbdPipeTypeBulk, + UsbdPipeTypeInterrupt +} USBD_PIPE_TYPE; + +typedef struct { + USBD_PIPE_TYPE PipeType; + UCHAR PipeId; + USHORT MaximumPacketSize; + UCHAR Interval; +} WINUSB_PIPE_INFORMATION, *PWINUSB_PIPE_INFORMATION; + +#pragma pack(1) +typedef struct { + UCHAR request_type; + UCHAR request; + USHORT value; + USHORT index; + USHORT length; +} WINUSB_SETUP_PACKET, *PWINUSB_SETUP_PACKET; +#pragma pack() + +typedef void *WINUSB_INTERFACE_HANDLE, *PWINUSB_INTERFACE_HANDLE; + +DLL_DECLARE(WINAPI, BOOL, WinUsb_Initialize, (HANDLE, PWINUSB_INTERFACE_HANDLE)); +DLL_DECLARE(WINAPI, BOOL, WinUsb_Free, (WINUSB_INTERFACE_HANDLE)); +DLL_DECLARE(WINAPI, BOOL, WinUsb_GetAssociatedInterface, (WINUSB_INTERFACE_HANDLE, UCHAR, PWINUSB_INTERFACE_HANDLE)); +DLL_DECLARE(WINAPI, BOOL, WinUsb_GetDescriptor, (WINUSB_INTERFACE_HANDLE, UCHAR, UCHAR, USHORT, PUCHAR, ULONG, PULONG)); +DLL_DECLARE(WINAPI, BOOL, WinUsb_QueryInterfaceSettings, (WINUSB_INTERFACE_HANDLE, UCHAR, PUSB_INTERFACE_DESCRIPTOR)); +DLL_DECLARE(WINAPI, BOOL, WinUsb_QueryDeviceInformation, (WINUSB_INTERFACE_HANDLE, ULONG, PULONG, PVOID)); +DLL_DECLARE(WINAPI, BOOL, WinUsb_SetCurrentAlternateSetting, (WINUSB_INTERFACE_HANDLE, UCHAR)); +DLL_DECLARE(WINAPI, BOOL, WinUsb_GetCurrentAlternateSetting, (WINUSB_INTERFACE_HANDLE, PUCHAR)); +DLL_DECLARE(WINAPI, BOOL, WinUsb_QueryPipe, (WINUSB_INTERFACE_HANDLE, UCHAR, UCHAR, PWINUSB_PIPE_INFORMATION)); +DLL_DECLARE(WINAPI, BOOL, WinUsb_SetPipePolicy, (WINUSB_INTERFACE_HANDLE, UCHAR, ULONG, ULONG, PVOID)); +DLL_DECLARE(WINAPI, BOOL, WinUsb_GetPipePolicy, (WINUSB_INTERFACE_HANDLE, UCHAR, ULONG, PULONG, PVOID)); +DLL_DECLARE(WINAPI, BOOL, WinUsb_ReadPipe, (WINUSB_INTERFACE_HANDLE, UCHAR, PUCHAR, ULONG, PULONG, LPOVERLAPPED)); +DLL_DECLARE(WINAPI, BOOL, WinUsb_WritePipe, (WINUSB_INTERFACE_HANDLE, UCHAR, PUCHAR, ULONG, PULONG, LPOVERLAPPED)); +DLL_DECLARE(WINAPI, BOOL, WinUsb_ControlTransfer, (WINUSB_INTERFACE_HANDLE, WINUSB_SETUP_PACKET, PUCHAR, ULONG, PULONG, LPOVERLAPPED)); +DLL_DECLARE(WINAPI, BOOL, WinUsb_ResetPipe, (WINUSB_INTERFACE_HANDLE, UCHAR)); +DLL_DECLARE(WINAPI, BOOL, WinUsb_AbortPipe, (WINUSB_INTERFACE_HANDLE, UCHAR)); +DLL_DECLARE(WINAPI, BOOL, WinUsb_FlushPipe, (WINUSB_INTERFACE_HANDLE, UCHAR)); diff --git a/compat/libusb-1.0/libusb/sync.c b/compat/libusb-1.0/libusb/sync.c new file mode 100644 index 0000000..ac3ab7e --- /dev/null +++ b/compat/libusb-1.0/libusb/sync.c @@ -0,0 +1,322 @@ +/* + * Synchronous I/O functions for libusb + * Copyright (C) 2007-2008 Daniel Drake + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include + +#include "libusbi.h" + +/** + * @defgroup syncio Synchronous device I/O + * + * This page documents libusb's synchronous (blocking) API for USB device I/O. + * This interface is easy to use but has some limitations. More advanced users + * may wish to consider using the \ref asyncio "asynchronous I/O API" instead. + */ + +static void LIBUSB_CALL ctrl_transfer_cb(struct libusb_transfer *transfer) +{ + int *completed = transfer->user_data; + *completed = 1; + usbi_dbg("actual_length=%d", transfer->actual_length); + /* caller interprets result and frees transfer */ +} + +/** \ingroup syncio + * Perform a USB control transfer. + * + * The direction of the transfer is inferred from the bmRequestType field of + * the setup packet. + * + * The wValue, wIndex and wLength fields values should be given in host-endian + * byte order. + * + * \param dev_handle a handle for the device to communicate with + * \param bmRequestType the request type field for the setup packet + * \param bRequest the request field for the setup packet + * \param wValue the value field for the setup packet + * \param wIndex the index field for the setup packet + * \param data a suitably-sized data buffer for either input or output + * (depending on direction bits within bmRequestType) + * \param wLength the length field for the setup packet. The data buffer should + * be at least this size. + * \param timeout timeout (in millseconds) that this function should wait + * before giving up due to no response being received. For an unlimited + * timeout, use value 0. + * \returns on success, the number of bytes actually transferred + * \returns LIBUSB_ERROR_TIMEOUT if the transfer timed out + * \returns LIBUSB_ERROR_PIPE if the control request was not supported by the + * device + * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected + * \returns another LIBUSB_ERROR code on other failures + */ +int API_EXPORTED libusb_control_transfer(libusb_device_handle *dev_handle, + uint8_t bmRequestType, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, + unsigned char *data, uint16_t wLength, unsigned int timeout) +{ + struct libusb_transfer *transfer = libusb_alloc_transfer(0); + unsigned char *buffer; + int completed = 0; + int r; + + if (!transfer) + return LIBUSB_ERROR_NO_MEM; + + buffer = malloc(LIBUSB_CONTROL_SETUP_SIZE + wLength); + if (!buffer) { + libusb_free_transfer(transfer); + return LIBUSB_ERROR_NO_MEM; + } + + libusb_fill_control_setup(buffer, bmRequestType, bRequest, wValue, wIndex, + wLength); + if ((bmRequestType & LIBUSB_ENDPOINT_DIR_MASK) == LIBUSB_ENDPOINT_OUT) + memcpy(buffer + LIBUSB_CONTROL_SETUP_SIZE, data, wLength); + + libusb_fill_control_transfer(transfer, dev_handle, buffer, + ctrl_transfer_cb, &completed, timeout); + transfer->flags = LIBUSB_TRANSFER_FREE_BUFFER; + r = libusb_submit_transfer(transfer); + if (r < 0) { + libusb_free_transfer(transfer); + return r; + } + + while (!completed) { + r = libusb_handle_events_completed(HANDLE_CTX(dev_handle), &completed); + if (r < 0) { + if (r == LIBUSB_ERROR_INTERRUPTED) + continue; + libusb_cancel_transfer(transfer); + while (!completed) + if (libusb_handle_events_completed(HANDLE_CTX(dev_handle), &completed) < 0) + break; + libusb_free_transfer(transfer); + return r; + } + } + + if ((bmRequestType & LIBUSB_ENDPOINT_DIR_MASK) == LIBUSB_ENDPOINT_IN) + memcpy(data, libusb_control_transfer_get_data(transfer), + transfer->actual_length); + + switch (transfer->status) { + case LIBUSB_TRANSFER_COMPLETED: + r = transfer->actual_length; + break; + case LIBUSB_TRANSFER_TIMED_OUT: + r = LIBUSB_ERROR_TIMEOUT; + break; + case LIBUSB_TRANSFER_STALL: + r = LIBUSB_ERROR_PIPE; + break; + case LIBUSB_TRANSFER_NO_DEVICE: + r = LIBUSB_ERROR_NO_DEVICE; + break; + case LIBUSB_TRANSFER_OVERFLOW: + r = LIBUSB_ERROR_OVERFLOW; + break; + case LIBUSB_TRANSFER_ERROR: + case LIBUSB_TRANSFER_CANCELLED: + r = LIBUSB_ERROR_IO; + break; + default: + usbi_warn(HANDLE_CTX(dev_handle), + "unrecognised status code %d", transfer->status); + r = LIBUSB_ERROR_OTHER; + } + + libusb_free_transfer(transfer); + return r; +} + +static void LIBUSB_CALL bulk_transfer_cb(struct libusb_transfer *transfer) +{ + int *completed = transfer->user_data; + *completed = 1; + usbi_dbg("actual_length=%d", transfer->actual_length); + /* caller interprets results and frees transfer */ +} + +static int do_sync_bulk_transfer(struct libusb_device_handle *dev_handle, + unsigned char endpoint, unsigned char *buffer, int length, + int *transferred, unsigned int timeout, unsigned char type) +{ + struct libusb_transfer *transfer = libusb_alloc_transfer(0); + int completed = 0; + int r; + + if (!transfer) + return LIBUSB_ERROR_NO_MEM; + + libusb_fill_bulk_transfer(transfer, dev_handle, endpoint, buffer, length, + bulk_transfer_cb, &completed, timeout); + transfer->type = type; + + r = libusb_submit_transfer(transfer); + if (r < 0) { + libusb_free_transfer(transfer); + return r; + } + + while (!completed) { + r = libusb_handle_events_completed(HANDLE_CTX(dev_handle), &completed); + if (r < 0) { + if (r == LIBUSB_ERROR_INTERRUPTED) + continue; + libusb_cancel_transfer(transfer); + while (!completed) + if (libusb_handle_events_completed(HANDLE_CTX(dev_handle), &completed) < 0) + break; + libusb_free_transfer(transfer); + return r; + } + } + + *transferred = transfer->actual_length; + switch (transfer->status) { + case LIBUSB_TRANSFER_COMPLETED: + r = 0; + break; + case LIBUSB_TRANSFER_TIMED_OUT: + r = LIBUSB_ERROR_TIMEOUT; + break; + case LIBUSB_TRANSFER_STALL: + r = LIBUSB_ERROR_PIPE; + break; + case LIBUSB_TRANSFER_OVERFLOW: + r = LIBUSB_ERROR_OVERFLOW; + break; + case LIBUSB_TRANSFER_NO_DEVICE: + r = LIBUSB_ERROR_NO_DEVICE; + break; + case LIBUSB_TRANSFER_ERROR: + case LIBUSB_TRANSFER_CANCELLED: + r = LIBUSB_ERROR_IO; + break; + default: + usbi_warn(HANDLE_CTX(dev_handle), + "unrecognised status code %d", transfer->status); + r = LIBUSB_ERROR_OTHER; + } + + libusb_free_transfer(transfer); + return r; +} + +/** \ingroup syncio + * Perform a USB bulk transfer. The direction of the transfer is inferred from + * the direction bits of the endpoint address. + * + * For bulk reads, the length field indicates the maximum length of + * data you are expecting to receive. If less data arrives than expected, + * this function will return that data, so be sure to check the + * transferred output parameter. + * + * You should also check the transferred parameter for bulk writes. + * Not all of the data may have been written. + * + * Also check transferred when dealing with a timeout error code. + * libusb may have to split your transfer into a number of chunks to satisfy + * underlying O/S requirements, meaning that the timeout may expire after + * the first few chunks have completed. libusb is careful not to lose any data + * that may have been transferred; do not assume that timeout conditions + * indicate a complete lack of I/O. + * + * \param dev_handle a handle for the device to communicate with + * \param endpoint the address of a valid endpoint to communicate with + * \param data a suitably-sized data buffer for either input or output + * (depending on endpoint) + * \param length for bulk writes, the number of bytes from data to be sent. for + * bulk reads, the maximum number of bytes to receive into the data buffer. + * \param transferred output location for the number of bytes actually + * transferred. + * \param timeout timeout (in millseconds) that this function should wait + * before giving up due to no response being received. For an unlimited + * timeout, use value 0. + * + * \returns 0 on success (and populates transferred) + * \returns LIBUSB_ERROR_TIMEOUT if the transfer timed out (and populates + * transferred) + * \returns LIBUSB_ERROR_PIPE if the endpoint halted + * \returns LIBUSB_ERROR_OVERFLOW if the device offered more data, see + * \ref packetoverflow + * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected + * \returns another LIBUSB_ERROR code on other failures + */ +int API_EXPORTED libusb_bulk_transfer(struct libusb_device_handle *dev_handle, + unsigned char endpoint, unsigned char *data, int length, int *transferred, + unsigned int timeout) +{ + return do_sync_bulk_transfer(dev_handle, endpoint, data, length, + transferred, timeout, LIBUSB_TRANSFER_TYPE_BULK); +} + +/** \ingroup syncio + * Perform a USB interrupt transfer. The direction of the transfer is inferred + * from the direction bits of the endpoint address. + * + * For interrupt reads, the length field indicates the maximum length + * of data you are expecting to receive. If less data arrives than expected, + * this function will return that data, so be sure to check the + * transferred output parameter. + * + * You should also check the transferred parameter for interrupt + * writes. Not all of the data may have been written. + * + * Also check transferred when dealing with a timeout error code. + * libusb may have to split your transfer into a number of chunks to satisfy + * underlying O/S requirements, meaning that the timeout may expire after + * the first few chunks have completed. libusb is careful not to lose any data + * that may have been transferred; do not assume that timeout conditions + * indicate a complete lack of I/O. + * + * The default endpoint bInterval value is used as the polling interval. + * + * \param dev_handle a handle for the device to communicate with + * \param endpoint the address of a valid endpoint to communicate with + * \param data a suitably-sized data buffer for either input or output + * (depending on endpoint) + * \param length for bulk writes, the number of bytes from data to be sent. for + * bulk reads, the maximum number of bytes to receive into the data buffer. + * \param transferred output location for the number of bytes actually + * transferred. + * \param timeout timeout (in millseconds) that this function should wait + * before giving up due to no response being received. For an unlimited + * timeout, use value 0. + * + * \returns 0 on success (and populates transferred) + * \returns LIBUSB_ERROR_TIMEOUT if the transfer timed out + * \returns LIBUSB_ERROR_PIPE if the endpoint halted + * \returns LIBUSB_ERROR_OVERFLOW if the device offered more data, see + * \ref packetoverflow + * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected + * \returns another LIBUSB_ERROR code on other error + */ +int API_EXPORTED libusb_interrupt_transfer( + struct libusb_device_handle *dev_handle, unsigned char endpoint, + unsigned char *data, int length, int *transferred, unsigned int timeout) +{ + return do_sync_bulk_transfer(dev_handle, endpoint, data, length, + transferred, timeout, LIBUSB_TRANSFER_TYPE_INTERRUPT); +} + diff --git a/compat/libusb-1.0/libusb/version.h b/compat/libusb-1.0/libusb/version.h new file mode 100644 index 0000000..01ee5ef --- /dev/null +++ b/compat/libusb-1.0/libusb/version.h @@ -0,0 +1,18 @@ +/* This file is parsed by m4 and windres and RC.EXE so please keep it simple. */ +#ifndef LIBUSB_MAJOR +#define LIBUSB_MAJOR 1 +#endif +#ifndef LIBUSB_MINOR +#define LIBUSB_MINOR 0 +#endif +#ifndef LIBUSB_MICRO +#define LIBUSB_MICRO 16 +#endif +/* LIBUSB_NANO may be used for Windows internal versioning. 0 means unused. */ +#ifndef LIBUSB_NANO +#define LIBUSB_NANO 0 +#endif +/* LIBUSB_RC is the release candidate suffix. Should normally be empty. */ +#ifndef LIBUSB_RC +#define LIBUSB_RC "-rc10" +#endif diff --git a/compat/libusb-1.0/m4/.gitignore b/compat/libusb-1.0/m4/.gitignore new file mode 100644 index 0000000..464ba5c --- /dev/null +++ b/compat/libusb-1.0/m4/.gitignore @@ -0,0 +1,5 @@ +libtool.m4 +lt~obsolete.m4 +ltoptions.m4 +ltsugar.m4 +ltversion.m4 diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..c2884df --- /dev/null +++ b/configure.ac @@ -0,0 +1,832 @@ +##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--## +##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--## +m4_define([v_maj], [1]) +m4_define([v_min], [0]) +m4_define([v_mic], [0]) +##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--## +m4_define([v_ver], [v_maj.v_min.v_mic]) +m4_define([lt_rev], m4_eval(v_maj + v_min)) +m4_define([lt_cur], v_mic) +m4_define([lt_age], v_min) +##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--## +##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--## + +AC_INIT([bmminer], [v_ver], [support@bitmain.com]) + +AC_PREREQ(2.59) +AC_CANONICAL_SYSTEM +AC_CONFIG_MACRO_DIR([m4]) +AC_CONFIG_SRCDIR([cgminer.c]) +AC_CONFIG_HEADERS([config.h]) + +AM_INIT_AUTOMAKE([foreign subdir-objects]) +m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) +AC_USE_SYSTEM_EXTENSIONS + +##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--## +##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--## +m4_ifdef([v_rev], , [m4_define([v_rev], [0])]) +m4_ifdef([v_rel], , [m4_define([v_rel], [])]) +AC_DEFINE_UNQUOTED(CGMINER_MAJOR_VERSION, [v_maj], [Major version]) +AC_DEFINE_UNQUOTED(CGMINER_MINOR_VERSION, [v_min], [Minor version]) +AC_DEFINE_UNQUOTED(CGMINER_MINOR_SUBVERSION, [v_mic], [Micro version]) +version_info="lt_rev:lt_cur:lt_age" +release_info="v_rel" +AC_SUBST(version_info) +AC_SUBST(release_info) +##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--## +##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--## +VMAJ=v_maj +AC_SUBST(VMAJ) + +AC_CANONICAL_BUILD +AC_CANONICAL_HOST + +dnl Make sure anyone changing configure.ac/Makefile.am has a clue +AM_MAINTAINER_MODE + +dnl Checks for programs +AC_PROG_CC +gl_EARLY +AC_PROG_GCC_TRADITIONAL +AM_PROG_CC_C_O +LT_INIT([disable-shared]) + +gl_INIT + +dnl Checks for header files. +AC_HEADER_STDC +AC_CHECK_HEADERS(syslog.h) + +AC_FUNC_ALLOCA + +have_win32=false +PTHREAD_FLAGS="-lpthread" +DLOPEN_FLAGS="-ldl" +WS2_LIBS="" +MM_LIBS="" +MATH_LIBS="-lm" +RT_LIBS="-lrt" + +case $target in + amd64-*) + have_x86_64=true + ;; + x86_64-*) + have_x86_64=true + ;; + *) + have_x86_64=false + ;; +esac + +case $target in + *-*-linux-gnu*) + have_linux=true + ;; + *-*-mingw*) + have_win32=true + PTHREAD_FLAGS="" + DLOPEN_FLAGS="" + WS2_LIBS="-lws2_32" + MM_LIBS="-lwinmm" + RT_LIBS="" + AC_DEFINE([_WIN32_WINNT], [0x0501], "WinNT version for XP+ support") + ;; + powerpc-*-darwin*) + have_darwin=true + CFLAGS="$CFLAGS -faltivec" + PTHREAD_FLAGS="" + RT_LIBS="" + ;; + *-*-darwin*) + have_darwin=true + PTHREAD_FLAGS="" + RT_LIBS="" + ;; +esac + +has_winpthread=false +if test "x$have_win32" = xtrue; then + has_winpthread=true + AC_CHECK_LIB(winpthread, nanosleep, , has_winpthread=false) + PTHREAD_LIBS=-lwinpthread +fi + +if test "x$has_winpthread" != xtrue; then + AC_CHECK_LIB(pthread, pthread_create, , + AC_MSG_ERROR([Could not find pthread library - please install libpthread])) + PTHREAD_LIBS=-lpthread +fi + +# Drivers that are designed to be run on dedicated hardware should set standalone to yes +# All drivers should prepend an x to the drivercount + +standalone="no" +bmsc="no" +drivercount="" + +AC_ARG_ENABLE([bmsc], + [AC_HELP_STRING([--enable-bmsc],[Compile support for Bitmain Single Chain (default disabled)])], + [bmsc=$enableval] + ) +if test "x$bmsc" = xyes; then + AC_DEFINE([USE_BMSC], [1], [Defined to 1 if Bitmain Single Chain support is wanted]) + drivercount=x$drivercount + standalone="yes" +fi +AM_CONDITIONAL([HAS_BMSC], [test x$bmsc = xyes]) + + +bitmain="no" + +AC_ARG_ENABLE([bitmain], + [AC_HELP_STRING([--enable-bitmain],[Compile support for Bitmain Multi Chain (default disabled)])], + [bitmain=$enableval] + ) +if test "x$bitmain" = xyes; then + AC_DEFINE([USE_BITMAIN], [1], [Defined to 1 if Bitmain Multi Chain support is wanted]) + drivercount=x$drivercount + standalone="yes" +fi +AM_CONDITIONAL([HAS_BITMAIN], [test x$bitmain = xyes]) + +bitmain_c5="no" + +AC_ARG_ENABLE([bitmain_c5], + [AC_HELP_STRING([--enable-bitmain-c5],[Compile support for Bitmain C5(default disabled)])], + [bitmain_c5=$enableval] + ) +if test "x$bitmain_c5" = xyes; then + AC_DEFINE([USE_BITMAIN_C5], [1], [Defined to 1 if Bitmain C5 support is wanted]) + drivercount=x$drivercount + standalone="yes" +fi +AM_CONDITIONAL([HAS_BITMAIN_C5], [test x$bitmain_c5 = xyes]) + + +avalon="no" + +AC_ARG_ENABLE([avalon], + [AC_HELP_STRING([--enable-avalon],[Compile support for Avalon (default disabled)])], + [avalon=$enableval] + ) +if test "x$avalon" = xyes; then + AC_DEFINE([USE_AVALON], [1], [Defined to 1 if Avalon support is wanted]) + drivercount=x$drivercount +fi +AM_CONDITIONAL([HAS_AVALON], [test x$avalon = xyes]) + + +avalon2="no" + +AC_ARG_ENABLE([avalon2], + [AC_HELP_STRING([--enable-avalon2],[Compile support for Avalon2 (default disabled)])], + [avalon2=$enableval] + ) +if test "x$avalon2" = xyes; then + AC_DEFINE([USE_AVALON2], [1], [Defined to 1 if Avalon2 support is wanted]) + drivercount=x$drivercount +fi +AM_CONDITIONAL([HAS_AVALON2], [test x$avalon2 = xyes]) + + +avalon4="no" + +AC_ARG_ENABLE([avalon4], + [AC_HELP_STRING([--enable-avalon4],[Compile support for Avalon4 (default disabled)])], + [avalon4=$enableval] + ) +if test "x$avalon4" = xyes; then + AC_DEFINE([USE_AVALON4], [1], [Defined to 1 if Avalon4 support is wanted]) +fi +AM_CONDITIONAL([HAS_AVALON4], [test x$avalon4 = xyes]) + + +bab="no" + +AC_ARG_ENABLE([bab], + [AC_HELP_STRING([--enable-bab],[Compile support for BlackArrow Bitfury STANDALONE(default disabled)])], + [bab=$enableval] + ) +if test "x$bab" = xyes; then + AC_DEFINE([USE_BAB], [1], [Defined to 1 if BlackArrow Bitfury support is wanted]) + drivercount=x$drivercount + standalone="yes" +fi +AM_CONDITIONAL([HAS_BAB], [test x$bab = xyes]) + + +bflsc="no" + +AC_ARG_ENABLE([bflsc], + [AC_HELP_STRING([--enable-bflsc],[Compile support for BFL ASICs (default disabled)])], + [bflsc=$enableval] + ) +if test "x$bflsc" = xyes; then + AC_DEFINE([USE_BFLSC], [1], [Defined to 1 if BFL ASIC support is wanted]) + drivercount=x$drivercount +fi +AM_CONDITIONAL([HAS_BFLSC], [test x$bflsc = xyes]) + + +bitforce="no" + +AC_ARG_ENABLE([bitforce], + [AC_HELP_STRING([--enable-bitforce],[Compile support for BitForce FPGAs (default disabled)])], + [bitforce=$enableval] + ) +if test "x$bitforce" = xyes; then + AC_DEFINE([USE_BITFORCE], [1], [Defined to 1 if BitForce support is wanted]) + drivercount=x$drivercount +fi +AM_CONDITIONAL([HAS_BITFORCE], [test x$bitforce = xyes]) + + +bitfury="no" + +AC_ARG_ENABLE([bitfury], + [AC_HELP_STRING([--enable-bitfury],[Compile support for BitFury ASICs (default disabled)])], + [bitfury=$enableval] + ) +if test "x$bitfury" = xyes; then + AC_DEFINE([USE_BITFURY], [1], [Defined to 1 if BitFury ASIC support is wanted]) + drivercount=x$drivercount +fi +AM_CONDITIONAL([HAS_BITFURY], [test x$bitfury = xyes]) + + +bitmine_A1="no" + +AC_ARG_ENABLE([bitmine_A1], + [AC_HELP_STRING([--enable-bitmine_A1],[Compile support for Bitmine.ch A1 ASICs STANDALONE(default disabled)])], + [bitmine_A1=$enableval] + ) +if test "x$bitmine_A1" = xyes; then + AC_DEFINE([USE_BITMINE_A1], [1], [Defined to 1 if Bitmine A1 support is wanted]) + drivercount=x$drivercount + standalone="yes" +fi +AM_CONDITIONAL([HAS_BITMINE_A1], [test x$bitmine_A1 = xyes]) + + +blockerupter="no" + +AC_ARG_ENABLE([blockerupter], + [AC_HELP_STRING([--enable-blockerupter],[Compile support for BlockErupter (default disabled)])], + [blockerupter=$enableval] + ) +if test "x$blockerupter" = xyes; then + AC_DEFINE([USE_BLOCKERUPTER], [1], [Defined to 1 if BlockErupter support is wanted]) + drivercount=x$drivercount +fi +AM_CONDITIONAL([HAS_BLOCKERUPTER], [test x$blockerupter = xyes]) + + +cointerra="no" + +AC_ARG_ENABLE([cointerra], + [AC_HELP_STRING([--enable-cointerra],[Compile support for Cointerra ASICs (default disabled)])], + [cointerra=$enableval] + ) +if test "x$cointerra" = xyes; then + AC_DEFINE([USE_COINTERRA], [1], [Defined to 1 if Cointerra support is wanted]) + drivercount=x$drivercount +fi +AM_CONDITIONAL([HAS_COINTERRA], [test x$cointerra = xyes]) + + +drillbit="no" + +AC_ARG_ENABLE([drillbit], + [AC_HELP_STRING([--enable-drillbit],[Compile support for Drillbit BitFury ASICs (default disabled)])], + [drillbit=$enableval] + ) +if test "x$drillbit" = xyes; then + AC_DEFINE([USE_DRILLBIT], [1], [Defined to 1 if Drillbit BitFury support is wanted]) + drivercount=x$drivercount +fi +AM_CONDITIONAL([HAS_DRILLBIT], [test x$drillbit = xyes]) + + +hashfast="no" + +AC_ARG_ENABLE([hashfast], + [AC_HELP_STRING([--enable-hashfast],[Compile support for Hashfast (default disabled)])], + [hashfast=$enableval] + ) +if test "x$hashfast" = xyes; then + AC_DEFINE([USE_HASHFAST], [1], [Defined to 1 if Hashfast support is wanted]) + drivercount=x$drivercount +fi +AM_CONDITIONAL([HAS_HASHFAST], [test x$hashfast = xyes]) + + +hashratio="no" + +AC_ARG_ENABLE([hashratio], + [AC_HELP_STRING([--enable-hashratio],[Compile support for Hashratio (default disabled)])], + [hashratio=$enableval] + ) +if test "x$hashratio" = xyes; then + AC_DEFINE([USE_HASHRATIO], [1], [Defined to 1 if Hashratiosupport is wanted]) + drivercount=x$drivercount +fi +AM_CONDITIONAL([HAS_HASHRATIO], [test x$hashratio = xyes]) + + +icarus="no" + +AC_ARG_ENABLE([icarus], + [AC_HELP_STRING([--enable-icarus],[Compile support for Icarus (default disabled)])], + [icarus=$enableval] + ) +if test "x$icarus" = xyes; then + AC_DEFINE([USE_ICARUS], [1], [Defined to 1 if Icarus support is wanted]) + drivercount=x$drivercount +fi +AM_CONDITIONAL([HAS_ICARUS], [test x$icarus = xyes]) + + +klondike="no" + +AC_ARG_ENABLE([klondike], + [AC_HELP_STRING([--enable-klondike],[Compile support for Klondike (default disabled)])], + [klondike=$enableval] + ) +if test "x$klondike" = xyes; then + AC_DEFINE([USE_KLONDIKE], [1], [Defined to 1 if Klondike support is wanted]) + drivercount=x$drivercount +fi +AM_CONDITIONAL([HAS_KLONDIKE], [test x$klondike = xyes]) + + +knc="no" + +AC_ARG_ENABLE([knc], + [AC_HELP_STRING([--enable-knc],[Compile support for KnC miners STANDALONE(default disabled)])], + [knc=$enableval] + ) +if test "x$knc" = xyes; then + AC_DEFINE([USE_KNC], [1], [Defined to 1 if KnC miner support is wanted]) + drivercount=x$drivercount + standalone="yes" +fi +AM_CONDITIONAL([HAS_KNC], [test x$knc = xyes]) + + +minion="no" + +AC_ARG_ENABLE([minion], + [AC_HELP_STRING([--enable-minion],[Compile support for Minion BlackArrow ASIC STANDALONE(default disabled)])], + [minion=$enableval] + ) +if test "x$minion" = xyes; then + AC_DEFINE([USE_MINION], [1], [Defined to 1 if Minion BlackArrow ASIC support is wanted]) + drivercount=x$drivercount + standalone="yes" +fi +AM_CONDITIONAL([HAS_MINION], [test x$minion = xyes]) + + +modminer="no" + +AC_ARG_ENABLE([modminer], + [AC_HELP_STRING([--enable-modminer],[Compile support for ModMiner FPGAs(default disabled)])], + [modminer=$enableval] + ) +if test "x$modminer" = xyes; then + AC_DEFINE([USE_MODMINER], [1], [Defined to 1 if ModMiner support is wanted]) + drivercount=x$drivercount +fi +AM_CONDITIONAL([HAS_MODMINER], [test x$modminer = xyes]) + + +sp10="no" + +AC_ARG_ENABLE([sp10], + [AC_HELP_STRING([--enable-sp10],[Compile support for Spondoolies SP10 STANDALONE(default disabled)])], + [sp10=$enableval] + ) +if test "x$sp10" = xyes; then + AC_DEFINE([USE_SP10], [1], [Defined to 1 if Spondoolies SP10 support is wanted]) + drivercount=x$drivercount + standalone="yes" +fi +AM_CONDITIONAL([HAS_SP10], [test x$sp10 = xyes]) + + + +sp30="no" + +AC_ARG_ENABLE([sp30], + [AC_HELP_STRING([--enable-sp30],[Compile support for Spondoolies SP30 STANDALONE(default disabled)])], + [sp30=$enableval] + ) +if test "x$sp30" = xyes; then + AC_DEFINE([USE_SP30], [1], [Defined to 1 if SP30 support is wanted]) + drivercount=x$drivercount + standalone="yes" +fi +AM_CONDITIONAL([HAS_SP30], [test x$sp30 = xyes]) + + +forcecombo="no" + +AC_ARG_ENABLE([forcecombo], + [AC_HELP_STRING([--enable-forcecombo],[Allow combinations of drivers not intended to be built together(default disabled)])], + [forcecombo=$enableval] + ) +if test "x$forcecombo" = xyes; then + standalone="no" +fi + +curses="auto" + +AC_ARG_WITH([curses], + [AC_HELP_STRING([--without-curses],[Compile support for curses TUI (default enabled)])], + [curses=$withval] + ) +if test "x$curses" = "xno"; then + cursesmsg='User specified --without-curses. TUI support DISABLED' +else + AC_SEARCH_LIBS(addstr, ncurses pdcurses, [ + curses=yes + cursesmsg="FOUND: ${ac_cv_search_addstr}" + AC_DEFINE([HAVE_CURSES], [1], [Defined to 1 if curses TUI support is wanted]) + ], [ + if test "x$curses" = "xyes"; then + AC_MSG_ERROR([Could not find curses library - please install libncurses-dev or pdcurses-dev (or configure --without-curses)]) + else + AC_MSG_WARN([Could not find curses library - if you want a TUI, install libncurses-dev or pdcurses-dev]) + curses=no + cursesmsg='NOT FOUND. TUI support DISABLED' + fi + ]) +fi + + +#Add a new device to this list if it needs libusb, along with a no on the end. +if test x$avalon$avalon2$avalon4$bitforce$bitfury$blockerupter$modminer$bflsc$icarus$hashfast$hashratio$klondike$drillbit$cointerra$bmsc$bitmain != xnononononononononononononononono; then + want_usbutils=true +else + want_usbutils=false +fi + +if test x$bitfury != xno; then + want_libbitfury=true +else + want_libbitfury=false +fi + +if test x$avalon2$avalon4$hashratio$bitmain_c5 != xnonono; then + want_crc16=true +else + want_crc16=false +fi + +AM_CONDITIONAL([NEED_FPGAUTILS], [test x$modminer != xno]) +AM_CONDITIONAL([WANT_USBUTILS], [test x$want_usbutils != xfalse]) +AM_CONDITIONAL([WANT_LIBBITFURY], [test x$want_libbitfury != xfalse]) +AM_CONDITIONAL([HAVE_CURSES], [test x$curses = xyes]) +AM_CONDITIONAL([HAVE_WINDOWS], [test x$have_win32 = xtrue]) +AM_CONDITIONAL([HAVE_x86_64], [test x$have_x86_64 = xtrue]) +AM_CONDITIONAL([WANT_CRC16], [test x$want_crc16 != xfalse]) + +if test "x$want_usbutils" != xfalse; then + dlibusb="no" + AC_DEFINE([USE_USBUTILS], [1], [Defined to 1 if usbutils support required]) + AC_ARG_WITH([system-libusb], + [AC_HELP_STRING([--with-system-libusb],[NOT RECOMMENDED! Compile against dynamic system libusb. (Default use included static libusb)])], + [dlibusb=$withval] + ) + + if test "x$dlibusb" != xno; then + case $target in + *-*-freebsd*) + LIBUSB_LIBS="-lusb" + LIBUSB_CFLAGS="" + AC_DEFINE(HAVE_LIBUSB, 1, [Define if you have libusb-1.0]) + ;; + *) + PKG_CHECK_MODULES(LIBUSB, libusb-1.0, [AC_DEFINE(HAVE_LIBUSB, 1, [Define if you have libusb-1.0])], [AC_MSG_ERROR([Could not find usb library - please install libusb-1.0])]) + ;; + esac + else + AC_CONFIG_SUBDIRS([compat/libusb-1.0]) + LIBUSB_LIBS="compat/libusb-1.0/libusb/.libs/libusb-1.0.a" + if test "x$have_linux" = "xtrue"; then + AC_ARG_ENABLE([udev], + [AC_HELP_STRING([--disable-udev],[Disable building libusb with udev])], + [udev=$enableval] + ) + + if test "x$udev" != xno; then + LIBUSB_LIBS+=" -ludev" + fi + fi + if test "x$have_darwin" = "xtrue"; then + LIBUSB_LIBS+=" -lobjc" + LDFLAGS+=" -framework CoreFoundation -framework IOKit" + fi + fi +else + LIBUSB_LIBS="" +fi + +AM_CONDITIONAL([WANT_STATIC_LIBUSB], [test x$dlibusb = xno]) + +AC_CONFIG_SUBDIRS([compat/jansson-2.6]) +JANSSON_LIBS="compat/jansson-2.6/src/.libs/libjansson.a" + +PKG_PROG_PKG_CONFIG() + +if test "x$have_cgminer_sdk" = "xtrue"; then + if test "x$have_x86_64" = xtrue; then + ARCH_DIR=x86_64 + else + ARCH_DIR=x86 + fi + PKG_CONFIG="${PKG_CONFIG:-pkg-config} --define-variable=arch=$ARCH_DIR --define-variable=target=$target --define-variable=cgminersdkdir=$CGMINER_SDK" + PKG_CONFIG_PATH="$CGMINER_SDK/lib/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}" +fi + +AC_SUBST(LIBUSB_LIBS) +AC_SUBST(LIBUSB_CFLAGS) + +AC_ARG_ENABLE([libcurl], + [AC_HELP_STRING([--disable-libcurl],[Disable building with libcurl for getwork and GBT support])], + [libcurl=$enableval] + ) + +if test "x$libcurl" != xno; then + if test "x$have_win32" != xtrue; then + PKG_CHECK_MODULES([LIBCURL], [libcurl >= 7.25.0], [AC_DEFINE([CURL_HAS_KEEPALIVE], [1], [Defined if version of curl supports keepalive.])], + [PKG_CHECK_MODULES([LIBCURL], [libcurl >= 7.18.2], ,[AC_MSG_ERROR([Missing required libcurl dev >= 7.18.2])])]) + else + PKG_CHECK_MODULES([LIBCURL], [libcurl >= 7.25.0], ,[AC_MSG_ERROR([Missing required libcurl dev >= 7.25.0])]) + AC_DEFINE([CURL_HAS_KEEPALIVE], [1]) + fi + AC_DEFINE([HAVE_LIBCURL], [1], [Defined to 1 if libcurl support built in]) +else + LIBCURL_LIBS="" +fi +AC_SUBST(LIBCURL_LIBS) + + +#check execv signature +AC_COMPILE_IFELSE([AC_LANG_SOURCE([ + #include + int execv(const char*, const char*const*); + ])], + AC_DEFINE([EXECV_2ND_ARG_TYPE], [const char* const*], [int execv(const char*, const char*const*);]), + AC_DEFINE([EXECV_2ND_ARG_TYPE], [char* const*], [int execv(const char*, char*const*);])) + +dnl CCAN wants to know a lot of vars. +# All the configuration checks. Regrettably, the __attribute__ checks will +# give false positives on old GCCs, since they just cause warnings. But that's +# fairly harmless. +AC_COMPILE_IFELSE([AC_LANG_SOURCE([static void __attribute__((cold)) cleanup(void) { }])], + AC_DEFINE([HAVE_ATTRIBUTE_COLD], [1], + [Define if __attribute__((cold))])) +AC_COMPILE_IFELSE([AC_LANG_SOURCE([static void __attribute__((const)) cleanup(void) { }])], + AC_DEFINE([HAVE_ATTRIBUTE_CONST], [1], + [Define if __attribute__((const))])) +AC_COMPILE_IFELSE([AC_LANG_SOURCE([static void __attribute__((noreturn)) cleanup(void) { exit(1); }])], + AC_DEFINE([HAVE_ATTRIBUTE_NORETURN], [1], + [Define if __attribute__((noreturn))])) +AC_COMPILE_IFELSE([AC_LANG_SOURCE([static void __attribute__((format(__printf__, 1, 2))) cleanup(const char *fmt, ...) { }])], + AC_DEFINE([HAVE_ATTRIBUTE_PRINTF], [1], + [Define if __attribute__((format(__printf__)))])) +AC_COMPILE_IFELSE([AC_LANG_SOURCE([static void __attribute__((unused)) cleanup(void) { }])], + AC_DEFINE([HAVE_ATTRIBUTE_UNUSED], [1], + [Define if __attribute__((unused))])) +AC_COMPILE_IFELSE([AC_LANG_SOURCE([static void __attribute__((used)) cleanup(void) { }])], + AC_DEFINE([HAVE_ATTRIBUTE_USED], [1], + [Define if __attribute__((used))])) +AC_LINK_IFELSE([AC_LANG_SOURCE([int main(void) { return __builtin_constant_p(1) ? 0 : 1; }])], + AC_DEFINE([HAVE_BUILTIN_CONSTANT_P], [1], + [Define if have __builtin_constant_p])) +AC_LINK_IFELSE([AC_LANG_SOURCE([int main(void) { return __builtin_types_compatible_p(char *, int) ? 1 : 0; }])], + AC_DEFINE([HAVE_BUILTIN_TYPES_COMPATIBLE_P], [1], + [Define if have __builtin_types_compatible_p])) +AC_COMPILE_IFELSE([AC_LANG_SOURCE([static int __attribute__((warn_unused_result)) func(int x) { return x; }])], + AC_DEFINE([HAVE_WARN_UNUSED_RESULT], [1], + [Define if __attribute__((warn_unused_result))])) + +if test "x$prefix" = xNONE; then + prefix=/usr/local +fi + +AC_DEFINE_UNQUOTED([CGMINER_PREFIX], ["$prefix/bin"], [Path to cgminer install]) + +AC_SUBST(JANSSON_LIBS) +AC_SUBST(PTHREAD_FLAGS) +AC_SUBST(DLOPEN_FLAGS) +AC_SUBST(PTHREAD_LIBS) +AC_SUBST(NCURSES_LIBS) +AC_SUBST(PDCURSES_LIBS) +AC_SUBST(WS2_LIBS) +AC_SUBST(MM_LIBS) +AC_SUBST(MATH_LIBS) +AC_SUBST(RT_LIBS) + +AC_CONFIG_FILES([ + Makefile + compat/Makefile + ccan/Makefile + lib/Makefile + ]) +AC_OUTPUT + + +echo +echo +echo +echo "------------------------------------------------------------------------" +echo "$PACKAGE $VERSION" +echo "------------------------------------------------------------------------" +echo +echo +echo "Configuration Options Summary:" +echo + +if test "x$libcurl" != xno; then + echo " libcurl(GBT+getwork).: Enabled: $LIBCURL_LIBS" +else + echo " libcurl(GBT+getwork).: Disabled" +fi + +echo " curses.TUI...........: $cursesmsg" + + +echo +if test "x$bmsc" = xyes; then + echo " Bitmain.SingleChain..: Enabled" +else + echo " Bitmain.SingleChain..: Disabled" +fi + +if test "x$bitmain" = xyes; then + echo " Bitmain.MultiChain...: Enabled" +else + echo " Bitmain.MultiChain...: Disabled" +fi + +if test "x$bitmain_c5" = xyes; then + echo " Bitmain.C5...: Enabled" +else + echo " Bitmain.C5...: Disabled" +fi + +if test "x$avalon" = xyes; then + echo " Avalon.ASICs.........: Enabled" +else + echo " Avalon.ASICs.........: Disabled" +fi + +if test "x$avalon2" = xyes; then + echo " Avalon2.ASICs........: Enabled" +else + echo " Avalon2.ASICs........: Disabled" +fi + +if test "x$avalon4" = xyes; then + echo " Avalon4.ASICs........: Enabled" +else + echo " Avalon4.ASICs........: Disabled" +fi + +if test "x$minion" = xyes; then + echo " BlackArrowMinion.ASIC: Enabled" +else + echo " BlackArrowMinion.ASIC: Disabled" +fi + +if test "x$bab" = xyes; then + echo " BlackArrow.ASICs.....: Enabled" +else + echo " BlackArrow.ASICs.....: Disabled" +fi + +if test "x$bflsc" = xyes; then + echo " BFL.ASICs............: Enabled" +else + echo " BFL.ASICs............: Disabled" +fi + +if test "x$bitforce" = xyes; then + echo " BitForce.FPGAs.......: Enabled" +else + echo " BitForce.FPGAs.......: Disabled" +fi + +if test "x$bitfury" = xyes; then + echo " BitFury.ASICs........: Enabled" +else + echo " BitFury.ASICs........: Disabled" +fi + +if test "x$blockerupter" = xyes; then + echo " BlockErupter.ASICs...: Enabled" +else + echo " BlockErupter.ASICs...: Disabled" +fi + +if test "x$cointerra" = xyes; then + echo " Cointerra.ASICs......: Enabled" +else + echo " Cointerra.ASICs......: Disabled" +fi + +if test "x$sp10" = xyes; then + echo " Spond-sp10.ASICs.....: Enabled" +else + echo " Spond-sp10.ASICs.....: Disabled" +fi + + +if test "x$sp30" = xyes; then + echo " Spond-sp30.ASICs.....: Enabled" +else + echo " Spond-sp30.ASICs.....: Disabled" +fi + + +if test "x$bitmine_A1" = xyes; then + echo " Bitmine-A1.ASICs.....: Enabled" +else + echo " Bitmine-A1.ASICs.....: Disabled" +fi + +if test "x$drillbit" = xyes; then + echo " Drillbit.BitFury.....: Enabled" +else + echo " Drillbit.BitFury.....: Disabled" +fi + +if test "x$hashfast" = xyes; then + echo " Hashfast.ASICs.......: Enabled" +else + echo " Hashfast.ASICs.......: Disabled" +fi + +if test "x$hashratio" = xyes; then + echo " Hashratio.ASICs......: Enabled" +else + echo " Hashratio.ASICs......: Disabled" +fi + +if test "x$icarus" = xyes; then + echo " Icarus.ASICs/FPGAs...: Enabled" +else + echo " Icarus.ASICs/FPGAs...: Disabled" +fi + +if test "x$klondike" = xyes; then + echo " Klondike.ASICs.......: Enabled" +else + echo " Klondike.ASICs.......: Disabled" +fi + +if test "x$knc" = xyes; then + echo " KnC.ASICs............: Enabled" +else + echo " KnC.ASICs............: Disabled" +fi + +if test "x$modminer" = xyes; then + echo " ModMiner.FPGAs.......: Enabled" +else + echo " ModMiner.FPGAs.......: Disabled" +fi + +#Add any new device to this, along with a no on the end of the test +if test "x$avalon$avalon2$bab$bflsc$bitforce$bitfury$blockerupter$hashfast$hashratio$icarus$klondike$knc$modminer$drillbit$minion$cointerra$bitmine_A1$bmsc$bitmain$sp10$sp30$bitmain_c5" = xnonononononononononononononononononononono; then + echo + AC_MSG_ERROR([No mining devices configured in]) + echo +fi + +if test "x$standalone" = xyes; then + if test $drivercount != x; then + echo + AC_MSG_ERROR([You have configured more than one driver in with a driver that is designed to be standalone only (see ./configure --help)]) + echo + fi +fi + +echo +echo "Compilation............: make (or gmake)" +echo " CPPFLAGS.............: $CPPFLAGS" +echo " CFLAGS...............: $CFLAGS" +echo " LDFLAGS..............: $LDFLAGS $PTHREAD_FLAGS" +echo " LDADD................: $DLOPEN_FLAGS $LIBCURL_LIBS $JANSSON_LIBS $PTHREAD_LIBS $NCURSES_LIBS $PDCURSES_LIBS $WS2_LIBS $MATH_LIBS $LIBUSB_LIBS $RT_LIBS" +echo +echo "Installation...........: make install (as root if needed, with 'su' or 'sudo')" +echo " prefix...............: $prefix" +echo +if test "x$want_usbutils$dlibusb" = xyesyes; then +echo "*** SYSTEM LIBUSB BEING ADDED - NOT RECOMMENDED UNLESS YOU ARE UNABLE TO COMPILE THE INCLUDED LIBUSB ***" +echo +fi diff --git a/crc.h b/crc.h new file mode 100644 index 0000000..820fbe7 --- /dev/null +++ b/crc.h @@ -0,0 +1,23 @@ +/* + * Milkymist SoC (Software) + * Copyright (C) 2007, 2008, 2009 Sebastien Bourdeauducq + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef _CRC_H_ +#define _CRC_H_ + +unsigned short crc16(const unsigned char *buffer, int len); + +#endif /* _CRC_H_ */ diff --git a/crc16.c b/crc16.c new file mode 100644 index 0000000..7d8374e --- /dev/null +++ b/crc16.c @@ -0,0 +1,45 @@ +unsigned int crc16_table[256] = { + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, + 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, + 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, + 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, + 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, + 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, + 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, + 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, + 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, + 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, + 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, + 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, + 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, + 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, + 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, + 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, + 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, + 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, + 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, + 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, + 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, + 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, + 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0 +}; + +unsigned short crc16(const unsigned char *buffer, int len) +{ + unsigned short crc; + + crc = 0; + while(len-- > 0) + crc = crc16_table[((crc >> 8) ^ (*buffer++)) & 0xFF] ^ (crc << 8); + + return crc; +} diff --git a/driver-SPI-bitmine-A1.c b/driver-SPI-bitmine-A1.c new file mode 100644 index 0000000..21d2545 --- /dev/null +++ b/driver-SPI-bitmine-A1.c @@ -0,0 +1,1123 @@ +/* + * cgminer SPI driver for Bitmine.ch A1 devices + * + * Copyright 2013, 2014 Zefir Kurtisi + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#include +#include +#include +#include +#include +#include + +#include "spi-context.h" +#include "logging.h" +#include "miner.h" +#include "util.h" + +#include "A1-common.h" +#include "A1-board-selector.h" +#include "A1-trimpot-mcp4x.h" + +/* one global board_selector and spi context is enough */ +static struct board_selector *board_selector; +static struct spi_ctx *spi; + +/********** work queue */ +static bool wq_enqueue(struct work_queue *wq, struct work *work) +{ + if (work == NULL) + return false; + struct work_ent *we = malloc(sizeof(*we)); + assert(we != NULL); + + we->work = work; + INIT_LIST_HEAD(&we->head); + list_add_tail(&we->head, &wq->head); + wq->num_elems++; + return true; +} + +static struct work *wq_dequeue(struct work_queue *wq) +{ + if (wq == NULL) + return NULL; + if (wq->num_elems == 0) + return NULL; + struct work_ent *we; + we = list_entry(wq->head.next, struct work_ent, head); + struct work *work = we->work; + + list_del(&we->head); + free(we); + wq->num_elems--; + return work; +} + +/* + * if not cooled sufficiently, communication fails and chip is temporary + * disabled. we let it inactive for 30 seconds to cool down + * + * TODO: to be removed after bring up / test phase + */ +#define COOLDOWN_MS (30 * 1000) +/* if after this number of retries a chip is still inaccessible, disable it */ +#define DISABLE_CHIP_FAIL_THRESHOLD 3 + + +enum A1_command { + A1_BIST_START = 0x01, + A1_BIST_FIX = 0x03, + A1_RESET = 0x04, + A1_WRITE_JOB = 0x07, + A1_READ_RESULT = 0x08, + A1_WRITE_REG = 0x09, + A1_READ_REG = 0x0a, + A1_READ_REG_RESP = 0x1a, +}; + +/* + * for now, we have one global config, defaulting values: + * - ref_clk 16MHz / sys_clk 800MHz + * - 2000 kHz SPI clock + */ +struct A1_config_options A1_config_options = { + .ref_clk_khz = 16000, .sys_clk_khz = 800000, .spi_clk_khz = 2000, +}; + +/* override values with --bitmine-a1-options ref:sys:spi: - use 0 for default */ +static struct A1_config_options *parsed_config_options; + +/********** temporary helper for hexdumping SPI traffic */ +static void applog_hexdump(char *prefix, uint8_t *buff, int len, int level) +{ + static char line[256]; + char *pos = line; + int i; + if (len < 1) + return; + + pos += sprintf(pos, "%s: %d bytes:", prefix, len); + for (i = 0; i < len; i++) { + if (i > 0 && (i % 32) == 0) { + applog(LOG_DEBUG, "%s", line); + pos = line; + pos += sprintf(pos, "\t"); + } + pos += sprintf(pos, "%.2X ", buff[i]); + } + applog(level, "%s", line); +} + +static void hexdump(char *prefix, uint8_t *buff, int len) +{ + applog_hexdump(prefix, buff, len, LOG_DEBUG); +} + +static void hexdump_error(char *prefix, uint8_t *buff, int len) +{ + applog_hexdump(prefix, buff, len, LOG_ERR); +} + +static void flush_spi(struct A1_chain *a1) +{ + memset(a1->spi_tx, 0, 64); + spi_transfer(a1->spi_ctx, a1->spi_tx, a1->spi_rx, 64); +} + + +/********** upper layer SPI functions */ +static uint8_t *exec_cmd(struct A1_chain *a1, + uint8_t cmd, uint8_t chip_id, + uint8_t *data, uint8_t len, + uint8_t resp_len) +{ + int tx_len = 4 + len; + memset(a1->spi_tx, 0, tx_len); + a1->spi_tx[0] = cmd; + a1->spi_tx[1] = chip_id; + + if (data != NULL) + memcpy(a1->spi_tx + 2, data, len); + + assert(spi_transfer(a1->spi_ctx, a1->spi_tx, a1->spi_rx, tx_len)); + hexdump("send: TX", a1->spi_tx, tx_len); + hexdump("send: RX", a1->spi_rx, tx_len); + + int poll_len = resp_len; + if (chip_id == 0) { + if (a1->num_chips == 0) { + applog(LOG_INFO, "%d: unknown chips in chain, " + "assuming 8", a1->chain_id); + poll_len += 32; + } + poll_len += 4 * a1->num_chips; + } + else { + poll_len += 4 * chip_id - 2; + } + + assert(spi_transfer(a1->spi_ctx, NULL, a1->spi_rx + tx_len, poll_len)); + hexdump("poll: RX", a1->spi_rx + tx_len, poll_len); + int ack_len = tx_len + resp_len; + int ack_pos = tx_len + poll_len - ack_len; + hexdump("poll: ACK", a1->spi_rx + ack_pos, ack_len - 2); + + return (a1->spi_rx + ack_pos); +} + + +/********** A1 SPI commands */ +static uint8_t *cmd_BIST_FIX_BCAST(struct A1_chain *a1) +{ + uint8_t *ret = exec_cmd(a1, A1_BIST_FIX, 0x00, NULL, 0, 0); + if (ret == NULL || ret[0] != A1_BIST_FIX) { + applog(LOG_ERR, "%d: cmd_BIST_FIX_BCAST failed", a1->chain_id); + return NULL; + } + return ret; +} + +static uint8_t *cmd_RESET_BCAST(struct A1_chain *a1, uint8_t strategy) +{ + static uint8_t s[2]; + s[0] = strategy; + s[1] = strategy; + uint8_t *ret = exec_cmd(a1, A1_RESET, 0x00, s, 2, 0); + if (ret == NULL || (ret[0] != A1_RESET && a1->num_chips != 0)) { + applog(LOG_ERR, "%d: cmd_RESET_BCAST failed", a1->chain_id); + return NULL; + } + return ret; +} + +static uint8_t *cmd_READ_RESULT_BCAST(struct A1_chain *a1) +{ + int tx_len = 8; + memset(a1->spi_tx, 0, tx_len); + a1->spi_tx[0] = A1_READ_RESULT; + + assert(spi_transfer(a1->spi_ctx, a1->spi_tx, a1->spi_rx, tx_len)); + hexdump("send: TX", a1->spi_tx, tx_len); + hexdump("send: RX", a1->spi_rx, tx_len); + + int poll_len = tx_len + 4 * a1->num_chips; + assert(spi_transfer(a1->spi_ctx, NULL, a1->spi_rx + tx_len, poll_len)); + hexdump("poll: RX", a1->spi_rx + tx_len, poll_len); + + uint8_t *scan = a1->spi_rx; + int i; + for (i = 0; i < poll_len; i += 2) { + if ((scan[i] & 0x0f) == A1_READ_RESULT) + return scan + i; + } + applog(LOG_ERR, "%d: cmd_READ_RESULT_BCAST failed", a1->chain_id); + return NULL; +} + +static uint8_t *cmd_WRITE_REG(struct A1_chain *a1, uint8_t chip, uint8_t *reg) +{ + uint8_t *ret = exec_cmd(a1, A1_WRITE_REG, chip, reg, 6, 0); + if (ret == NULL || ret[0] != A1_WRITE_REG) { + applog(LOG_ERR, "%d: cmd_WRITE_REG failed", a1->chain_id); + return NULL; + } + return ret; +} + +static uint8_t *cmd_READ_REG(struct A1_chain *a1, uint8_t chip) +{ + uint8_t *ret = exec_cmd(a1, A1_READ_REG, chip, NULL, 0, 6); + if (ret == NULL || ret[0] != A1_READ_REG_RESP || ret[1] != chip) { + applog(LOG_ERR, "%d: cmd_READ_REG chip %d failed", + a1->chain_id, chip); + return NULL; + } + memcpy(a1->spi_rx, ret, 8); + return ret; +} + +static uint8_t *cmd_WRITE_JOB(struct A1_chain *a1, uint8_t chip_id, + uint8_t *job) +{ + /* ensure we push the SPI command to the last chip in chain */ + int tx_len = WRITE_JOB_LENGTH + 2; + memcpy(a1->spi_tx, job, WRITE_JOB_LENGTH); + memset(a1->spi_tx + WRITE_JOB_LENGTH, 0, tx_len - WRITE_JOB_LENGTH); + + assert(spi_transfer(a1->spi_ctx, a1->spi_tx, a1->spi_rx, tx_len)); + hexdump("send: TX", a1->spi_tx, tx_len); + hexdump("send: RX", a1->spi_rx, tx_len); + + int poll_len = 4 * chip_id - 2; + + assert(spi_transfer(a1->spi_ctx, NULL, a1->spi_rx + tx_len, poll_len)); + hexdump("poll: RX", a1->spi_rx + tx_len, poll_len); + + int ack_len = tx_len; + int ack_pos = tx_len + poll_len - ack_len; + hexdump("poll: ACK", a1->spi_rx + ack_pos, tx_len); + + uint8_t *ret = a1->spi_rx + ack_pos; + if (ret[0] != a1->spi_tx[0] || ret[1] != a1->spi_tx[1]){ + applog(LOG_ERR, "%d: cmd_WRITE_JOB failed: " + "0x%02x%02x/0x%02x%02x", a1->chain_id, + ret[0], ret[1], a1->spi_tx[0], a1->spi_tx[1]); + return NULL; + } + return ret; +} + +/********** A1 low level functions */ +#define MAX_PLL_WAIT_CYCLES 25 +#define PLL_CYCLE_WAIT_TIME 40 +static bool check_chip_pll_lock(struct A1_chain *a1, int chip_id, uint8_t *wr) +{ + int n; + for (n = 0; n < MAX_PLL_WAIT_CYCLES; n++) { + /* check for PLL lock status */ + if (cmd_READ_REG(a1, chip_id) && (a1->spi_rx[4] & 1) == 1) + /* double check that we read back what we set before */ + return wr[0] == a1->spi_rx[2] && wr[1] == a1->spi_rx[3]; + + cgsleep_ms(PLL_CYCLE_WAIT_TIME); + } + applog(LOG_ERR, "%d: chip %d failed PLL lock", a1->chain_id, chip_id); + return false; +} + +static uint8_t *get_pll_reg(struct A1_chain *a1, int ref_clock_khz, + int sys_clock_khz) +{ + /* + * PLL parameters after: + * sys_clk = (ref_clk * pll_fbdiv) / (pll_prediv * 2^(pll_postdiv - 1)) + * + * with a higher pll_postdiv being desired over a higher pll_prediv + */ + + static uint8_t writereg[6] = { 0x00, 0x00, 0x21, 0x84, }; + uint8_t pre_div = 1; + uint8_t post_div = 1; + uint32_t fb_div; + + int cid = a1->chain_id; + + applog(LOG_WARNING, "%d: Setting PLL: CLK_REF=%dMHz, SYS_CLK=%dMHz", + cid, ref_clock_khz / 1000, sys_clock_khz / 1000); + + /* Euclidean search for GCD */ + int a = ref_clock_khz; + int b = sys_clock_khz; + while (b != 0) { + int h = a % b; + a = b; + b = h; + } + fb_div = sys_clock_khz / a; + int n = ref_clock_khz / a; + /* approximate multiplier if not exactly matchable */ + if (fb_div > 511) { + int f = fb_div / n; + int m = (f < 32) ? 16 : (f < 64) ? 8 : + (f < 128) ? 4 : (256 < 2) ? 2 : 1; + fb_div = m * fb_div / n; + n = m; + } + /* try to maximize post divider */ + if ((n & 3) == 0) + post_div = 3; + else if ((n & 1) == 0) + post_div = 2; + else + post_div = 1; + /* remainder goes to pre_div */ + pre_div = n / (1 << (post_div - 1)); + /* correct pre_div overflow */ + if (pre_div > 31) { + fb_div = 31 * fb_div / pre_div; + pre_div = 31; + } + writereg[0] = (post_div << 6) | (pre_div << 1) | (fb_div >> 8); + writereg[1] = fb_div & 0xff; + applog(LOG_WARNING, "%d: setting PLL: pre_div=%d, post_div=%d, " + "fb_div=%d: 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x", cid, + pre_div, post_div, fb_div, + writereg[0], writereg[1], writereg[2], + writereg[3], writereg[4], writereg[5]); + return writereg; +} + +static bool set_pll_config(struct A1_chain *a1, int chip_id, + int ref_clock_khz, int sys_clock_khz) +{ + uint8_t *writereg = get_pll_reg(a1, ref_clock_khz, sys_clock_khz); + if (writereg == NULL) + return false; + if (!cmd_WRITE_REG(a1, chip_id, writereg)) + return false; + + int from = (chip_id == 0) ? 0 : chip_id - 1; + int to = (chip_id == 0) ? a1->num_active_chips : chip_id - 1; + + int i; + for (i = from; i < to; i++) { + int cid = i + 1; + if (!check_chip_pll_lock(a1, chip_id, writereg)) { + applog(LOG_ERR, "%d: chip %d failed PLL lock", + a1->chain_id, cid); + return false; + } + } + return true; +} + +#define WEAK_CHIP_THRESHOLD 30 +#define BROKEN_CHIP_THRESHOLD 26 +#define WEAK_CHIP_SYS_CLK (600 * 1000) +#define BROKEN_CHIP_SYS_CLK (400 * 1000) +static bool check_chip(struct A1_chain *a1, int i) +{ + int chip_id = i + 1; + int cid = a1->chain_id; + if (!cmd_READ_REG(a1, chip_id)) { + applog(LOG_WARNING, "%d: Failed to read register for " + "chip %d -> disabling", cid, chip_id); + a1->chips[i].num_cores = 0; + a1->chips[i].disabled = 1; + return false;; + } + a1->chips[i].num_cores = a1->spi_rx[7]; + a1->num_cores += a1->chips[i].num_cores; + applog(LOG_WARNING, "%d: Found chip %d with %d active cores", + cid, chip_id, a1->chips[i].num_cores); + if (a1->chips[i].num_cores < BROKEN_CHIP_THRESHOLD) { + applog(LOG_WARNING, "%d: broken chip %d with %d active " + "cores (threshold = %d)", cid, chip_id, + a1->chips[i].num_cores, BROKEN_CHIP_THRESHOLD); + set_pll_config(a1, chip_id, A1_config_options.ref_clk_khz, + BROKEN_CHIP_SYS_CLK); + cmd_READ_REG(a1, chip_id); + hexdump_error("new.PLL", a1->spi_rx, 8); + a1->chips[i].disabled = true; + a1->num_cores -= a1->chips[i].num_cores; + return false; + } + + if (a1->chips[i].num_cores < WEAK_CHIP_THRESHOLD) { + applog(LOG_WARNING, "%d: weak chip %d with %d active " + "cores (threshold = %d)", cid, + chip_id, a1->chips[i].num_cores, WEAK_CHIP_THRESHOLD); + set_pll_config(a1, chip_id, A1_config_options.ref_clk_khz, + WEAK_CHIP_SYS_CLK); + cmd_READ_REG(a1, chip_id); + hexdump_error("new.PLL", a1->spi_rx, 8); + return false; + } + return true; +} + +/* + * BIST_START works only once after HW reset, on subsequent calls it + * returns 0 as number of chips. + */ +static int chain_detect(struct A1_chain *a1) +{ + int tx_len = 6; + + memset(a1->spi_tx, 0, tx_len); + a1->spi_tx[0] = A1_BIST_START; + a1->spi_tx[1] = 0; + + if (!spi_transfer(a1->spi_ctx, a1->spi_tx, a1->spi_rx, tx_len)) + return 0; + hexdump("TX", a1->spi_tx, 6); + hexdump("RX", a1->spi_rx, 6); + + int i; + int cid = a1->chain_id; + int max_poll_words = MAX_CHAIN_LENGTH * 2; + for(i = 1; i < max_poll_words; i++) { + if (a1->spi_rx[0] == A1_BIST_START && a1->spi_rx[1] == 0) { + spi_transfer(a1->spi_ctx, NULL, a1->spi_rx, 2); + hexdump("RX", a1->spi_rx, 2); + uint8_t n = a1->spi_rx[1]; + a1->num_chips = (i / 2) + 1; + if (a1->num_chips != n) { + applog(LOG_ERR, "%d: enumeration: %d <-> %d", + cid, a1->num_chips, n); + if (n != 0) + a1->num_chips = n; + } + applog(LOG_WARNING, "%d: detected %d chips", + cid, a1->num_chips); + return a1->num_chips; + } + bool s = spi_transfer(a1->spi_ctx, NULL, a1->spi_rx, 2); + hexdump("RX", a1->spi_rx, 2); + if (!s) + return 0; + } + applog(LOG_WARNING, "%d: no A1 chip-chain detected", cid); + return 0; +} + +/********** disable / re-enable related section (temporary for testing) */ +static int get_current_ms(void) +{ + cgtimer_t ct; + cgtimer_time(&ct); + return cgtimer_to_ms(&ct); +} + +static bool is_chip_disabled(struct A1_chain *a1, uint8_t chip_id) +{ + struct A1_chip *chip = &a1->chips[chip_id - 1]; + return chip->disabled || chip->cooldown_begin != 0; +} + +/* check and disable chip, remember time */ +static void disable_chip(struct A1_chain *a1, uint8_t chip_id) +{ + flush_spi(a1); + struct A1_chip *chip = &a1->chips[chip_id - 1]; + int cid = a1->chain_id; + if (is_chip_disabled(a1, chip_id)) { + applog(LOG_WARNING, "%d: chip %d already disabled", + cid, chip_id); + return; + } + applog(LOG_WARNING, "%d: temporary disabling chip %d", cid, chip_id); + chip->cooldown_begin = get_current_ms(); +} + +/* check if disabled chips can be re-enabled */ +void check_disabled_chips(struct A1_chain *a1) +{ + int i; + int cid = a1->chain_id; + for (i = 0; i < a1->num_active_chips; i++) { + int chip_id = i + 1; + struct A1_chip *chip = &a1->chips[i]; + if (!is_chip_disabled(a1, chip_id)) + continue; + /* do not re-enable fully disabled chips */ + if (chip->disabled) + continue; + if (chip->cooldown_begin + COOLDOWN_MS > get_current_ms()) + continue; + if (!cmd_READ_REG(a1, chip_id)) { + chip->fail_count++; + applog(LOG_WARNING, "%d: chip %d not yet working - %d", + cid, chip_id, chip->fail_count); + if (chip->fail_count > DISABLE_CHIP_FAIL_THRESHOLD) { + applog(LOG_WARNING, + "%d: completely disabling chip %d at %d", + cid, chip_id, chip->fail_count); + chip->disabled = true; + a1->num_cores -= chip->num_cores; + continue; + } + /* restart cooldown period */ + chip->cooldown_begin = get_current_ms(); + continue; + } + applog(LOG_WARNING, "%d: chip %d is working again", + cid, chip_id); + chip->cooldown_begin = 0; + chip->fail_count = 0; + } +} + +/********** job creation and result evaluation */ +uint32_t get_diff(double diff) +{ + uint32_t n_bits; + int shift = 29; + double f = (double) 0x0000ffff / diff; + while (f < (double) 0x00008000) { + shift--; + f *= 256.0; + } + while (f >= (double) 0x00800000) { + shift++; + f /= 256.0; + } + n_bits = (int) f + (shift << 24); + return n_bits; +} + +static uint8_t *create_job(uint8_t chip_id, uint8_t job_id, struct work *work) +{ + static uint8_t job[WRITE_JOB_LENGTH] = { + /* command */ + 0x00, 0x00, + /* midstate */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* wdata */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + /* start nonce */ + 0x00, 0x00, 0x00, 0x00, + /* difficulty 1 */ + 0xff, 0xff, 0x00, 0x1d, + /* end nonce */ + 0xff, 0xff, 0xff, 0xff, + }; + uint8_t *midstate = work->midstate; + uint8_t *wdata = work->data + 64; + + uint32_t *p1 = (uint32_t *) &job[34]; + uint32_t *p2 = (uint32_t *) wdata; + + job[0] = (job_id << 4) | A1_WRITE_JOB; + job[1] = chip_id; + + swab256(job + 2, midstate); + p1[0] = bswap_32(p2[0]); + p1[1] = bswap_32(p2[1]); + p1[2] = bswap_32(p2[2]); +#ifdef USE_REAL_DIFF + p1[4] = get_diff(work->sdiff); +#endif + return job; +} + +/* set work for given chip, returns true if a nonce range was finished */ +static bool set_work(struct A1_chain *a1, uint8_t chip_id, struct work *work, + uint8_t queue_states) +{ + int cid = a1->chain_id; + struct A1_chip *chip = &a1->chips[chip_id - 1]; + bool retval = false; + + int job_id = chip->last_queued_id + 1; + + applog(LOG_INFO, "%d: queuing chip %d with job_id %d, state=0x%02x", + cid, chip_id, job_id, queue_states); + if (job_id == (queue_states & 0x0f) || job_id == (queue_states >> 4)) + applog(LOG_WARNING, "%d: job overlap: %d, 0x%02x", + cid, job_id, queue_states); + + if (chip->work[chip->last_queued_id] != NULL) { + work_completed(a1->cgpu, chip->work[chip->last_queued_id]); + chip->work[chip->last_queued_id] = NULL; + retval = true; + } + uint8_t *jobdata = create_job(chip_id, job_id, work); + if (!cmd_WRITE_JOB(a1, chip_id, jobdata)) { + /* give back work */ + work_completed(a1->cgpu, work); + + applog(LOG_ERR, "%d: failed to set work for chip %d.%d", + cid, chip_id, job_id); + disable_chip(a1, chip_id); + } else { + chip->work[chip->last_queued_id] = work; + chip->last_queued_id++; + chip->last_queued_id &= 3; + } + return retval; +} + +static bool get_nonce(struct A1_chain *a1, uint8_t *nonce, + uint8_t *chip, uint8_t *job_id) +{ + uint8_t *ret = cmd_READ_RESULT_BCAST(a1); + if (ret == NULL) + return false; + if (ret[1] == 0) { + applog(LOG_DEBUG, "%d: output queue empty", a1->chain_id); + return false; + } + *job_id = ret[0] >> 4; + *chip = ret[1]; + memcpy(nonce, ret + 2, 4); + return true; +} + +/* reset input work queues in chip chain */ +static bool abort_work(struct A1_chain *a1) +{ + /* drop jobs already queued: reset strategy 0xed */ + return cmd_RESET_BCAST(a1, 0xed); +} + +/********** driver interface */ +void exit_A1_chain(struct A1_chain *a1) +{ + if (a1 == NULL) + return; + free(a1->chips); + a1->chips = NULL; + a1->spi_ctx = NULL; + free(a1); +} + +struct A1_chain *init_A1_chain(struct spi_ctx *ctx, int chain_id) +{ + int i; + struct A1_chain *a1 = malloc(sizeof(*a1)); + assert(a1 != NULL); + + applog(LOG_DEBUG, "%d: A1 init chain", chain_id); + memset(a1, 0, sizeof(*a1)); + a1->spi_ctx = ctx; + a1->chain_id = chain_id; + + a1->num_chips = chain_detect(a1); + if (a1->num_chips == 0) + goto failure; + + applog(LOG_WARNING, "spidev%d.%d: %d: Found %d A1 chips", + a1->spi_ctx->config.bus, a1->spi_ctx->config.cs_line, + a1->chain_id, a1->num_chips); + + if (!set_pll_config(a1, 0, A1_config_options.ref_clk_khz, + A1_config_options.sys_clk_khz)) + goto failure; + + /* override max number of active chips if requested */ + a1->num_active_chips = a1->num_chips; + if (A1_config_options.override_chip_num > 0 && + a1->num_chips > A1_config_options.override_chip_num) { + a1->num_active_chips = A1_config_options.override_chip_num; + applog(LOG_WARNING, "%d: limiting chain to %d chips", + a1->chain_id, a1->num_active_chips); + } + + a1->chips = calloc(a1->num_active_chips, sizeof(struct A1_chip)); + assert (a1->chips != NULL); + + if (!cmd_BIST_FIX_BCAST(a1)) + goto failure; + + for (i = 0; i < a1->num_active_chips; i++) + check_chip(a1, i); + + applog(LOG_WARNING, "%d: found %d chips with total %d active cores", + a1->chain_id, a1->num_active_chips, a1->num_cores); + + mutex_init(&a1->lock); + INIT_LIST_HEAD(&a1->active_wq.head); + + return a1; + +failure: + exit_A1_chain(a1); + return NULL; +} + +static bool detect_single_chain(void) +{ + board_selector = (struct board_selector*)&dummy_board_selector; + applog(LOG_WARNING, "A1: checking single chain"); + struct A1_chain *a1 = init_A1_chain(spi, 0); + if (a1 == NULL) + return false; + + struct cgpu_info *cgpu = malloc(sizeof(*cgpu)); + assert(cgpu != NULL); + + memset(cgpu, 0, sizeof(*cgpu)); + cgpu->drv = &bitmineA1_drv; + cgpu->name = "BitmineA1.SingleChain"; + cgpu->threads = 1; + + cgpu->device_data = a1; + + a1->cgpu = cgpu; + add_cgpu(cgpu); + applog(LOG_WARNING, "Detected single A1 chain with %d chips / %d cores", + a1->num_active_chips, a1->num_cores); + return true; +} + +bool detect_coincraft_desk(void) +{ + static const uint8_t mcp4x_mapping[] = { 0x2c, 0x2b, 0x2a, 0x29, 0x28 }; + board_selector = ccd_board_selector_init(); + if (board_selector == NULL) { + applog(LOG_INFO, "No CoinCrafd Desk backplane detected."); + return false; + } + board_selector->reset_all(); + + int boards_detected = 0; + int board_id; + for (board_id = 0; board_id < CCD_MAX_CHAINS; board_id++) { + uint8_t mcp_slave = mcp4x_mapping[board_id]; + struct mcp4x *mcp = mcp4x_init(mcp_slave); + if (mcp == NULL) + continue; + + if (A1_config_options.wiper != 0) + mcp->set_wiper(mcp, 0, A1_config_options.wiper); + + applog(LOG_WARNING, "checking board %d...", board_id); + board_selector->select(board_id); + + struct A1_chain *a1 = init_A1_chain(spi, board_id); + board_selector->release(); + if (a1 == NULL) + continue; + + struct cgpu_info *cgpu = malloc(sizeof(*cgpu)); + assert(cgpu != NULL); + + memset(cgpu, 0, sizeof(*cgpu)); + cgpu->drv = &bitmineA1_drv; + cgpu->name = "BitmineA1.CCD"; + cgpu->threads = 1; + + cgpu->device_data = a1; + + a1->cgpu = cgpu; + add_cgpu(cgpu); + boards_detected++; + } + if (boards_detected == 0) + return false; + + applog(LOG_WARNING, "Detected CoinCraft Desk with %d boards", + boards_detected); + return true; +} + +bool detect_coincraft_rig_v3(void) +{ + board_selector = ccr_board_selector_init(); + if (board_selector == NULL) + return false; + + board_selector->reset_all(); + int chains_detected = 0; + int c; + for (c = 0; c < CCR_MAX_CHAINS; c++) { + applog(LOG_WARNING, "checking RIG chain %d...", c); + + if (!board_selector->select(c)) + continue; + + struct A1_chain *a1 = init_A1_chain(spi, c); + board_selector->release(); + + if (a1 == NULL) + continue; + + if (A1_config_options.wiper != 0 && (c & 1) == 0) { + struct mcp4x *mcp = mcp4x_init(0x28); + if (mcp == NULL) { + applog(LOG_ERR, "%d: Cant access poti", c); + } else { + mcp->set_wiper(mcp, 0, A1_config_options.wiper); + mcp->set_wiper(mcp, 1, A1_config_options.wiper); + mcp->exit(mcp); + applog(LOG_WARNING, "%d: set wiper to 0x%02x", + c, A1_config_options.wiper); + } + } + + struct cgpu_info *cgpu = malloc(sizeof(*cgpu)); + assert(cgpu != NULL); + + memset(cgpu, 0, sizeof(*cgpu)); + cgpu->drv = &bitmineA1_drv; + cgpu->name = "BitmineA1.CCR"; + cgpu->threads = 1; + + cgpu->device_data = a1; + + a1->cgpu = cgpu; + add_cgpu(cgpu); + chains_detected++; + } + if (chains_detected == 0) + return false; + + applog(LOG_WARNING, "Detected CoinCraft Rig with %d chains", + chains_detected); + return true; +} + +/* Probe SPI channel and register chip chain */ +void A1_detect(bool hotplug) +{ + /* no hotplug support for SPI */ + if (hotplug) + return; + + /* parse bimine-a1-options */ + if (opt_bitmine_a1_options != NULL && parsed_config_options == NULL) { + int ref_clk = 0; + int sys_clk = 0; + int spi_clk = 0; + int override_chip_num = 0; + int wiper = 0; + + sscanf(opt_bitmine_a1_options, "%d:%d:%d:%d:%d", + &ref_clk, &sys_clk, &spi_clk, &override_chip_num, + &wiper); + if (ref_clk != 0) + A1_config_options.ref_clk_khz = ref_clk; + if (sys_clk != 0) { + if (sys_clk < 100000) + quit(1, "system clock must be above 100MHz"); + A1_config_options.sys_clk_khz = sys_clk; + } + if (spi_clk != 0) + A1_config_options.spi_clk_khz = spi_clk; + if (override_chip_num != 0) + A1_config_options.override_chip_num = override_chip_num; + if (wiper != 0) + A1_config_options.wiper = wiper; + + /* config options are global, scan them once */ + parsed_config_options = &A1_config_options; + } + applog(LOG_DEBUG, "A1 detect"); + + /* register global SPI context */ + struct spi_config cfg = default_spi_config; + cfg.mode = SPI_MODE_1; + cfg.speed = A1_config_options.spi_clk_khz * 1000; + spi = spi_init(&cfg); + if (spi == NULL) + return; + + /* detect and register supported products */ + if (detect_coincraft_desk()) + return; + if (detect_coincraft_rig_v3()) + return; + if (detect_single_chain()) + return; + /* release SPI context if no A1 products found */ + spi_exit(spi); +} + +#define TEMP_UPDATE_INT_MS 2000 +static int64_t A1_scanwork(struct thr_info *thr) +{ + int i; + struct cgpu_info *cgpu = thr->cgpu; + struct A1_chain *a1 = cgpu->device_data; + int32_t nonce_ranges_processed = 0; + + if (a1->num_cores == 0) { + cgpu->deven = DEV_DISABLED; + return 0; + } + board_selector->select(a1->chain_id); + + applog(LOG_DEBUG, "A1 running scanwork"); + + uint32_t nonce; + uint8_t chip_id; + uint8_t job_id; + bool work_updated = false; + + mutex_lock(&a1->lock); + + if (a1->last_temp_time + TEMP_UPDATE_INT_MS < get_current_ms()) { + a1->temp = board_selector->get_temp(0); + a1->last_temp_time = get_current_ms(); + } + int cid = a1->chain_id; + /* poll queued results */ + while (true) { + if (!get_nonce(a1, (uint8_t*)&nonce, &chip_id, &job_id)) + break; + nonce = bswap_32(nonce); + work_updated = true; + if (chip_id < 1 || chip_id > a1->num_active_chips) { + applog(LOG_WARNING, "%d: wrong chip_id %d", + cid, chip_id); + continue; + } + if (job_id < 1 && job_id > 4) { + applog(LOG_WARNING, "%d: chip %d: result has wrong " + "job_id %d", cid, chip_id, job_id); + flush_spi(a1); + continue; + } + + struct A1_chip *chip = &a1->chips[chip_id - 1]; + struct work *work = chip->work[job_id - 1]; + if (work == NULL) { + /* already been flushed => stale */ + applog(LOG_WARNING, "%d: chip %d: stale nonce 0x%08x", + cid, chip_id, nonce); + chip->stales++; + continue; + } + if (!submit_nonce(thr, work, nonce)) { + applog(LOG_WARNING, "%d: chip %d: invalid nonce 0x%08x", + cid, chip_id, nonce); + chip->hw_errors++; + /* add a penalty of a full nonce range on HW errors */ + nonce_ranges_processed--; + continue; + } + applog(LOG_DEBUG, "YEAH: %d: chip %d / job_id %d: nonce 0x%08x", + cid, chip_id, job_id, nonce); + chip->nonces_found++; + } + + /* check for completed works */ + for (i = a1->num_active_chips; i > 0; i--) { + uint8_t c = i; + if (is_chip_disabled(a1, c)) + continue; + if (!cmd_READ_REG(a1, c)) { + disable_chip(a1, c); + continue; + } + uint8_t qstate = a1->spi_rx[5] & 3; + uint8_t qbuff = a1->spi_rx[6]; + struct work *work; + struct A1_chip *chip = &a1->chips[i - 1]; + switch(qstate) { + case 3: + continue; + case 2: + applog(LOG_ERR, "%d: chip %d: invalid state = 2", + cid, c); + continue; + case 1: + /* fall through */ + case 0: + work_updated = true; + + work = wq_dequeue(&a1->active_wq); + if (work == NULL) { + applog(LOG_INFO, "%d: chip %d: work underflow", + cid, c); + break; + } + if (set_work(a1, c, work, qbuff)) { + chip->nonce_ranges_done++; + nonce_ranges_processed++; + } + applog(LOG_DEBUG, "%d: chip %d: job done: %d/%d/%d/%d", + cid, c, + chip->nonce_ranges_done, chip->nonces_found, + chip->hw_errors, chip->stales); + break; + } + } + check_disabled_chips(a1); + mutex_unlock(&a1->lock); + + board_selector->release(); + + if (nonce_ranges_processed < 0) + nonce_ranges_processed = 0; + + if (nonce_ranges_processed != 0) { + applog(LOG_DEBUG, "%d, nonces processed %d", + cid, nonce_ranges_processed); + } + /* in case of no progress, prevent busy looping */ + if (!work_updated) + cgsleep_ms(40); + + return (int64_t)nonce_ranges_processed << 32; +} + + +/* queue two work items per chip in chain */ +static bool A1_queue_full(struct cgpu_info *cgpu) +{ + struct A1_chain *a1 = cgpu->device_data; + int queue_full = false; + + mutex_lock(&a1->lock); + applog(LOG_DEBUG, "%d, A1 running queue_full: %d/%d", + a1->chain_id, a1->active_wq.num_elems, a1->num_active_chips); + + if (a1->active_wq.num_elems >= a1->num_active_chips * 2) + queue_full = true; + else + wq_enqueue(&a1->active_wq, get_queued(cgpu)); + + mutex_unlock(&a1->lock); + + return queue_full; +} + +static void A1_flush_work(struct cgpu_info *cgpu) +{ + struct A1_chain *a1 = cgpu->device_data; + int cid = a1->chain_id; + board_selector->select(cid); + + applog(LOG_DEBUG, "%d: A1 running flushwork", cid); + + int i; + + mutex_lock(&a1->lock); + /* stop chips hashing current work */ + if (!abort_work(a1)) { + applog(LOG_ERR, "%d: failed to abort work in chip chain!", cid); + } + /* flush the work chips were currently hashing */ + for (i = 0; i < a1->num_active_chips; i++) { + int j; + struct A1_chip *chip = &a1->chips[i]; + for (j = 0; j < 4; j++) { + struct work *work = chip->work[j]; + if (work == NULL) + continue; + applog(LOG_DEBUG, "%d: flushing chip %d, work %d: 0x%p", + cid, i, j + 1, work); + work_completed(cgpu, work); + chip->work[j] = NULL; + } + chip->last_queued_id = 0; + } + /* flush queued work */ + applog(LOG_DEBUG, "%d: flushing queued work...", cid); + while (a1->active_wq.num_elems > 0) { + struct work *work = wq_dequeue(&a1->active_wq); + assert(work != NULL); + work_completed(cgpu, work); + } + mutex_unlock(&a1->lock); + + board_selector->release(); +} + +static void A1_get_statline_before(char *buf, size_t len, + struct cgpu_info *cgpu) +{ + struct A1_chain *a1 = cgpu->device_data; + char temp[10]; + if (a1->temp != 0) + snprintf(temp, 9, "%2dC", a1->temp); + tailsprintf(buf, len, " %2d:%2d/%3d %s", + a1->chain_id, a1->num_active_chips, a1->num_cores, + a1->temp == 0 ? " " : temp); +} + +struct device_drv bitmineA1_drv = { + .drv_id = DRIVER_bitmineA1, + .dname = "BitmineA1", + .name = "BA1", + .drv_detect = A1_detect, + + .hash_work = hash_queued_work, + .scanwork = A1_scanwork, + .queue_full = A1_queue_full, + .flush_work = A1_flush_work, + .get_statline_before = A1_get_statline_before, +}; diff --git a/driver-avalon.c b/driver-avalon.c new file mode 100644 index 0000000..1d00a5f --- /dev/null +++ b/driver-avalon.c @@ -0,0 +1,1716 @@ +/* + * Copyright 2013-2014 Con Kolivas + * Copyright 2012-2013 Xiangfu + * Copyright 2012 Luke Dashjr + * Copyright 2012 Andrew Smith + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef WIN32 + #include + #include + #include + #include + #ifndef O_CLOEXEC + #define O_CLOEXEC 0 + #endif +#else + #include "compat.h" + #include + #include +#endif + +#include "elist.h" +#include "miner.h" +#include "usbutils.h" +#include "driver-avalon.h" +#include "hexdump.c" +#include "util.h" + +int opt_avalon_temp = AVALON_TEMP_TARGET; +int opt_avalon_overheat = AVALON_TEMP_OVERHEAT; +int opt_avalon_fan_min = AVALON_DEFAULT_FAN_MIN_PWM; +int opt_avalon_fan_max = AVALON_DEFAULT_FAN_MAX_PWM; +int opt_avalon_freq_min = AVALON_MIN_FREQUENCY; +int opt_avalon_freq_max = AVALON_MAX_FREQUENCY; +int opt_bitburner_core_voltage = BITBURNER_DEFAULT_CORE_VOLTAGE; +int opt_bitburner_fury_core_voltage = BITBURNER_FURY_DEFAULT_CORE_VOLTAGE; +bool opt_avalon_auto; + +static int option_offset = -1; +static int bbf_option_offset = -1; + +static int avalon_init_task(struct avalon_task *at, + uint8_t reset, uint8_t ff, uint8_t fan, + uint8_t timeout, uint8_t asic_num, + uint8_t miner_num, uint8_t nonce_elf, + uint8_t gate_miner, int frequency, int asic) +{ + uint16_t *lefreq16; + uint8_t *buf; + static bool first = true; + + if (unlikely(!at)) + return -1; + + if (unlikely(timeout <= 0 || asic_num <= 0 || miner_num <= 0)) + return -1; + + memset(at, 0, sizeof(struct avalon_task)); + + if (unlikely(reset)) { + at->reset = 1; + at->fan_eft = 1; + at->timer_eft = 1; + first = true; + } + + at->flush_fifo = (ff ? 1 : 0); + at->fan_eft = (fan ? 1 : 0); + + if (unlikely(first && !at->reset)) { + at->fan_eft = 1; + at->timer_eft = 1; + first = false; + } + + at->fan_pwm_data = (fan ? fan : AVALON_DEFAULT_FAN_MAX_PWM); + at->timeout_data = timeout; + at->asic_num = asic_num; + at->miner_num = miner_num; + at->nonce_elf = nonce_elf; + + at->gate_miner_elf = 1; + at->asic_pll = 1; + + if (unlikely(gate_miner)) { + at-> gate_miner = 1; + at->asic_pll = 0; + } + + buf = (uint8_t *)at; + buf[5] = 0x00; + buf[8] = 0x74; + buf[9] = 0x01; + buf[10] = 0x00; + buf[11] = 0x00; + + /* With 55nm, this is the real clock in Mhz, 1Mhz means 2Mhs */ + lefreq16 = (uint16_t *)&buf[6]; + if (asic == AVALON_A3256) + frequency *= 8; + else + frequency = frequency * 32 / 50 + 0x7FE0; + *lefreq16 = htole16(frequency); + + return 0; +} + +static inline void avalon_create_task(struct avalon_task *at, + struct work *work) +{ + memcpy(at->midstate, work->midstate, 32); + memcpy(at->data, work->data + 64, 12); +} + +static int avalon_write(struct cgpu_info *avalon, char *buf, ssize_t len, int ep) +{ + int err, amount; + + err = usb_write(avalon, buf, len, &amount, ep); + applog(LOG_DEBUG, "%s%i: usb_write got err %d", avalon->drv->name, + avalon->device_id, err); + + if (unlikely(err != 0)) { + applog(LOG_WARNING, "usb_write error on avalon_write"); + return AVA_SEND_ERROR; + } + if (amount != len) { + applog(LOG_WARNING, "usb_write length mismatch on avalon_write"); + return AVA_SEND_ERROR; + } + + return AVA_SEND_OK; +} + +static int avalon_send_task(const struct avalon_task *at, struct cgpu_info *avalon, + struct avalon_info *info) + +{ + uint8_t buf[AVALON_WRITE_SIZE + 4 * AVALON_DEFAULT_ASIC_NUM]; + int delay, ret, i, ep = C_AVALON_TASK; + uint32_t nonce_range; + size_t nr_len; + + if (at->nonce_elf) + nr_len = AVALON_WRITE_SIZE + 4 * at->asic_num; + else + nr_len = AVALON_WRITE_SIZE; + + memcpy(buf, at, AVALON_WRITE_SIZE); + + if (at->nonce_elf) { + nonce_range = (uint32_t)0xffffffff / at->asic_num; + for (i = 0; i < at->asic_num; i++) { + buf[AVALON_WRITE_SIZE + (i * 4) + 3] = + (i * nonce_range & 0xff000000) >> 24; + buf[AVALON_WRITE_SIZE + (i * 4) + 2] = + (i * nonce_range & 0x00ff0000) >> 16; + buf[AVALON_WRITE_SIZE + (i * 4) + 1] = + (i * nonce_range & 0x0000ff00) >> 8; + buf[AVALON_WRITE_SIZE + (i * 4) + 0] = + (i * nonce_range & 0x000000ff) >> 0; + } + } +#if defined(__BIG_ENDIAN__) || defined(MIPSEB) + uint8_t tt = 0; + + tt = (buf[0] & 0x0f) << 4; + tt |= ((buf[0] & 0x10) ? (1 << 3) : 0); + tt |= ((buf[0] & 0x20) ? (1 << 2) : 0); + tt |= ((buf[0] & 0x40) ? (1 << 1) : 0); + tt |= ((buf[0] & 0x80) ? (1 << 0) : 0); + buf[0] = tt; + + tt = (buf[4] & 0x0f) << 4; + tt |= ((buf[4] & 0x10) ? (1 << 3) : 0); + tt |= ((buf[4] & 0x20) ? (1 << 2) : 0); + tt |= ((buf[4] & 0x40) ? (1 << 1) : 0); + tt |= ((buf[4] & 0x80) ? (1 << 0) : 0); + buf[4] = tt; +#endif + delay = nr_len * 10 * 1000000; + delay = delay / info->baud; + delay += 4000; + + if (at->reset) { + ep = C_AVALON_RESET; + nr_len = 1; + } + if (opt_debug) { + applog(LOG_DEBUG, "Avalon: Sent(%u):", (unsigned int)nr_len); + hexdump(buf, nr_len); + } + /* Sleep from the last time we sent data */ + cgsleep_us_r(&info->cgsent, info->send_delay); + + cgsleep_prepare_r(&info->cgsent); + ret = avalon_write(avalon, (char *)buf, nr_len, ep); + + applog(LOG_DEBUG, "Avalon: Sent: Buffer delay: %dus", info->send_delay); + info->send_delay = delay; + + return ret; +} + +static int bitburner_send_task(const struct avalon_task *at, struct cgpu_info *avalon) + +{ + uint8_t buf[AVALON_WRITE_SIZE + 4 * AVALON_DEFAULT_ASIC_NUM]; + int ret, ep = C_AVALON_TASK; + cgtimer_t ts_start; + size_t nr_len; + + if (at->nonce_elf) + nr_len = AVALON_WRITE_SIZE + 4 * at->asic_num; + else + nr_len = AVALON_WRITE_SIZE; + + memset(buf, 0, nr_len); + memcpy(buf, at, AVALON_WRITE_SIZE); + +#if defined(__BIG_ENDIAN__) || defined(MIPSEB) + uint8_t tt = 0; + + tt = (buf[0] & 0x0f) << 4; + tt |= ((buf[0] & 0x10) ? (1 << 3) : 0); + tt |= ((buf[0] & 0x20) ? (1 << 2) : 0); + tt |= ((buf[0] & 0x40) ? (1 << 1) : 0); + tt |= ((buf[0] & 0x80) ? (1 << 0) : 0); + buf[0] = tt; + + tt = (buf[4] & 0x0f) << 4; + tt |= ((buf[4] & 0x10) ? (1 << 3) : 0); + tt |= ((buf[4] & 0x20) ? (1 << 2) : 0); + tt |= ((buf[4] & 0x40) ? (1 << 1) : 0); + tt |= ((buf[4] & 0x80) ? (1 << 0) : 0); + buf[4] = tt; +#endif + + if (at->reset) { + ep = C_AVALON_RESET; + nr_len = 1; + } + if (opt_debug) { + applog(LOG_DEBUG, "Avalon: Sent(%u):", (unsigned int)nr_len); + hexdump(buf, nr_len); + } + cgsleep_prepare_r(&ts_start); + ret = avalon_write(avalon, (char *)buf, nr_len, ep); + cgsleep_us_r(&ts_start, 3000); // 3 ms = 333 tasks per second, or 1.4 TH/s + + return ret; +} + +static bool avalon_decode_nonce(struct thr_info *thr, struct cgpu_info *avalon, + struct avalon_info *info, struct avalon_result *ar, + struct work *work) +{ + uint32_t nonce; + + info = avalon->device_data; + info->matching_work[work->subid]++; + nonce = htole32(ar->nonce); + if (info->asic == AVALON_A3255) + nonce -= 0xc0; + applog(LOG_DEBUG, "Avalon: nonce = %0x08x", nonce); + return submit_nonce(thr, work, nonce); +} + +/* Wait until the ftdi chip returns a CTS saying we can send more data. */ +static void wait_avalon_ready(struct cgpu_info *avalon) +{ + while (avalon_buffer_full(avalon)) { + cgsleep_ms(40); + } +} + +#define AVALON_CTS (1 << 4) + +static inline bool avalon_cts(char c) +{ + return (c & AVALON_CTS); +} + +static int avalon_read(struct cgpu_info *avalon, char *buf, size_t bufsize, int ep) +{ + size_t total = 0, readsize = bufsize + 2; + char readbuf[AVALON_READBUF_SIZE]; + int err, amount, ofs = 2, cp; + + err = usb_read_once(avalon, readbuf, readsize, &amount, ep); + applog(LOG_DEBUG, "%s%i: Get avalon read got err %d", + avalon->drv->name, avalon->device_id, err); + if (err && err != LIBUSB_ERROR_TIMEOUT) + return err; + + if (amount < 2) + goto out; + + /* The first 2 of every 64 bytes are status on FTDIRL */ + while (amount > 2) { + cp = amount - 2; + if (cp > 62) + cp = 62; + memcpy(&buf[total], &readbuf[ofs], cp); + total += cp; + amount -= cp + 2; + ofs += 64; + } +out: + return total; +} + +static int avalon_reset(struct cgpu_info *avalon, bool initial) +{ + struct avalon_result ar; + int ret, i, spare; + struct avalon_task at; + uint8_t *buf, *tmp; + struct timespec p; + struct avalon_info *info = avalon->device_data; + + /* Send reset, then check for result */ + avalon_init_task(&at, 1, 0, + AVALON_DEFAULT_FAN_MAX_PWM, + AVALON_DEFAULT_TIMEOUT, + AVALON_DEFAULT_ASIC_NUM, + AVALON_DEFAULT_MINER_NUM, + 0, 0, + AVALON_DEFAULT_FREQUENCY, + AVALON_A3256); + + wait_avalon_ready(avalon); + ret = avalon_send_task(&at, avalon, info); + if (unlikely(ret == AVA_SEND_ERROR)) + return -1; + + if (!initial) { + applog(LOG_ERR, "%s%d reset sequence sent", avalon->drv->name, avalon->device_id); + return 0; + } + + ret = avalon_read(avalon, (char *)&ar, AVALON_READ_SIZE, C_GET_AVALON_RESET); + + /* What do these sleeps do?? */ + p.tv_sec = 0; + p.tv_nsec = AVALON_RESET_PITCH; + nanosleep(&p, NULL); + + /* Look for the first occurrence of 0xAA, the reset response should be: + * AA 55 AA 55 00 00 00 00 00 00 */ + spare = ret - 10; + buf = tmp = (uint8_t *)&ar; + if (opt_debug) { + applog(LOG_DEBUG, "%s%d reset: get:", avalon->drv->name, avalon->device_id); + hexdump(tmp, AVALON_READ_SIZE); + } + + for (i = 0; i <= spare; i++) { + buf = &tmp[i]; + if (buf[0] == 0xAA) + break; + } + i = 0; + + if (buf[0] == 0xAA && buf[1] == 0x55 && + buf[2] == 0xAA && buf[3] == 0x55) { + for (i = 4; i < 11; i++) + if (buf[i] != 0) + break; + } + + if (i != 11) { + applog(LOG_ERR, "%s%d: Reset failed! not an Avalon?" + " (%d: %02x %02x %02x %02x)", avalon->drv->name, avalon->device_id, + i, buf[0], buf[1], buf[2], buf[3]); + /* FIXME: return 1; */ + } else { + /* buf[44]: minor + * buf[45]: day + * buf[46]: year,month, d6: 201306 + */ + info->ctlr_ver = ((buf[46] >> 4) + 2000) * 1000000 + + (buf[46] & 0x0f) * 10000 + + buf[45] * 100 + buf[44]; + applog(LOG_WARNING, "%s%d: Reset succeeded (Controller version: %d)", + avalon->drv->name, avalon->device_id, info->ctlr_ver); + } + + return 0; +} + +static int avalon_calc_timeout(int frequency) +{ + return AVALON_TIMEOUT_FACTOR / frequency; +} + +static bool get_options(int this_option_offset, int *baud, int *miner_count, + int *asic_count, int *timeout, int *frequency, int *asic, + char *options) +{ + char buf[BUFSIZ+1]; + char *ptr, *comma, *colon, *colon2, *colon3, *colon4, *colon5; + bool timeout_default; + size_t max; + int i, tmp; + + if (options == NULL) + buf[0] = '\0'; + else { + ptr = options; + for (i = 0; i < this_option_offset; i++) { + comma = strchr(ptr, ','); + if (comma == NULL) + break; + ptr = comma + 1; + } + + comma = strchr(ptr, ','); + if (comma == NULL) + max = strlen(ptr); + else + max = comma - ptr; + + if (max > BUFSIZ) + max = BUFSIZ; + strncpy(buf, ptr, max); + buf[max] = '\0'; + } + + if (!(*buf)) + return false; + + colon = strchr(buf, ':'); + if (colon) + *(colon++) = '\0'; + + tmp = atoi(buf); + switch (tmp) { + case 115200: + *baud = 115200; + break; + case 57600: + *baud = 57600; + break; + case 38400: + *baud = 38400; + break; + case 19200: + *baud = 19200; + break; + default: + quit(1, "Invalid avalon-options for baud (%s) " + "must be 115200, 57600, 38400 or 19200", buf); + } + + if (colon && *colon) { + colon2 = strchr(colon, ':'); + if (colon2) + *(colon2++) = '\0'; + + if (*colon) { + tmp = atoi(colon); + if (tmp > 0 && tmp <= AVALON_MAX_MINER_NUM) { + *miner_count = tmp; + } else { + quit(1, "Invalid avalon-options for " + "miner_count (%s) must be 1 ~ %d", + colon, AVALON_MAX_MINER_NUM); + } + } + + if (colon2 && *colon2) { + colon3 = strchr(colon2, ':'); + if (colon3) + *(colon3++) = '\0'; + + tmp = atoi(colon2); + if (tmp > 0 && tmp <= AVALON_DEFAULT_ASIC_NUM) + *asic_count = tmp; + else { + quit(1, "Invalid avalon-options for " + "asic_count (%s) must be 1 ~ %d", + colon2, AVALON_DEFAULT_ASIC_NUM); + } + + timeout_default = false; + if (colon3 && *colon3) { + colon4 = strchr(colon3, ':'); + if (colon4) + *(colon4++) = '\0'; + + if (tolower(*colon3) == 'd') + timeout_default = true; + else { + tmp = atoi(colon3); + if (tmp > 0 && tmp <= 0xff) + *timeout = tmp; + else { + quit(1, "Invalid avalon-options for " + "timeout (%s) must be 1 ~ %d", + colon3, 0xff); + } + } + if (colon4 && *colon4) { + colon5 = strchr(colon4, ':'); + if (colon5) + *(colon5++) = '\0'; + + tmp = atoi(colon4); + if (tmp < AVALON_MIN_FREQUENCY || tmp > AVALON_MAX_FREQUENCY) { + quit(1, "Invalid avalon-options for frequency, must be %d <= frequency <= %d", + AVALON_MIN_FREQUENCY, AVALON_MAX_FREQUENCY); + } + *frequency = tmp; + if (timeout_default) + *timeout = avalon_calc_timeout(*frequency); + if (colon5 && *colon5) { + tmp = atoi(colon5); + if (tmp != AVALON_A3256 && tmp != AVALON_A3255) + quit(1, "Invalid avalon-options for asic, must be 110 or 55"); + *asic = tmp; + } + } + } + } + } + return true; +} + +char *set_avalon_fan(char *arg) +{ + int val1, val2, ret; + + ret = sscanf(arg, "%d-%d", &val1, &val2); + if (ret < 1) + return "No values passed to avalon-fan"; + if (ret == 1) + val2 = val1; + + if (val1 < 0 || val1 > 100 || val2 < 0 || val2 > 100 || val2 < val1) + return "Invalid value passed to avalon-fan"; + + opt_avalon_fan_min = val1 * AVALON_PWM_MAX / 100; + opt_avalon_fan_max = val2 * AVALON_PWM_MAX / 100; + + return NULL; +} + +char *set_avalon_freq(char *arg) +{ + int val1, val2, ret; + + ret = sscanf(arg, "%d-%d", &val1, &val2); + if (ret < 1) + return "No values passed to avalon-freq"; + if (ret == 1) + val2 = val1; + + if (val1 < AVALON_MIN_FREQUENCY || val1 > AVALON_MAX_FREQUENCY || + val2 < AVALON_MIN_FREQUENCY || val2 > AVALON_MAX_FREQUENCY || + val2 < val1) + return "Invalid value passed to avalon-freq"; + + opt_avalon_freq_min = val1; + opt_avalon_freq_max = val2; + + return NULL; +} + +static void avalon_idle(struct cgpu_info *avalon, struct avalon_info *info) +{ + int i; + + wait_avalon_ready(avalon); + /* Send idle to all miners */ + for (i = 0; i < info->miner_count; i++) { + struct avalon_task at; + + if (unlikely(avalon_buffer_full(avalon))) + break; + info->idle++; + avalon_init_task(&at, 0, 0, info->fan_pwm, info->timeout, + info->asic_count, info->miner_count, 1, 1, + info->frequency, info->asic); + if (avalon_send_task(&at, avalon, info) == AVA_SEND_ERROR) + break; + } + applog(LOG_WARNING, "%s%i: Idling %d miners", avalon->drv->name, avalon->device_id, i); + wait_avalon_ready(avalon); +} + +static void avalon_initialise(struct cgpu_info *avalon) +{ + int err, interface; + + if (avalon->usbinfo.nodev) + return; + + interface = usb_interface(avalon); + // Reset + err = usb_transfer(avalon, FTDI_TYPE_OUT, FTDI_REQUEST_RESET, + FTDI_VALUE_RESET, interface, C_RESET); + + applog(LOG_DEBUG, "%s%i: reset got err %d", + avalon->drv->name, avalon->device_id, err); + + if (avalon->usbinfo.nodev) + return; + + // Set latency + err = usb_transfer(avalon, FTDI_TYPE_OUT, FTDI_REQUEST_LATENCY, + AVALON_LATENCY, interface, C_LATENCY); + + applog(LOG_DEBUG, "%s%i: latency got err %d", + avalon->drv->name, avalon->device_id, err); + + if (avalon->usbinfo.nodev) + return; + + // Set data + err = usb_transfer(avalon, FTDI_TYPE_OUT, FTDI_REQUEST_DATA, + FTDI_VALUE_DATA_AVA, interface, C_SETDATA); + + applog(LOG_DEBUG, "%s%i: data got err %d", + avalon->drv->name, avalon->device_id, err); + + if (avalon->usbinfo.nodev) + return; + + // Set the baud + err = usb_transfer(avalon, FTDI_TYPE_OUT, FTDI_REQUEST_BAUD, FTDI_VALUE_BAUD_AVA, + (FTDI_INDEX_BAUD_AVA & 0xff00) | interface, + C_SETBAUD); + + applog(LOG_DEBUG, "%s%i: setbaud got err %d", + avalon->drv->name, avalon->device_id, err); + + if (avalon->usbinfo.nodev) + return; + + // Set Modem Control + err = usb_transfer(avalon, FTDI_TYPE_OUT, FTDI_REQUEST_MODEM, + FTDI_VALUE_MODEM, interface, C_SETMODEM); + + applog(LOG_DEBUG, "%s%i: setmodemctrl got err %d", + avalon->drv->name, avalon->device_id, err); + + if (avalon->usbinfo.nodev) + return; + + // Set Flow Control + err = usb_transfer(avalon, FTDI_TYPE_OUT, FTDI_REQUEST_FLOW, + FTDI_VALUE_FLOW, interface, C_SETFLOW); + + applog(LOG_DEBUG, "%s%i: setflowctrl got err %d", + avalon->drv->name, avalon->device_id, err); + + if (avalon->usbinfo.nodev) + return; + + /* Avalon repeats the following */ + // Set Modem Control + err = usb_transfer(avalon, FTDI_TYPE_OUT, FTDI_REQUEST_MODEM, + FTDI_VALUE_MODEM, interface, C_SETMODEM); + + applog(LOG_DEBUG, "%s%i: setmodemctrl 2 got err %d", + avalon->drv->name, avalon->device_id, err); + + if (avalon->usbinfo.nodev) + return; + + // Set Flow Control + err = usb_transfer(avalon, FTDI_TYPE_OUT, FTDI_REQUEST_FLOW, + FTDI_VALUE_FLOW, interface, C_SETFLOW); + + applog(LOG_DEBUG, "%s%i: setflowctrl 2 got err %d", + avalon->drv->name, avalon->device_id, err); +} + +static bool is_bitburner(struct cgpu_info *avalon) +{ + enum sub_ident ident; + + ident = usb_ident(avalon); + return ident == IDENT_BTB || ident == IDENT_BBF; +} + +static bool bitburner_set_core_voltage(struct cgpu_info *avalon, int core_voltage) +{ + uint8_t buf[2]; + int err; + + if (is_bitburner(avalon)) { + buf[0] = (uint8_t)core_voltage; + buf[1] = (uint8_t)(core_voltage >> 8); + err = usb_transfer_data(avalon, FTDI_TYPE_OUT, BITBURNER_REQUEST, + BITBURNER_VALUE, BITBURNER_INDEX_SET_VOLTAGE, + (uint32_t *)buf, sizeof(buf), C_BB_SET_VOLTAGE); + if (unlikely(err < 0)) { + applog(LOG_ERR, "%s%i: SetCoreVoltage failed: err = %d", + avalon->drv->name, avalon->device_id, err); + return false; + } else { + applog(LOG_WARNING, "%s%i: Core voltage set to %d millivolts", + avalon->drv->name, avalon->device_id, + core_voltage); + } + return true; + } + return false; +} + +static int bitburner_get_core_voltage(struct cgpu_info *avalon) +{ + uint8_t buf[2]; + int err; + int amount; + + if (is_bitburner(avalon)) { + err = usb_transfer_read(avalon, FTDI_TYPE_IN, BITBURNER_REQUEST, + BITBURNER_VALUE, BITBURNER_INDEX_GET_VOLTAGE, + (char *)buf, sizeof(buf), &amount, + C_BB_GET_VOLTAGE); + if (unlikely(err != 0 || amount != 2)) { + applog(LOG_ERR, "%s%i: GetCoreVoltage failed: err = %d, amount = %d", + avalon->drv->name, avalon->device_id, err, amount); + return 0; + } else { + return (int)(buf[0] + ((unsigned int)buf[1] << 8)); + } + } else { + return 0; + } +} + +static void bitburner_get_version(struct cgpu_info *avalon) +{ + struct avalon_info *info = avalon->device_data; + uint8_t buf[3]; + int err; + int amount; + + err = usb_transfer_read(avalon, FTDI_TYPE_IN, BITBURNER_REQUEST, + BITBURNER_VALUE, BITBURNER_INDEX_GET_VERSION, + (char *)buf, sizeof(buf), &amount, + C_GETVERSION); + if (unlikely(err != 0 || amount != sizeof(buf))) { + applog(LOG_DEBUG, "%s%i: GetVersion failed: err=%d, amt=%d assuming %d.%d.%d", + avalon->drv->name, avalon->device_id, err, amount, + BITBURNER_VERSION1, BITBURNER_VERSION2, BITBURNER_VERSION3); + info->version1 = BITBURNER_VERSION1; + info->version2 = BITBURNER_VERSION2; + info->version3 = BITBURNER_VERSION3; + } else { + info->version1 = buf[0]; + info->version2 = buf[1]; + info->version3 = buf[2]; + } +} + +static struct cgpu_info *avalon_detect_one(libusb_device *dev, struct usb_find_devices *found) +{ + int baud, miner_count, asic_count, timeout, frequency, asic; + int this_option_offset; + struct avalon_info *info; + struct cgpu_info *avalon; + bool configured; + int ret; + + avalon = usb_alloc_cgpu(&avalon_drv, AVALON_MINER_THREADS); + + baud = AVALON_IO_SPEED; + miner_count = AVALON_DEFAULT_MINER_NUM; + asic_count = AVALON_DEFAULT_ASIC_NUM; + timeout = AVALON_DEFAULT_TIMEOUT; + frequency = AVALON_DEFAULT_FREQUENCY; + asic = AVALON_A3256; + + if (!usb_init(avalon, dev, found)) + goto shin; + + this_option_offset = usb_ident(avalon) == IDENT_BBF ? ++bbf_option_offset : ++option_offset; + configured = get_options(this_option_offset, &baud, &miner_count, + &asic_count, &timeout, &frequency, &asic, + (usb_ident(avalon) == IDENT_BBF && opt_bitburner_fury_options != NULL) ? opt_bitburner_fury_options : opt_avalon_options); + + /* Even though this is an FTDI type chip, we want to do the parsing + * all ourselves so set it to std usb type */ + avalon->usbdev->usb_type = USB_TYPE_STD; + + /* We have a real Avalon! */ + avalon_initialise(avalon); + + avalon->device_data = calloc(sizeof(struct avalon_info), 1); + if (unlikely(!(avalon->device_data))) + quit(1, "Failed to calloc avalon_info data"); + info = avalon->device_data; + + if (configured) { + info->asic = asic; + info->baud = baud; + info->miner_count = miner_count; + info->asic_count = asic_count; + info->timeout = timeout; + info->frequency = frequency; + } else { + info->asic = AVALON_A3256; + info->baud = AVALON_IO_SPEED; + info->asic_count = AVALON_DEFAULT_ASIC_NUM; + switch (usb_ident(avalon)) { + case IDENT_BBF: + info->miner_count = BITBURNER_FURY_DEFAULT_MINER_NUM; + info->timeout = BITBURNER_FURY_DEFAULT_TIMEOUT; + info->frequency = BITBURNER_FURY_DEFAULT_FREQUENCY; + break; + default: + info->miner_count = AVALON_DEFAULT_MINER_NUM; + info->timeout = AVALON_DEFAULT_TIMEOUT; + info->frequency = AVALON_DEFAULT_FREQUENCY; + } + } + if (info->asic == AVALON_A3255) + info->increment = info->decrement = 50; + else { + info->increment = 2; + info->decrement = 1; + } + + info->fan_pwm = AVALON_DEFAULT_FAN_MIN_PWM; + /* This is for check the temp/fan every 3~4s */ + info->temp_history_count = + (4 / (float)((float)info->timeout * (AVALON_A3256 / info->asic) * ((float)1.67/0x32))) + 1; + if (info->temp_history_count <= 0) + info->temp_history_count = 1; + + info->temp_history_index = 0; + info->temp_sum = 0; + info->temp_old = 0; + + if (!add_cgpu(avalon)) + goto unshin; + + ret = avalon_reset(avalon, true); + if (ret && !configured) + goto unshin; + + update_usb_stats(avalon); + + avalon_idle(avalon, info); + + applog(LOG_DEBUG, "Avalon Detected: %s " + "(miner_count=%d asic_count=%d timeout=%d frequency=%d chip=%d)", + avalon->device_path, info->miner_count, info->asic_count, info->timeout, + info->frequency, info->asic); + + if (usb_ident(avalon) == IDENT_BTB) { + if (opt_bitburner_core_voltage < BITBURNER_MIN_COREMV || + opt_bitburner_core_voltage > BITBURNER_MAX_COREMV) { + quit(1, "Invalid bitburner-voltage %d must be %dmv - %dmv", + opt_bitburner_core_voltage, + BITBURNER_MIN_COREMV, + BITBURNER_MAX_COREMV); + } else + bitburner_set_core_voltage(avalon, opt_bitburner_core_voltage); + } else if (usb_ident(avalon) == IDENT_BBF) { + if (opt_bitburner_fury_core_voltage < BITBURNER_FURY_MIN_COREMV || + opt_bitburner_fury_core_voltage > BITBURNER_FURY_MAX_COREMV) { + quit(1, "Invalid bitburner-fury-voltage %d must be %dmv - %dmv", + opt_bitburner_fury_core_voltage, + BITBURNER_FURY_MIN_COREMV, + BITBURNER_FURY_MAX_COREMV); + } else + bitburner_set_core_voltage(avalon, opt_bitburner_fury_core_voltage); + } + + if (is_bitburner(avalon)) { + bitburner_get_version(avalon); + } + + return avalon; + +unshin: + + usb_uninit(avalon); + +shin: + + free(avalon->device_data); + avalon->device_data = NULL; + + avalon = usb_free_cgpu(avalon); + + return NULL; +} + +static void avalon_detect(bool __maybe_unused hotplug) +{ + usb_detect(&avalon_drv, avalon_detect_one); +} + +static void avalon_init(struct cgpu_info *avalon) +{ + applog(LOG_INFO, "Avalon: Opened on %s", avalon->device_path); +} + +static struct work *avalon_valid_result(struct cgpu_info *avalon, struct avalon_result *ar) +{ + return clone_queued_work_bymidstate(avalon, (char *)ar->midstate, 32, + (char *)ar->data, 64, 12); +} + +static void avalon_update_temps(struct cgpu_info *avalon, struct avalon_info *info, + struct avalon_result *ar); + +static void avalon_inc_nvw(struct avalon_info *info, struct thr_info *thr) +{ + applog(LOG_INFO, "%s%d: No matching work - HW error", + thr->cgpu->drv->name, thr->cgpu->device_id); + + inc_hw_errors(thr); + info->no_matching_work++; +} + +static void avalon_parse_results(struct cgpu_info *avalon, struct avalon_info *info, + struct thr_info *thr, char *buf, int *offset) +{ + int i, spare = *offset - AVALON_READ_SIZE; + bool found = false; + + for (i = 0; i <= spare; i++) { + struct avalon_result *ar; + struct work *work; + + ar = (struct avalon_result *)&buf[i]; + work = avalon_valid_result(avalon, ar); + if (work) { + bool gettemp = false; + + found = true; + + if (avalon_decode_nonce(thr, avalon, info, ar, work)) { + mutex_lock(&info->lock); + if (!info->nonces++) + gettemp = true; + info->auto_nonces++; + mutex_unlock(&info->lock); + } else if (opt_avalon_auto) { + mutex_lock(&info->lock); + info->auto_hw++; + mutex_unlock(&info->lock); + } + free_work(work); + + if (gettemp) + avalon_update_temps(avalon, info, ar); + break; + } + } + + if (!found) { + spare = *offset - AVALON_READ_SIZE; + /* We are buffering and haven't accumulated one more corrupt + * work result. */ + if (spare < (int)AVALON_READ_SIZE) + return; + avalon_inc_nvw(info, thr); + } else { + spare = AVALON_READ_SIZE + i; + if (i) { + if (i >= (int)AVALON_READ_SIZE) + avalon_inc_nvw(info, thr); + else + applog(LOG_WARNING, "Avalon: Discarding %d bytes from buffer", i); + } + } + + *offset -= spare; + memmove(buf, buf + spare, *offset); +} + +static void avalon_running_reset(struct cgpu_info *avalon, + struct avalon_info *info) +{ + avalon_reset(avalon, false); + avalon_idle(avalon, info); + avalon->results = 0; + info->reset = false; +} + +static void *avalon_get_results(void *userdata) +{ + struct cgpu_info *avalon = (struct cgpu_info *)userdata; + struct avalon_info *info = avalon->device_data; + const int rsize = AVALON_FTDI_READSIZE; + char readbuf[AVALON_READBUF_SIZE]; + struct thr_info *thr = info->thr; + int offset = 0, ret = 0; + char threadname[16]; + + snprintf(threadname, sizeof(threadname), "%d/AvaRecv", avalon->device_id); + RenameThread(threadname); + + while (likely(!avalon->shutdown)) { + char buf[rsize]; + + if (offset >= (int)AVALON_READ_SIZE) + avalon_parse_results(avalon, info, thr, readbuf, &offset); + + if (unlikely(offset + rsize >= AVALON_READBUF_SIZE)) { + /* This should never happen */ + applog(LOG_ERR, "Avalon readbuf overflow, resetting buffer"); + offset = 0; + } + + if (unlikely(info->reset)) { + avalon_running_reset(avalon, info); + /* Discard anything in the buffer */ + offset = 0; + } + + ret = avalon_read(avalon, buf, rsize, C_AVALON_READ); + + if (unlikely(ret < 0)) + break; + + if (ret < 1) + continue; + + if (opt_debug) { + applog(LOG_DEBUG, "Avalon: get:"); + hexdump((uint8_t *)buf, ret); + } + + memcpy(&readbuf[offset], &buf, ret); + offset += ret; + } + return NULL; +} + +static void avalon_rotate_array(struct cgpu_info *avalon, struct avalon_info *info) +{ + mutex_lock(&info->qlock); + avalon->queued = 0; + if (++avalon->work_array >= AVALON_ARRAY_SIZE) + avalon->work_array = 0; + mutex_unlock(&info->qlock); +} + +static void bitburner_rotate_array(struct cgpu_info *avalon) +{ + avalon->queued = 0; + if (++avalon->work_array >= BITBURNER_ARRAY_SIZE) + avalon->work_array = 0; +} + +static void avalon_set_timeout(struct avalon_info *info) +{ + info->timeout = avalon_calc_timeout(info->frequency); +} + +static void avalon_set_freq(struct cgpu_info *avalon, int frequency) +{ + struct avalon_info *info = avalon->device_data; + + info->frequency = frequency; + if (info->frequency > opt_avalon_freq_max) + info->frequency = opt_avalon_freq_max; + if (info->frequency < opt_avalon_freq_min) + info->frequency = opt_avalon_freq_min; + avalon_set_timeout(info); + applog(LOG_WARNING, "%s%i: Set frequency to %d, timeout %d", + avalon->drv->name, avalon->device_id, + info->frequency, info->timeout); +} + +static void avalon_inc_freq(struct avalon_info *info) +{ + info->frequency += info->increment; + if (info->frequency > opt_avalon_freq_max) + info->frequency = opt_avalon_freq_max; + avalon_set_timeout(info); + applog(LOG_NOTICE, "Avalon increasing frequency to %d, timeout %d", + info->frequency, info->timeout); +} + +static void avalon_dec_freq(struct avalon_info *info) +{ + info->frequency -= info->decrement; + if (info->frequency < opt_avalon_freq_min) + info->frequency = opt_avalon_freq_min; + avalon_set_timeout(info); + applog(LOG_NOTICE, "Avalon decreasing frequency to %d, timeout %d", + info->frequency, info->timeout); +} + +static void avalon_reset_auto(struct avalon_info *info) +{ + info->auto_queued = + info->auto_nonces = + info->auto_hw = 0; +} + +static void avalon_adjust_freq(struct avalon_info *info, struct cgpu_info *avalon) +{ + if (opt_avalon_auto && info->auto_queued >= AVALON_AUTO_CYCLE) { + mutex_lock(&info->lock); + if (!info->optimal) { + if (info->fan_pwm >= opt_avalon_fan_max) { + applog(LOG_WARNING, + "%s%i: Above optimal temperature, throttling", + avalon->drv->name, avalon->device_id); + avalon_dec_freq(info); + } + } else if (info->auto_nonces >= AVALON_AUTO_CYCLE / 2) { + int total = info->auto_nonces + info->auto_hw; + + /* Try to keep hw errors < 2% */ + if (info->auto_hw * 100 < total) + avalon_inc_freq(info); + else if (info->auto_hw * 66 > total) + avalon_dec_freq(info); + } + avalon_reset_auto(info); + mutex_unlock(&info->lock); + } +} + +static void *avalon_send_tasks(void *userdata) +{ + struct cgpu_info *avalon = (struct cgpu_info *)userdata; + struct avalon_info *info = avalon->device_data; + const int avalon_get_work_count = info->miner_count; + char threadname[16]; + + snprintf(threadname, sizeof(threadname), "%d/AvaSend", avalon->device_id); + RenameThread(threadname); + + while (likely(!avalon->shutdown)) { + int start_count, end_count, i, j, ret; + cgtimer_t ts_start; + struct avalon_task at; + bool idled = false; + int64_t us_timeout; + + while (avalon_buffer_full(avalon)) + cgsleep_ms(40); + + avalon_adjust_freq(info, avalon); + + /* A full nonce range */ + us_timeout = 0x100000000ll / info->asic_count / info->frequency; + cgsleep_prepare_r(&ts_start); + + start_count = avalon->work_array * avalon_get_work_count; + end_count = start_count + avalon_get_work_count; + for (i = start_count, j = 0; i < end_count; i++, j++) { + if (avalon_buffer_full(avalon)) { + applog(LOG_INFO, + "%s%i: Buffer full after only %d of %d work queued", + avalon->drv->name, avalon->device_id, j, avalon_get_work_count); + break; + } + + mutex_lock(&info->qlock); + if (likely(j < avalon->queued && !info->overheat && avalon->works[i])) { + avalon_init_task(&at, 0, 0, info->fan_pwm, + info->timeout, info->asic_count, + info->miner_count, 1, 0, info->frequency, info->asic); + avalon_create_task(&at, avalon->works[i]); + info->auto_queued++; + } else { + int idle_freq = info->frequency; + + if (!info->idle++) + idled = true; + if (unlikely(info->overheat && opt_avalon_auto)) + idle_freq = AVALON_MIN_FREQUENCY; + avalon_init_task(&at, 0, 0, info->fan_pwm, + info->timeout, info->asic_count, + info->miner_count, 1, 1, idle_freq, info->asic); + /* Reset the auto_queued count if we end up + * idling any miners. */ + avalon_reset_auto(info); + } + mutex_unlock(&info->qlock); + + ret = avalon_send_task(&at, avalon, info); + + if (unlikely(ret == AVA_SEND_ERROR)) { + /* Send errors are fatal */ + applog(LOG_ERR, "%s%i: Comms error(buffer)", + avalon->drv->name, avalon->device_id); + dev_error(avalon, REASON_DEV_COMMS_ERROR); + goto out; + } + } + + avalon_rotate_array(avalon, info); + + cgsem_post(&info->qsem); + + if (unlikely(idled)) { + applog(LOG_WARNING, "%s%i: Idled %d miners", + avalon->drv->name, avalon->device_id, idled); + } + + /* Sleep how long it would take to complete a full nonce range + * at the current frequency using the clock_nanosleep function + * timed from before we started loading new work so it will + * fall short of the full duration. */ + cgsleep_us_r(&ts_start, us_timeout); + } +out: + return NULL; +} + +static void *bitburner_send_tasks(void *userdata) +{ + struct cgpu_info *avalon = (struct cgpu_info *)userdata; + struct avalon_info *info = avalon->device_data; + const int avalon_get_work_count = info->miner_count; + char threadname[16]; + + snprintf(threadname, sizeof(threadname), "%d/AvaSend", avalon->device_id); + RenameThread(threadname); + + while (likely(!avalon->shutdown)) { + int start_count, end_count, i, j, ret; + struct avalon_task at; + bool idled = false; + + while (avalon_buffer_full(avalon)) + cgsleep_ms(40); + + avalon_adjust_freq(info, avalon); + + /* Give other threads a chance to acquire qlock. */ + i = 0; + do { + cgsleep_ms(40); + } while (!avalon->shutdown && i++ < 15 + && avalon->queued < avalon_get_work_count); + + mutex_lock(&info->qlock); + start_count = avalon->work_array * avalon_get_work_count; + end_count = start_count + avalon_get_work_count; + for (i = start_count, j = 0; i < end_count; i++, j++) { + while (avalon_buffer_full(avalon)) + cgsleep_ms(40); + + if (likely(j < avalon->queued && !info->overheat && avalon->works[i])) { + avalon_init_task(&at, 0, 0, info->fan_pwm, + info->timeout, info->asic_count, + info->miner_count, 1, 0, info->frequency, info->asic); + avalon_create_task(&at, avalon->works[i]); + info->auto_queued++; + } else { + int idle_freq = info->frequency; + + if (!info->idle++) + idled = true; + if (unlikely(info->overheat && opt_avalon_auto)) + idle_freq = AVALON_MIN_FREQUENCY; + avalon_init_task(&at, 0, 0, info->fan_pwm, + info->timeout, info->asic_count, + info->miner_count, 1, 1, idle_freq, info->asic); + /* Reset the auto_queued count if we end up + * idling any miners. */ + avalon_reset_auto(info); + } + + ret = bitburner_send_task(&at, avalon); + + if (unlikely(ret == AVA_SEND_ERROR)) { + applog(LOG_ERR, "%s%i: Comms error(buffer)", + avalon->drv->name, avalon->device_id); + dev_error(avalon, REASON_DEV_COMMS_ERROR); + info->reset = true; + break; + } + } + + bitburner_rotate_array(avalon); + mutex_unlock(&info->qlock); + + cgsem_post(&info->qsem); + + if (unlikely(idled)) { + applog(LOG_WARNING, "%s%i: Idled %d miners", + avalon->drv->name, avalon->device_id, idled); + } + } + return NULL; +} + +static bool avalon_prepare(struct thr_info *thr) +{ + struct cgpu_info *avalon = thr->cgpu; + struct avalon_info *info = avalon->device_data; + int array_size = AVALON_ARRAY_SIZE; + void *(*write_thread_fn)(void *) = avalon_send_tasks; + + if (is_bitburner(avalon)) { + array_size = BITBURNER_ARRAY_SIZE; + write_thread_fn = bitburner_send_tasks; + } + + free(avalon->works); + avalon->works = calloc(info->miner_count * sizeof(struct work *), + array_size); + if (!avalon->works) + quit(1, "Failed to calloc avalon works in avalon_prepare"); + + info->thr = thr; + mutex_init(&info->lock); + mutex_init(&info->qlock); + cgsem_init(&info->qsem); + + if (pthread_create(&info->read_thr, NULL, avalon_get_results, (void *)avalon)) + quit(1, "Failed to create avalon read_thr"); + + if (pthread_create(&info->write_thr, NULL, write_thread_fn, (void *)avalon)) + quit(1, "Failed to create avalon write_thr"); + + avalon_init(avalon); + + return true; +} + +static inline void record_temp_fan(struct cgpu_info *avalon, struct avalon_info *info, + struct avalon_result *ar) +{ + double temp_max; + + info->fan0 = ar->fan0 * AVALON_FAN_FACTOR; + info->fan1 = ar->fan1 * AVALON_FAN_FACTOR; + info->fan2 = ar->fan2 * AVALON_FAN_FACTOR; + + info->temp0 = ar->temp0; + info->temp1 = ar->temp1; + info->temp2 = ar->temp2; + if (ar->temp0 & 0x80) { + ar->temp0 &= 0x7f; + info->temp0 = 0 - ((~ar->temp0 & 0x7f) + 1); + } + if (ar->temp1 & 0x80) { + ar->temp1 &= 0x7f; + info->temp1 = 0 - ((~ar->temp1 & 0x7f) + 1); + } + if (ar->temp2 & 0x80) { + ar->temp2 &= 0x7f; + info->temp2 = 0 - ((~ar->temp2 & 0x7f) + 1); + } + + temp_max = info->temp0; + if (info->temp1 > temp_max) + temp_max = info->temp1; + if (info->temp2 > temp_max) + temp_max = info->temp2; + avalon->temp = avalon->temp * 0.63 + temp_max * 0.37; +} + +static void temp_rise(struct avalon_info *info, int temp) +{ + if (temp >= opt_avalon_temp + AVALON_TEMP_HYSTERESIS * 3) { + info->fan_pwm = AVALON_PWM_MAX; + return; + } + if (temp >= opt_avalon_temp + AVALON_TEMP_HYSTERESIS * 2) + info->fan_pwm += 10; + else if (temp > opt_avalon_temp) + info->fan_pwm += 5; + else if (temp >= opt_avalon_temp - AVALON_TEMP_HYSTERESIS) + info->fan_pwm += 1; + else + return; + + if (info->fan_pwm > opt_avalon_fan_max) + info->fan_pwm = opt_avalon_fan_max; +} + +static void temp_drop(struct avalon_info *info, int temp) +{ + if (temp <= opt_avalon_temp - AVALON_TEMP_HYSTERESIS * 3) { + info->fan_pwm = opt_avalon_fan_min; + return; + } + if (temp <= opt_avalon_temp - AVALON_TEMP_HYSTERESIS * 2) + info->fan_pwm -= 10; + else if (temp <= opt_avalon_temp - AVALON_TEMP_HYSTERESIS) + info->fan_pwm -= 5; + else if (temp < opt_avalon_temp) + info->fan_pwm -= 1; + + if (info->fan_pwm < opt_avalon_fan_min) + info->fan_pwm = opt_avalon_fan_min; +} + +static inline void adjust_fan(struct avalon_info *info) +{ + int temp_new; + + temp_new = info->temp_sum / info->temp_history_count; + + if (temp_new > info->temp_old) + temp_rise(info, temp_new); + else if (temp_new < info->temp_old) + temp_drop(info, temp_new); + else { + /* temp_new == info->temp_old */ + if (temp_new > opt_avalon_temp) + temp_rise(info, temp_new); + else if (temp_new < opt_avalon_temp - AVALON_TEMP_HYSTERESIS) + temp_drop(info, temp_new); + } + info->temp_old = temp_new; + if (info->temp_old <= opt_avalon_temp) + info->optimal = true; + else + info->optimal = false; +} + +static void avalon_update_temps(struct cgpu_info *avalon, struct avalon_info *info, + struct avalon_result *ar) +{ + record_temp_fan(avalon, info, ar); + applog(LOG_INFO, + "Avalon: Fan1: %d/m, Fan2: %d/m, Fan3: %d/m\t" + "Temp1: %dC, Temp2: %dC, Temp3: %dC, TempMAX: %.0fC", + info->fan0, info->fan1, info->fan2, + info->temp0, info->temp1, info->temp2, avalon->temp); + info->temp_history_index++; + info->temp_sum += avalon->temp; + applog(LOG_DEBUG, "Avalon: temp_index: %d, temp_count: %d, temp_old: %d", + info->temp_history_index, info->temp_history_count, info->temp_old); + if (is_bitburner(avalon)) { + info->core_voltage = bitburner_get_core_voltage(avalon); + } + if (info->temp_history_index == info->temp_history_count) { + adjust_fan(info); + info->temp_history_index = 0; + info->temp_sum = 0; + } + if (unlikely(info->temp_old >= opt_avalon_overheat)) { + applog(LOG_WARNING, "%s%d overheat! Idling", avalon->drv->name, avalon->device_id); + info->overheat = true; + } else if (info->overheat && info->temp_old <= opt_avalon_temp) { + applog(LOG_WARNING, "%s%d cooled, restarting", avalon->drv->name, avalon->device_id); + info->overheat = false; + } +} + +static void get_avalon_statline_before(char *buf, size_t bufsiz, struct cgpu_info *avalon) +{ + struct avalon_info *info = avalon->device_data; + int lowfan = 10000; + + if (is_bitburner(avalon)) { + int temp = info->temp0; + if (info->temp2 > temp) + temp = info->temp2; + if (temp > 99) + temp = 99; + if (temp < 0) + temp = 0; + tailsprintf(buf, bufsiz, "%2dC %3dMHz %4dmV", temp, info->frequency, info->core_voltage); + } else { + /* Find the lowest fan speed of the ASIC cooling fans. */ + if (info->fan1 >= 0 && info->fan1 < lowfan) + lowfan = info->fan1; + if (info->fan2 >= 0 && info->fan2 < lowfan) + lowfan = info->fan2; + + tailsprintf(buf, bufsiz, "%2dC/%3dC %04dR", info->temp0, info->temp2, lowfan); + } +} + +/* We use a replacement algorithm to only remove references to work done from + * the buffer when we need the extra space for new work. */ +static bool avalon_fill(struct cgpu_info *avalon) +{ + struct avalon_info *info = avalon->device_data; + int subid, slot, mc; + struct work *work; + bool ret = true; + + mc = info->miner_count; + mutex_lock(&info->qlock); + if (avalon->queued >= mc) + goto out_unlock; + work = get_queued(avalon); + if (unlikely(!work)) { + ret = false; + goto out_unlock; + } + subid = avalon->queued++; + work->subid = subid; + slot = avalon->work_array * mc + subid; + if (likely(avalon->works[slot])) + work_completed(avalon, avalon->works[slot]); + avalon->works[slot] = work; + if (avalon->queued < mc) + ret = false; +out_unlock: + mutex_unlock(&info->qlock); + + return ret; +} + +static int64_t avalon_scanhash(struct thr_info *thr) +{ + struct cgpu_info *avalon = thr->cgpu; + struct avalon_info *info = avalon->device_data; + const int miner_count = info->miner_count; + int64_t hash_count, ms_timeout; + + /* Half nonce range */ + ms_timeout = 0x80000000ll / info->asic_count / info->frequency / 1000; + + /* Wait until avalon_send_tasks signals us that it has completed + * sending its work or a full nonce range timeout has occurred. We use + * cgsems to never miss a wakeup. */ + cgsem_mswait(&info->qsem, ms_timeout); + + mutex_lock(&info->lock); + hash_count = 0xffffffffull * (uint64_t)info->nonces; + avalon->results += info->nonces; + if (avalon->results > miner_count || info->idle) + avalon->results = miner_count; + if (!info->reset) + avalon->results--; + info->nonces = info->idle = 0; + mutex_unlock(&info->lock); + + /* Check for nothing but consecutive bad results or consistently less + * results than we should be getting and reset the FPGA if necessary */ + if (!is_bitburner(avalon)) { + if (avalon->results < -miner_count && !info->reset) { + applog(LOG_ERR, "%s%d: Result return rate low, resetting!", + avalon->drv->name, avalon->device_id); + avalon->results = miner_count; + info->reset = true; + } + } + + if (unlikely(avalon->usbinfo.nodev)) { + applog(LOG_ERR, "%s%d: Device disappeared, shutting down thread", + avalon->drv->name, avalon->device_id); + hash_count = -1; + } + + /* This hashmeter is just a utility counter based on returned shares */ + return hash_count; +} + +static void avalon_flush_work(struct cgpu_info *avalon) +{ + struct avalon_info *info = avalon->device_data; + + /* Will overwrite any work queued. Do this unlocked since it's just + * changing a single non-critical value and prevents deadlocks */ + avalon->queued = 0; + + /* Signal main loop we need more work */ + cgsem_post(&info->qsem); +} + +static struct api_data *avalon_api_stats(struct cgpu_info *cgpu) +{ + struct api_data *root = NULL; + struct avalon_info *info = cgpu->device_data; + char buf[64]; + int i; + double hwp = (cgpu->hw_errors + cgpu->diff1) ? + (double)(cgpu->hw_errors) / (double)(cgpu->hw_errors + cgpu->diff1) : 0; + + root = api_add_int(root, "baud", &(info->baud), false); + root = api_add_int(root, "miner_count", &(info->miner_count),false); + root = api_add_int(root, "asic_count", &(info->asic_count), false); + root = api_add_int(root, "timeout", &(info->timeout), false); + root = api_add_int(root, "frequency", &(info->frequency), false); + + root = api_add_int(root, "fan1", &(info->fan0), false); + root = api_add_int(root, "fan2", &(info->fan1), false); + root = api_add_int(root, "fan3", &(info->fan2), false); + + root = api_add_int(root, "temp1", &(info->temp0), false); + root = api_add_int(root, "temp2", &(info->temp1), false); + root = api_add_int(root, "temp3", &(info->temp2), false); + root = api_add_double(root, "temp_max", &cgpu->temp, false); + + root = api_add_percent(root, "Device Hardware%", &hwp, true); + root = api_add_int(root, "no_matching_work", &(info->no_matching_work), false); + for (i = 0; i < info->miner_count; i++) { + char mcw[24]; + + sprintf(mcw, "match_work_count%d", i + 1); + root = api_add_int(root, mcw, &(info->matching_work[i]), false); + } + if (is_bitburner(cgpu)) { + root = api_add_int(root, "core_voltage", &(info->core_voltage), false); + snprintf(buf, sizeof(buf), "%"PRIu8".%"PRIu8".%"PRIu8, + info->version1, info->version2, info->version3); + root = api_add_string(root, "version", buf, true); + } + root = api_add_uint32(root, "Controller Version", &(info->ctlr_ver), false); + root = api_add_uint32(root, "Avalon Chip", &(info->asic), false); + + return root; +} + +static void avalon_shutdown(struct thr_info *thr) +{ + struct cgpu_info *avalon = thr->cgpu; + struct avalon_info *info = avalon->device_data; + + pthread_join(info->read_thr, NULL); + pthread_join(info->write_thr, NULL); + avalon_running_reset(avalon, info); + cgsem_destroy(&info->qsem); + mutex_destroy(&info->qlock); + mutex_destroy(&info->lock); + free(avalon->works); + avalon->works = NULL; +} + +static char *avalon_set_device(struct cgpu_info *avalon, char *option, char *setting, char *replybuf) +{ + int val; + + if (strcasecmp(option, "help") == 0) { + sprintf(replybuf, "freq: range %d-%d millivolts: range %d-%d", + AVALON_MIN_FREQUENCY, AVALON_MAX_FREQUENCY, + BITBURNER_MIN_COREMV, BITBURNER_MAX_COREMV); + return replybuf; + } + + if (strcasecmp(option, "millivolts") == 0 || strcasecmp(option, "mv") == 0) { + if (!is_bitburner(avalon)) { + sprintf(replybuf, "%s cannot set millivolts", avalon->drv->name); + return replybuf; + } + + if (!setting || !*setting) { + sprintf(replybuf, "missing millivolts setting"); + return replybuf; + } + + val = atoi(setting); + if (val < BITBURNER_MIN_COREMV || val > BITBURNER_MAX_COREMV) { + sprintf(replybuf, "invalid millivolts: '%s' valid range %d-%d", + setting, BITBURNER_MIN_COREMV, BITBURNER_MAX_COREMV); + return replybuf; + } + + if (bitburner_set_core_voltage(avalon, val)) + return NULL; + else { + sprintf(replybuf, "Set millivolts failed"); + return replybuf; + } + } + + if (strcasecmp(option, "freq") == 0) { + if (!setting || !*setting) { + sprintf(replybuf, "missing freq setting"); + return replybuf; + } + + val = atoi(setting); + if (val < AVALON_MIN_FREQUENCY || val > AVALON_MAX_FREQUENCY) { + sprintf(replybuf, "invalid freq: '%s' valid range %d-%d", + setting, AVALON_MIN_FREQUENCY, AVALON_MAX_FREQUENCY); + return replybuf; + } + + avalon_set_freq(avalon, val); + return NULL; + } + + sprintf(replybuf, "Unknown option: %s", option); + return replybuf; +} + +struct device_drv avalon_drv = { + .drv_id = DRIVER_avalon, + .dname = "avalon", + .name = "AVA", + .drv_detect = avalon_detect, + .thread_prepare = avalon_prepare, + .hash_work = hash_queued_work, + .queue_full = avalon_fill, + .scanwork = avalon_scanhash, + .flush_work = avalon_flush_work, + .get_api_stats = avalon_api_stats, + .get_statline_before = get_avalon_statline_before, + .set_device = avalon_set_device, + .reinit_device = avalon_init, + .thread_shutdown = avalon_shutdown, +}; diff --git a/driver-avalon.h b/driver-avalon.h new file mode 100644 index 0000000..c718366 --- /dev/null +++ b/driver-avalon.h @@ -0,0 +1,207 @@ +/* + * Copyright 2013 Avalon project + * Copyright 2013-2014 Con Kolivas + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#ifndef AVALON_H +#define AVALON_H + +#ifdef USE_AVALON + +#include "util.h" + +#define AVALON_RESET_FAULT_DECISECONDS 1 +#define AVALON_MINER_THREADS 1 + +#define AVALON_IO_SPEED 115200 +#define AVALON_HASH_TIME_FACTOR ((float)1.67/0x32) +#define AVALON_RESET_PITCH (300*1000*1000) + + +#define AVALON_A3256 110 +#define AVALON_A3255 55 + +#define AVALON_FAN_FACTOR 120 +#define AVALON_PWM_MAX 0xA0 +#define AVALON_DEFAULT_FAN_MIN 20 +#define AVALON_DEFAULT_FAN_MAX 100 +#define AVALON_DEFAULT_FAN_MAX_PWM 0xA0 /* 100% */ +#define AVALON_DEFAULT_FAN_MIN_PWM 0x20 /* 20% */ + +#define AVALON_TEMP_TARGET 50 +#define AVALON_TEMP_HYSTERESIS 3 +#define AVALON_TEMP_OVERHEAT 60 + +/* Avalon-based BitBurner. */ +#define BITBURNER_DEFAULT_CORE_VOLTAGE 1200 /* in millivolts */ +#define BITBURNER_MIN_COREMV 1000 +/* change here if you want to risk killing it :) */ +#define BITBURNER_MAX_COREMV 1400 + +/* BitFury-based BitBurner. */ +#define BITBURNER_FURY_DEFAULT_CORE_VOLTAGE 900 /* in millivolts */ +#define BITBURNER_FURY_MIN_COREMV 700 +/* change here if you want to risk killing it :) */ +#define BITBURNER_FURY_MAX_COREMV 1100 + + +#define AVALON_DEFAULT_TIMEOUT 0x2D +#define AVALON_MIN_FREQUENCY 256 +#define AVALON_MAX_FREQUENCY 2000 +#define AVALON_TIMEOUT_FACTOR 12690 +#define AVALON_DEFAULT_FREQUENCY 282 +#define AVALON_DEFAULT_MINER_NUM 0x20 +#define AVALON_MAX_MINER_NUM 0x100 +#define AVALON_DEFAULT_ASIC_NUM 0xA + +/* Default number of miners for Bitburner Fury is for a stack of 8 boards, + but it will work acceptably for smaller stacks, too */ +#define BITBURNER_FURY_DEFAULT_MINER_NUM 128 +#define BITBURNER_FURY_DEFAULT_FREQUENCY 256 +#define BITBURNER_FURY_DEFAULT_TIMEOUT 50 + +#define AVALON_AUTO_CYCLE 1024 + +#define AVALON_FTDI_READSIZE 510 +#define AVALON_READBUF_SIZE 8192 +/* Set latency to just less than full 64 byte packet size at 115200 baud */ +#define AVALON_LATENCY 4 + +struct avalon_task { + uint8_t reset :1; + uint8_t flush_fifo :1; + uint8_t fan_eft :1; + uint8_t timer_eft :1; + uint8_t asic_num :4; + uint8_t fan_pwm_data; + uint8_t timeout_data; + uint8_t miner_num; + + uint8_t nonce_elf :1; + uint8_t gate_miner_elf :1; + uint8_t asic_pll :1; + uint8_t gate_miner :1; + uint8_t _pad0 :4; + uint8_t _pad1[3]; + uint32_t _pad2; + + uint8_t midstate[32]; + uint8_t data[12]; +} __attribute__((packed, aligned(4))); + +struct avalon_result { + uint32_t nonce; + uint8_t data[12]; + uint8_t midstate[32]; + + uint8_t fan0; + uint8_t fan1; + uint8_t fan2; + uint8_t temp0; + uint8_t temp1; + uint8_t temp2; + uint8_t _pad0[2]; + + uint16_t fifo_wp; + uint16_t fifo_rp; + uint8_t chip_num; + uint8_t pwm_data; + uint8_t timeout; + uint8_t miner_num; +} __attribute__((packed, aligned(4))); + +struct avalon_info { + int baud; + int miner_count; + int asic_count; + int timeout; + + int fan0; + int fan1; + int fan2; + + int temp0; + int temp1; + int temp2; + int temp_history_count; + int temp_history_index; + int temp_sum; + int temp_old; + int fan_pwm; + + int core_voltage; + + int no_matching_work; + int matching_work[AVALON_MAX_MINER_NUM]; + + int frequency; + uint32_t asic; + uint32_t ctlr_ver; + + struct thr_info *thr; + pthread_t read_thr; + pthread_t write_thr; + pthread_mutex_t lock; + pthread_mutex_t qlock; + cgsem_t qsem; + cgtimer_t cgsent; + int send_delay; + + int nonces; + int auto_queued; + int auto_nonces; + int auto_hw; + int increment; + int decrement; + + int idle; + bool reset; + bool overheat; + bool optimal; + + uint8_t version1; + uint8_t version2; + uint8_t version3; +}; + +#define BITBURNER_VERSION1 1 +#define BITBURNER_VERSION2 0 +#define BITBURNER_VERSION3 0 + +#define AVALON_WRITE_SIZE (sizeof(struct avalon_task)) +#define AVALON_READ_SIZE (sizeof(struct avalon_result)) +#define AVALON_ARRAY_SIZE 3 +#define BITBURNER_ARRAY_SIZE 4 + +#define AVA_GETS_ERROR -1 +#define AVA_GETS_OK 0 + +#define AVA_SEND_ERROR -1 +#define AVA_SEND_OK 0 + +#define avalon_buffer_full(avalon) !usb_ftdi_cts(avalon) + +#define AVALON_READ_TIME(baud) ((double)AVALON_READ_SIZE * (double)8.0 / (double)(baud)) +#define ASSERT1(condition) __maybe_unused static char sizeof_uint32_t_must_be_4[(condition)?1:-1] +ASSERT1(sizeof(uint32_t) == 4); + +extern struct avalon_info **avalon_info; +extern int opt_avalon_temp; +extern int opt_avalon_overheat; +extern int opt_avalon_fan_min; +extern int opt_avalon_fan_max; +extern int opt_avalon_freq_min; +extern int opt_avalon_freq_max; +extern bool opt_avalon_auto; +extern int opt_bitburner_core_voltage; +extern int opt_bitburner_fury_core_voltage; +extern char *set_avalon_fan(char *arg); +extern char *set_avalon_freq(char *arg); + +#endif /* USE_AVALON */ +#endif /* AVALON_H */ diff --git a/driver-avalon2.c b/driver-avalon2.c new file mode 100644 index 0000000..2ce216d --- /dev/null +++ b/driver-avalon2.c @@ -0,0 +1,1097 @@ +/* + * Copyright 2013-2014 Con Kolivas + * Copyright 2012-2014 Xiangfu + * Copyright 2012 Luke Dashjr + * Copyright 2012 Andrew Smith + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#ifndef WIN32 + #include + #include + #include + #ifndef O_CLOEXEC + #define O_CLOEXEC 0 + #endif +#else + #include + #include +#endif + +#include "elist.h" +#include "miner.h" +#include "fpgautils.h" +#include "driver-avalon2.h" +#include "crc.h" +#include "sha2.h" + +#define ASSERT1(condition) __maybe_unused static char sizeof_uint32_t_must_be_4[(condition)?1:-1] +ASSERT1(sizeof(uint32_t) == 4); + +#define get_fan_pwm(v) (AVA2_PWM_MAX - (v) * AVA2_PWM_MAX / 100) + +int opt_avalon2_freq_min; +int opt_avalon2_freq_max; + +int opt_avalon2_fan_min = AVA2_DEFAULT_FAN_MIN; +int opt_avalon2_fan_max = AVA2_DEFAULT_FAN_MAX; +static int avalon2_fan_min = get_fan_pwm(AVA2_DEFAULT_FAN_MIN); +static int avalon2_fan_max = get_fan_pwm(AVA2_DEFAULT_FAN_MAX); + +int opt_avalon2_voltage_min; +int opt_avalon2_voltage_max; + +int opt_avalon2_overheat = AVALON2_TEMP_OVERHEAT; +int opt_avalon2_polling_delay = AVALON2_DEFAULT_POLLING_DELAY; + +enum avalon2_fan_fixed opt_avalon2_fan_fixed = FAN_AUTO; + +#define UNPACK32(x, str) \ +{ \ + *((str) + 3) = (uint8_t) ((x) ); \ + *((str) + 2) = (uint8_t) ((x) >> 8); \ + *((str) + 1) = (uint8_t) ((x) >> 16); \ + *((str) + 0) = (uint8_t) ((x) >> 24); \ +} + +static void sha256_prehash(const unsigned char *message, unsigned int len, unsigned char *digest) +{ + sha256_ctx ctx; + int i; + sha256_init(&ctx); + sha256_update(&ctx, message, len); + + for (i = 0; i < 8; i++) { + UNPACK32(ctx.h[i], &digest[i << 2]); + } +} + +static inline uint8_t rev8(uint8_t d) +{ + int i; + uint8_t out = 0; + + /* (from left to right) */ + for (i = 0; i < 8; i++) + if (d & (1 << i)) + out |= (1 << (7 - i)); + + return out; +} + +char *set_avalon2_fan(char *arg) +{ + int val1, val2, ret; + + ret = sscanf(arg, "%d-%d", &val1, &val2); + if (ret < 1) + return "No values passed to avalon2-fan"; + if (ret == 1) + val2 = val1; + + if (val1 < 0 || val1 > 100 || val2 < 0 || val2 > 100 || val2 < val1) + return "Invalid value passed to avalon2-fan"; + + opt_avalon2_fan_min = val1; + opt_avalon2_fan_max = val2; + avalon2_fan_min = get_fan_pwm(val1); + avalon2_fan_max = get_fan_pwm(val2); + + return NULL; +} + +char *set_avalon2_fixed_speed(enum avalon2_fan_fixed *f) +{ + *f = FAN_FIXED; + return NULL; +} + +char *set_avalon2_freq(char *arg) +{ + int val1, val2, ret; + + ret = sscanf(arg, "%d-%d", &val1, &val2); + if (ret < 1) + return "No values passed to avalon2-freq"; + if (ret == 1) + val2 = val1; + + if (val1 < AVA2_DEFAULT_FREQUENCY_MIN || val1 > AVA2_DEFAULT_FREQUENCY_MAX || + val2 < AVA2_DEFAULT_FREQUENCY_MIN || val2 > AVA2_DEFAULT_FREQUENCY_MAX || + val2 < val1) + return "Invalid value passed to avalon2-freq"; + + opt_avalon2_freq_min = val1; + opt_avalon2_freq_max = val2; + + return NULL; +} + +char *set_avalon2_voltage(char *arg) +{ + int val1, val2, ret; + + ret = sscanf(arg, "%d-%d", &val1, &val2); + if (ret < 1) + return "No values passed to avalon2-voltage"; + if (ret == 1) + val2 = val1; + + if (val1 < AVA2_DEFAULT_VOLTAGE_MIN || val1 > AVA2_DEFAULT_VOLTAGE_MAX || + val2 < AVA2_DEFAULT_VOLTAGE_MIN || val2 > AVA2_DEFAULT_VOLTAGE_MAX || + val2 < val1) + return "Invalid value passed to avalon2-voltage"; + + opt_avalon2_voltage_min = val1; + opt_avalon2_voltage_max = val2; + + return NULL; +} + +static int avalon2_init_pkg(struct avalon2_pkg *pkg, uint8_t type, uint8_t idx, uint8_t cnt) +{ + unsigned short crc; + + pkg->head[0] = AVA2_H1; + pkg->head[1] = AVA2_H2; + + pkg->type = type; + pkg->idx = idx; + pkg->cnt = cnt; + + crc = crc16(pkg->data, AVA2_P_DATA_LEN); + + pkg->crc[0] = (crc & 0xff00) >> 8; + pkg->crc[1] = crc & 0x00ff; + return 0; +} + +static int job_idcmp(uint8_t *job_id, char *pool_job_id) +{ + int job_id_len; + unsigned short crc, crc_expect; + + if (!pool_job_id) + return 1; + + job_id_len = strlen(pool_job_id); + crc_expect = crc16((unsigned char *)pool_job_id, job_id_len); + + crc = job_id[0] << 8 | job_id[1]; + + if (crc_expect == crc) + return 0; + + applog(LOG_DEBUG, "Avalon2: job_id not match! [%04x:%04x (%s)]", + crc, crc_expect, pool_job_id); + + return 1; +} + +static inline int get_temp_max(struct avalon2_info *info) +{ + int i; + for (i = 0; i < 2 * AVA2_DEFAULT_MODULARS; i++) { + if (info->temp_max <= info->temp[i]) + info->temp_max = info->temp[i]; + } + return info->temp_max; +} + +static inline int get_current_temp_max(struct avalon2_info *info) +{ + int i; + int t = info->temp[0]; + + for (i = 1; i < 2 * AVA2_DEFAULT_MODULARS; i++) { + if (info->temp[i] > t) + t = info->temp[i]; + } + return t; +} + +/* http://www.onsemi.com/pub_link/Collateral/ADP3208D.PDF */ +static inline uint32_t encode_voltage(uint32_t v) +{ + return rev8((0x78 - v / 125) << 1 | 1) << 8; +} + +static inline uint32_t decode_voltage(uint32_t v) +{ + return (0x78 - (rev8(v >> 8) >> 1)) * 125; +} + +static void adjust_fan(struct avalon2_info *info) +{ + int t; + + if (opt_avalon2_fan_fixed == FAN_FIXED) { + info->fan_pct = opt_avalon2_fan_min; + info->fan_pwm = get_fan_pwm(info->fan_pct); + return; + } + + t = get_current_temp_max(info); + + /* TODO: Add options for temperature range and fan adjust function */ + if (t < 60) + info->fan_pct = opt_avalon2_fan_min; + else if (t > 80) + info->fan_pct = opt_avalon2_fan_max; + else + info->fan_pct = (t - 60) * (opt_avalon2_fan_max - opt_avalon2_fan_min) / 20 + opt_avalon2_fan_min; + + info->fan_pwm = get_fan_pwm(info->fan_pct); +} + +static inline int mm_cmp_1404(struct avalon2_info *info, int modular) +{ + /* <= 1404 return 1 */ + char *mm_1404 = "1404"; + return strncmp(info->mm_version[modular] + 2, mm_1404, 4) > 0 ? 0 : 1; +} + +static inline int mm_cmp_1406(struct avalon2_info *info) +{ + /* <= 1406 return 1 */ + char *mm_1406 = "1406"; + int i; + for (i = 0; i < AVA2_DEFAULT_MODULARS; i++) { + if (info->enable[i] && + strncmp(info->mm_version[i] + 2, mm_1406, 4) <= 0) + return 1; + } + + return 0; +} + +static int decode_pkg(struct thr_info *thr, struct avalon2_ret *ar, uint8_t *pkg) +{ + struct cgpu_info *avalon2 = thr->cgpu; + struct avalon2_info *info = avalon2->device_data; + struct pool *pool, *real_pool, *pool_stratum = &info->pool; + + unsigned int expected_crc; + unsigned int actual_crc; + uint32_t nonce, nonce2, miner, modular_id; + int pool_no; + uint8_t job_id[4]; + int tmp; + + int type = AVA2_GETS_ERROR; + + memcpy((uint8_t *)ar, pkg, AVA2_READ_SIZE); + + if (ar->head[0] == AVA2_H1 && ar->head[1] == AVA2_H2) { + expected_crc = crc16(ar->data, AVA2_P_DATA_LEN); + actual_crc = (ar->crc[0] & 0xff) | + ((ar->crc[1] & 0xff) << 8); + + type = ar->type; + applog(LOG_DEBUG, "Avalon2: %d: expected crc(%04x), actual_crc(%04x)", + type, expected_crc, actual_crc); + if (expected_crc != actual_crc) + goto out; + + memcpy(&modular_id, ar->data + 28, 4); + modular_id = be32toh(modular_id); + if (modular_id > 3) + modular_id = 0; + + switch(type) { + case AVA2_P_NONCE: + applog(LOG_DEBUG, "Avalon2: AVA2_P_NONCE"); + memcpy(&miner, ar->data + 0, 4); + memcpy(&pool_no, ar->data + 4, 4); + memcpy(&nonce2, ar->data + 8, 4); + /* Calc time ar->data + 12 */ + memcpy(&nonce, ar->data + 16, 4); + memcpy(job_id, ar->data + 20, 4); + + miner = be32toh(miner); + pool_no = be32toh(pool_no); + if (miner >= AVA2_DEFAULT_MINERS || + modular_id >= AVA2_DEFAULT_MINERS || + pool_no >= total_pools || + pool_no < 0) { + applog(LOG_DEBUG, "Avalon2: Wrong miner/pool/id no %d,%d,%d", miner, pool_no, modular_id); + break; + } else + info->matching_work[modular_id * AVA2_DEFAULT_MINERS + miner]++; + nonce2 = be32toh(nonce2); + nonce = be32toh(nonce); + nonce -= 0x180; + + applog(LOG_DEBUG, "Avalon2: Found! %d: (%08x) (%08x)", + pool_no, nonce2, nonce); + + real_pool = pool = pools[pool_no]; + if (job_idcmp(job_id, pool->swork.job_id)) { + if (!job_idcmp(job_id, pool_stratum->swork.job_id)) { + applog(LOG_DEBUG, "Avalon2: Match to previous stratum! (%s)", pool_stratum->swork.job_id); + pool = pool_stratum; + } else { + applog(LOG_ERR, "Avalon2: Cannot match to any stratum! (%s)", pool->swork.job_id); + break; + } + } + + if (submit_nonce2_nonce(thr, pool, real_pool, nonce2, nonce, 0)) + info->failing = false; + break; + case AVA2_P_STATUS: + applog(LOG_DEBUG, "Avalon2: AVA2_P_STATUS"); + memcpy(&tmp, ar->data, 4); + tmp = be32toh(tmp); + info->temp[0 + modular_id * 2] = tmp >> 16; + info->temp[1 + modular_id * 2] = tmp & 0xffff; + + memcpy(&tmp, ar->data + 4, 4); + tmp = be32toh(tmp); + info->fan[0 + modular_id * 2] = tmp >> 16; + info->fan[1 + modular_id * 2] = tmp & 0xffff; + + memcpy(&(info->get_frequency[modular_id]), ar->data + 8, 4); + memcpy(&(info->get_voltage[modular_id]), ar->data + 12, 4); + memcpy(&(info->local_work[modular_id]), ar->data + 16, 4); + memcpy(&(info->hw_work[modular_id]), ar->data + 20, 4); + memcpy(&(info->power_good[modular_id]), ar->data + 24, 4); + + info->get_frequency[modular_id] = be32toh(info->get_frequency[modular_id]); + if (info->dev_type[modular_id] == AVA2_ID_AVA3) + info->get_frequency[modular_id] = info->get_frequency[modular_id] * 768 / 65; + info->get_voltage[modular_id] = be32toh(info->get_voltage[modular_id]); + info->local_work[modular_id] = be32toh(info->local_work[modular_id]); + info->hw_work[modular_id] = be32toh(info->hw_work[modular_id]); + + info->local_works[modular_id] += info->local_work[modular_id]; + info->hw_works[modular_id] += info->hw_work[modular_id]; + + info->get_voltage[modular_id] = decode_voltage(info->get_voltage[modular_id]); + info->power_good[modular_id] = info->power_good[modular_id] >> 24; + + avalon2->temp = get_temp_max(info); + break; + case AVA2_P_ACKDETECT: + applog(LOG_DEBUG, "Avalon2: AVA2_P_ACKDETECT"); + break; + case AVA2_P_ACK: + applog(LOG_DEBUG, "Avalon2: AVA2_P_ACK"); + break; + case AVA2_P_NAK: + applog(LOG_DEBUG, "Avalon2: AVA2_P_NAK"); + break; + default: + applog(LOG_DEBUG, "Avalon2: Unknown response"); + type = AVA2_GETS_ERROR; + break; + } + } + +out: + return type; +} + +static inline int avalon2_gets(struct cgpu_info *avalon2, uint8_t *buf) +{ + int read_amount = AVA2_READ_SIZE, ret = 0; + uint8_t *buf_back = buf; + + while (true) { + int err; + + do { + memset(buf, 0, read_amount); + err = usb_read(avalon2, (char *)buf, read_amount, &ret, C_AVA2_READ); + if (unlikely(err && err != LIBUSB_ERROR_TIMEOUT)) { + applog(LOG_ERR, "Avalon2: Error %d on read in avalon_gets got %d", err, ret); + return AVA2_GETS_ERROR; + } + if (likely(ret >= read_amount)) { + if (unlikely(buf_back[0] != AVA2_H1 || buf_back[1] != AVA2_H2)) + return AVA2_GETS_ERROR; + return AVA2_GETS_OK; + } + buf += ret; + read_amount -= ret; + } while (ret > 0); + + return AVA2_GETS_TIMEOUT; + } +} + +static int avalon2_send_pkg(struct cgpu_info *avalon2, const struct avalon2_pkg *pkg) +{ + int err, amount; + uint8_t buf[AVA2_WRITE_SIZE]; + int nr_len = AVA2_WRITE_SIZE; + + if (unlikely(avalon2->usbinfo.nodev)) + return AVA2_SEND_ERROR; + + memcpy(buf, pkg, AVA2_WRITE_SIZE); + err = usb_write(avalon2, (char *)buf, nr_len, &amount, C_AVA2_WRITE); + if (err || amount != nr_len) { + applog(LOG_DEBUG, "Avalon2: Send(%d)!", amount); + usb_nodev(avalon2); + return AVA2_SEND_ERROR; + } + + return AVA2_SEND_OK; +} + +static void avalon2_stratum_pkgs(struct cgpu_info *avalon2, struct pool *pool) +{ + const int merkle_offset = 36; + struct avalon2_pkg pkg; + int i, a, b, tmp; + unsigned char target[32]; + int job_id_len, n2size; + unsigned short crc; + int diff; + + /* Cap maximum diff in order to still get shares */ + diff = pool->swork.diff; + if (diff > 64) + diff = 64; + else if (unlikely(diff < 1)) + diff = 1; + + /* Send out the first stratum message STATIC */ + applog(LOG_DEBUG, "Avalon2: Pool stratum message STATIC: %d, %d, %d, %d, %d", + pool->coinbase_len, + pool->nonce2_offset, + pool->n2size, + merkle_offset, + pool->merkles); + memset(pkg.data, 0, AVA2_P_DATA_LEN); + tmp = be32toh(pool->coinbase_len); + memcpy(pkg.data, &tmp, 4); + + tmp = be32toh(pool->nonce2_offset); + memcpy(pkg.data + 4, &tmp, 4); + + n2size = pool->n2size >= 4 ? 4 : pool->n2size; + tmp = be32toh(n2size); + memcpy(pkg.data + 8, &tmp, 4); + + tmp = be32toh(merkle_offset); + memcpy(pkg.data + 12, &tmp, 4); + + tmp = be32toh(pool->merkles); + memcpy(pkg.data + 16, &tmp, 4); + + tmp = be32toh(diff); + memcpy(pkg.data + 20, &tmp, 4); + + tmp = be32toh((int)pool->pool_no); + memcpy(pkg.data + 24, &tmp, 4); + + avalon2_init_pkg(&pkg, AVA2_P_STATIC, 1, 1); + if (avalon2_send_pkg(avalon2, &pkg)) + return; + + set_target(target, pool->sdiff); + memcpy(pkg.data, target, 32); + if (opt_debug) { + char *target_str; + target_str = bin2hex(target, 32); + applog(LOG_DEBUG, "Avalon2: Pool stratum target: %s", target_str); + free(target_str); + } + avalon2_init_pkg(&pkg, AVA2_P_TARGET, 1, 1); + if (avalon2_send_pkg(avalon2, &pkg)) + return; + + applog(LOG_DEBUG, "Avalon2: Pool stratum message JOBS_ID: %s", + pool->swork.job_id); + memset(pkg.data, 0, AVA2_P_DATA_LEN); + + job_id_len = strlen(pool->swork.job_id); + crc = crc16((unsigned char *)pool->swork.job_id, job_id_len); + pkg.data[0] = (crc & 0xff00) >> 8; + pkg.data[1] = crc & 0x00ff; + avalon2_init_pkg(&pkg, AVA2_P_JOB_ID, 1, 1); + if (avalon2_send_pkg(avalon2, &pkg)) + return; + + if (pool->coinbase_len > AVA2_P_COINBASE_SIZE) { + int coinbase_len_posthash, coinbase_len_prehash; + uint8_t coinbase_prehash[32]; + coinbase_len_prehash = pool->nonce2_offset - (pool->nonce2_offset % SHA256_BLOCK_SIZE); + coinbase_len_posthash = pool->coinbase_len - coinbase_len_prehash; + sha256_prehash(pool->coinbase, coinbase_len_prehash, coinbase_prehash); + + a = (coinbase_len_posthash / AVA2_P_DATA_LEN) + 1; + b = coinbase_len_posthash % AVA2_P_DATA_LEN; + memcpy(pkg.data, coinbase_prehash, 32); + avalon2_init_pkg(&pkg, AVA2_P_COINBASE, 1, a + (b ? 1 : 0)); + if (avalon2_send_pkg(avalon2, &pkg)) + return; + applog(LOG_DEBUG, "Avalon2: Pool stratum message modified COINBASE: %d %d", a, b); + for (i = 1; i < a; i++) { + memcpy(pkg.data, pool->coinbase + coinbase_len_prehash + i * 32 - 32, 32); + avalon2_init_pkg(&pkg, AVA2_P_COINBASE, i + 1, a + (b ? 1 : 0)); + if (avalon2_send_pkg(avalon2, &pkg)) + return; + } + if (b) { + memset(pkg.data, 0, AVA2_P_DATA_LEN); + memcpy(pkg.data, pool->coinbase + coinbase_len_prehash + i * 32 - 32, b); + avalon2_init_pkg(&pkg, AVA2_P_COINBASE, i + 1, i + 1); + if (avalon2_send_pkg(avalon2, &pkg)) + return; + } + } else { + a = pool->coinbase_len / AVA2_P_DATA_LEN; + b = pool->coinbase_len % AVA2_P_DATA_LEN; + applog(LOG_DEBUG, "Avalon2: Pool stratum message COINBASE: %d %d", a, b); + for (i = 0; i < a; i++) { + memcpy(pkg.data, pool->coinbase + i * 32, 32); + avalon2_init_pkg(&pkg, AVA2_P_COINBASE, i + 1, a + (b ? 1 : 0)); + if (avalon2_send_pkg(avalon2, &pkg)) + return; + } + if (b) { + memset(pkg.data, 0, AVA2_P_DATA_LEN); + memcpy(pkg.data, pool->coinbase + i * 32, b); + avalon2_init_pkg(&pkg, AVA2_P_COINBASE, i + 1, i + 1); + if (avalon2_send_pkg(avalon2, &pkg)) + return; + } + } + + + b = pool->merkles; + applog(LOG_DEBUG, "Avalon2: Pool stratum message MERKLES: %d", b); + for (i = 0; i < b; i++) { + memset(pkg.data, 0, AVA2_P_DATA_LEN); + memcpy(pkg.data, pool->swork.merkle_bin[i], 32); + avalon2_init_pkg(&pkg, AVA2_P_MERKLES, i + 1, b); + if (avalon2_send_pkg(avalon2, &pkg)) + return; + } + + applog(LOG_DEBUG, "Avalon2: Pool stratum message HEADER: 4"); + for (i = 0; i < 4; i++) { + memset(pkg.data, 0, AVA2_P_HEADER); + memcpy(pkg.data, pool->header_bin + i * 32, 32); + avalon2_init_pkg(&pkg, AVA2_P_HEADER, i + 1, 4); + if (avalon2_send_pkg(avalon2, &pkg)) + return; + } +} + +static void avalon2_initialise(struct cgpu_info *avalon2) +{ + uint32_t ava2_data[2] = { PL2303_VALUE_LINE0, PL2303_VALUE_LINE1 }; + int interface; + + if (avalon2->usbinfo.nodev) + return; + + interface = usb_interface(avalon2); + // Set Data Control + usb_transfer(avalon2, PL2303_VENDOR_OUT, PL2303_REQUEST_VENDOR, 8, + interface, C_VENDOR); + if (avalon2->usbinfo.nodev) + return; + + usb_transfer(avalon2, PL2303_VENDOR_OUT, PL2303_REQUEST_VENDOR, 9, + interface, C_VENDOR); + + if (avalon2->usbinfo.nodev) + return; + + // Set Line Control + usb_transfer_data(avalon2, PL2303_CTRL_OUT, PL2303_REQUEST_LINE, PL2303_VALUE_LINE, + interface, ava2_data, PL2303_VALUE_LINE_SIZE, C_SETLINE); + if (avalon2->usbinfo.nodev) + return; + + // Vendor + usb_transfer(avalon2, PL2303_VENDOR_OUT, PL2303_REQUEST_VENDOR, PL2303_VALUE_VENDOR, + interface, C_VENDOR); + + if (avalon2->usbinfo.nodev) + return; + + // Set More Line Control ? + usb_transfer(avalon2, PL2303_CTRL_OUT, PL2303_REQUEST_CTRL, 3, interface, C_SETLINE); +} + +static struct cgpu_info *avalon2_detect_one(struct libusb_device *dev, struct usb_find_devices *found) +{ + struct avalon2_info *info; + int ackdetect; + int err, amount; + int tmp, i, j, modular[AVA2_DEFAULT_MODULARS] = {}; + char mm_version[AVA2_DEFAULT_MODULARS][16]; + + struct cgpu_info *avalon2 = usb_alloc_cgpu(&avalon2_drv, 1); + struct avalon2_pkg detect_pkg; + struct avalon2_ret ret_pkg; + + if (!usb_init(avalon2, dev, found)) { + applog(LOG_ERR, "Avalon2 failed usb_init"); + avalon2 = usb_free_cgpu(avalon2); + return NULL; + } + avalon2_initialise(avalon2); + + for (j = 0; j < 2; j++) { + for (i = 0; i < AVA2_DEFAULT_MODULARS; i++) { + strcpy(mm_version[i], AVA2_MM_VERNULL); + /* Send out detect pkg */ + memset(detect_pkg.data, 0, AVA2_P_DATA_LEN); + tmp = be32toh(i); + memcpy(detect_pkg.data + 28, &tmp, 4); + + avalon2_init_pkg(&detect_pkg, AVA2_P_DETECT, 1, 1); + avalon2_send_pkg(avalon2, &detect_pkg); + err = usb_read(avalon2, (char *)&ret_pkg, AVA2_READ_SIZE, &amount, C_AVA2_READ); + if (err < 0 || amount != AVA2_READ_SIZE) { + applog(LOG_DEBUG, "%s %d: Avalon2 failed usb_read with err %d amount %d", + avalon2->drv->name, avalon2->device_id, err, amount); + continue; + } + ackdetect = ret_pkg.type; + applog(LOG_DEBUG, "Avalon2 Detect ID[%d]: %d", i, ackdetect); + if (ackdetect != AVA2_P_ACKDETECT && modular[i] == 0) + continue; + modular[i] = 1; + memcpy(mm_version[i], ret_pkg.data, 15); + mm_version[i][15] = '\0'; + } + } + if (!modular[0] && !modular[1] && !modular[2] && !modular[3]) { + applog(LOG_DEBUG, "Not an Avalon2 device"); + usb_uninit(avalon2); + usb_free_cgpu(avalon2); + return NULL; + } + + /* We have a real Avalon! */ + avalon2->threads = AVA2_MINER_THREADS; + add_cgpu(avalon2); + + update_usb_stats(avalon2); + + applog(LOG_INFO, "%s %d: Found at %s", avalon2->drv->name, avalon2->device_id, + avalon2->device_path); + + avalon2->device_data = calloc(sizeof(struct avalon2_info), 1); + if (unlikely(!(avalon2->device_data))) + quit(1, "Failed to calloc avalon2_info"); + + info = avalon2->device_data; + + info->fan_pwm = get_fan_pwm(AVA2_DEFAULT_FAN_PWM); + info->temp_max = 0; + + for (i = 0; i < AVA2_DEFAULT_MODULARS; i++) { + strcpy(info->mm_version[i], mm_version[i]); + info->modulars[i] = modular[i]; /* Enable modular */ + info->enable[i] = modular[i]; + info->dev_type[i] = AVA2_ID_AVAX; + + if (!strncmp((char *)&(info->mm_version[i]), AVA2_FW2_PREFIXSTR, 2)) { + info->dev_type[i] = AVA2_ID_AVA2; + info->set_voltage = AVA2_DEFAULT_VOLTAGE_MIN; + info->set_frequency = AVA2_DEFAULT_FREQUENCY; + } + if (!strncmp((char *)&(info->mm_version[i]), AVA2_FW3_PREFIXSTR, 2)) { + info->dev_type[i] = AVA2_ID_AVA3; + info->set_voltage = AVA2_AVA3_VOLTAGE; + info->set_frequency = AVA2_AVA3_FREQUENCY; + } + } + + if (!opt_avalon2_voltage_min) + opt_avalon2_voltage_min = opt_avalon2_voltage_max = info->set_voltage; + if (!opt_avalon2_freq_min) + opt_avalon2_freq_min = opt_avalon2_freq_max = info->set_frequency; + + return avalon2; +} + +static inline void avalon2_detect(bool __maybe_unused hotplug) +{ + usb_detect(&avalon2_drv, avalon2_detect_one); +} + +static bool avalon2_prepare(struct thr_info *thr) +{ + struct cgpu_info *avalon2 = thr->cgpu; + struct avalon2_info *info = avalon2->device_data; + + cglock_init(&info->pool.data_lock); + + return true; +} + +static int polling(struct thr_info *thr, struct cgpu_info *avalon2, struct avalon2_info *info) +{ + struct avalon2_pkg send_pkg; + struct avalon2_ret ar; + int i, tmp; + + for (i = 0; i < AVA2_DEFAULT_MODULARS; i++) { + if (info->modulars[i] && info->enable[i]) { + uint8_t result[AVA2_READ_SIZE]; + int ret; + + cgsleep_ms(opt_avalon2_polling_delay); + memset(send_pkg.data, 0, AVA2_P_DATA_LEN); + + tmp = be32toh(info->led_red[i]); /* RED LED */ + memcpy(send_pkg.data + 12, &tmp, 4); + + tmp = be32toh(i); /* ID */ + memcpy(send_pkg.data + 28, &tmp, 4); + if (info->led_red[i] && mm_cmp_1404(info, i)) { + avalon2_init_pkg(&send_pkg, AVA2_P_TEST, 1, 1); + avalon2_send_pkg(avalon2, &send_pkg); + info->enable[i] = 0; + continue; + } else + avalon2_init_pkg(&send_pkg, AVA2_P_POLLING, 1, 1); + + avalon2_send_pkg(avalon2, &send_pkg); + ret = avalon2_gets(avalon2, result); + if (ret == AVA2_GETS_OK) + decode_pkg(thr, &ar, result); + } + } + + return 0; +} + +static void copy_pool_stratum(struct avalon2_info *info, struct pool *pool) +{ + int i; + int merkles = pool->merkles; + size_t coinbase_len = pool->coinbase_len; + struct pool *pool_stratum = &info->pool; + + if (!job_idcmp((unsigned char *)pool->swork.job_id, pool_stratum->swork.job_id)) + return; + + cg_wlock(&pool_stratum->data_lock); + free(pool_stratum->swork.job_id); + free(pool_stratum->nonce1); + free(pool_stratum->coinbase); + + align_len(&coinbase_len); + pool_stratum->coinbase = calloc(coinbase_len, 1); + if (unlikely(!pool_stratum->coinbase)) + quit(1, "Failed to calloc pool_stratum coinbase in avalon2"); + memcpy(pool_stratum->coinbase, pool->coinbase, coinbase_len); + + + for (i = 0; i < pool_stratum->merkles; i++) + free(pool_stratum->swork.merkle_bin[i]); + if (merkles) { + pool_stratum->swork.merkle_bin = realloc(pool_stratum->swork.merkle_bin, + sizeof(char *) * merkles + 1); + for (i = 0; i < merkles; i++) { + pool_stratum->swork.merkle_bin[i] = malloc(32); + if (unlikely(!pool_stratum->swork.merkle_bin[i])) + quit(1, "Failed to malloc pool_stratum swork merkle_bin"); + memcpy(pool_stratum->swork.merkle_bin[i], pool->swork.merkle_bin[i], 32); + } + } + + pool_stratum->sdiff = pool->sdiff; + pool_stratum->coinbase_len = pool->coinbase_len; + pool_stratum->nonce2_offset = pool->nonce2_offset; + pool_stratum->n2size = pool->n2size; + pool_stratum->merkles = pool->merkles; + + pool_stratum->swork.job_id = strdup(pool->swork.job_id); + pool_stratum->nonce1 = strdup(pool->nonce1); + + memcpy(pool_stratum->ntime, pool->ntime, sizeof(pool_stratum->ntime)); + memcpy(pool_stratum->header_bin, pool->header_bin, sizeof(pool_stratum->header_bin)); + cg_wunlock(&pool_stratum->data_lock); +} + +static void avalon2_update(struct cgpu_info *avalon2) +{ + struct avalon2_info *info = avalon2->device_data; + struct thr_info *thr = avalon2->thr[0]; + struct avalon2_pkg send_pkg; + uint32_t tmp, range, start; + struct work *work; + struct pool *pool; + + applog(LOG_DEBUG, "Avalon2: New stratum: restart: %d, update: %d", + thr->work_restart, thr->work_update); + thr->work_update = false; + thr->work_restart = false; + + work = get_work(thr, thr->id); /* Make sure pool is ready */ + discard_work(work); /* Don't leak memory */ + + pool = current_pool(); + if (!pool->has_stratum) + quit(1, "Avalon2: MM have to use stratum pool"); + + if (pool->coinbase_len > AVA2_P_COINBASE_SIZE) { + applog(LOG_INFO, "Avalon2: MM pool coinbase length(%d) is more than %d", + pool->coinbase_len, AVA2_P_COINBASE_SIZE); + if (mm_cmp_1406(info)) { + applog(LOG_ERR, "Avalon2: MM version less then 1406"); + return; + } + if ((pool->coinbase_len - pool->nonce2_offset + 64) > AVA2_P_COINBASE_SIZE) { + applog(LOG_ERR, "Avalon2: MM pool modified coinbase length(%d) is more than %d", + pool->coinbase_len - pool->nonce2_offset + 64, AVA2_P_COINBASE_SIZE); + return; + } + } + if (pool->merkles > AVA2_P_MERKLES_COUNT) { + applog(LOG_ERR, "Avalon2: MM merkles have to less then %d", AVA2_P_MERKLES_COUNT); + return; + } + if (pool->n2size < 3) { + applog(LOG_ERR, "Avalon2: MM nonce2 size have to >= 3 (%d)", pool->n2size); + return; + } + + cgtime(&info->last_stratum); + cg_rlock(&pool->data_lock); + info->pool_no = pool->pool_no; + copy_pool_stratum(info, pool); + avalon2_stratum_pkgs(avalon2, pool); + cg_runlock(&pool->data_lock); + + /* Configuer the parameter from outside */ + adjust_fan(info); + info->set_voltage = opt_avalon2_voltage_min; + info->set_frequency = opt_avalon2_freq_min; + + /* Set the Fan, Voltage and Frequency */ + memset(send_pkg.data, 0, AVA2_P_DATA_LEN); + + tmp = be32toh(info->fan_pwm); + memcpy(send_pkg.data, &tmp, 4); + + applog(LOG_INFO, "Avalon2: Temp max: %d, Cut off temp: %d", + get_current_temp_max(info), opt_avalon2_overheat); + if (get_current_temp_max(info) >= opt_avalon2_overheat) + tmp = encode_voltage(0); + else + tmp = encode_voltage(info->set_voltage); + tmp = be32toh(tmp); + memcpy(send_pkg.data + 4, &tmp, 4); + + tmp = be32toh(info->set_frequency); + memcpy(send_pkg.data + 8, &tmp, 4); + + /* Configure the nonce2 offset and range */ + if (pool->n2size == 3) + range = 0xffffff / (total_devices + 1); + else + range = 0xffffffff / (total_devices + 1); + start = range * (avalon2->device_id + 1); + + tmp = be32toh(start); + memcpy(send_pkg.data + 12, &tmp, 4); + + tmp = be32toh(range); + memcpy(send_pkg.data + 16, &tmp, 4); + + /* Package the data */ + avalon2_init_pkg(&send_pkg, AVA2_P_SET, 1, 1); + avalon2_send_pkg(avalon2, &send_pkg); +} + +static int64_t avalon2_scanhash(struct thr_info *thr) +{ + struct timeval current_stratum; + struct cgpu_info *avalon2 = thr->cgpu; + struct avalon2_info *info = avalon2->device_data; + int stdiff; + int64_t h; + int i; + + if (unlikely(avalon2->usbinfo.nodev)) { + applog(LOG_ERR, "%s %d: Device disappeared, shutting down thread", + avalon2->drv->name, avalon2->device_id); + return -1; + } + + /* Stop polling the device if there is no stratum in 3 minutes, network is down */ + cgtime(¤t_stratum); + if (tdiff(¤t_stratum, &(info->last_stratum)) > (double)(3.0 * 60.0)) + return 0; + + polling(thr, avalon2, info); + + stdiff = share_work_tdiff(avalon2); + if (unlikely(info->failing)) { + if (stdiff > 120) { + applog(LOG_ERR, "%s %d: No valid shares for over 2 minutes, shutting down thread", + avalon2->drv->name, avalon2->device_id); + return -1; + } + } else if (stdiff > 60) { + applog(LOG_ERR, "%s %d: No valid shares for over 1 minute, issuing a USB reset", + avalon2->drv->name, avalon2->device_id); + usb_reset(avalon2); + info->failing = true; + + } + + h = 0; + for (i = 0; i < AVA2_DEFAULT_MODULARS; i++) { + h += info->enable[i] ? (info->local_work[i] - info->hw_work[i]) : 0; + } + return h * 0xffffffff; +} + +static struct api_data *avalon2_api_stats(struct cgpu_info *cgpu) +{ + struct api_data *root = NULL; + struct avalon2_info *info = cgpu->device_data; + int i, j, a, b; + char buf[24]; + double hwp; + int minerindex, minercount; + + for (i = 0; i < AVA2_DEFAULT_MODULARS; i++) { + if(info->dev_type[i] == AVA2_ID_AVAX) + continue; + sprintf(buf, "ID%d MM Version", i + 1); + root = api_add_string(root, buf, (char *)&(info->mm_version[i]), false); + } + + minerindex = 0; + minercount = 0; + for (i = 0; i < AVA2_DEFAULT_MODULARS; i++) { + if (info->dev_type[i] == AVA2_ID_AVAX) { + minerindex += AVA2_DEFAULT_MINERS; + continue; + } + + if (info->dev_type[i] == AVA2_ID_AVA2) + minercount = AVA2_DEFAULT_MINERS; + + if (info->dev_type[i] == AVA2_ID_AVA3) + minercount = AVA2_AVA3_MINERS; + + for (j = minerindex; j < (minerindex + minercount); j++) { + sprintf(buf, "Match work count%02d", j+1); + root = api_add_int(root, buf, &(info->matching_work[j]), false); + } + minerindex += AVA2_DEFAULT_MINERS; + } + + for (i = 0; i < AVA2_DEFAULT_MODULARS; i++) { + if(info->dev_type[i] == AVA2_ID_AVAX) + continue; + sprintf(buf, "Local works%d", i + 1); + root = api_add_int(root, buf, &(info->local_works[i]), false); + } + for (i = 0; i < AVA2_DEFAULT_MODULARS; i++) { + if(info->dev_type[i] == AVA2_ID_AVAX) + continue; + sprintf(buf, "Hardware error works%d", i + 1); + root = api_add_int(root, buf, &(info->hw_works[i]), false); + } + for (i = 0; i < AVA2_DEFAULT_MODULARS; i++) { + if(info->dev_type[i] == AVA2_ID_AVAX) + continue; + a = info->hw_works[i]; + b = info->local_works[i]; + hwp = b ? ((double)a / (double)b) : 0; + + sprintf(buf, "Device hardware error%d%%", i + 1); + root = api_add_percent(root, buf, &hwp, true); + } + for (i = 0; i < 2 * AVA2_DEFAULT_MODULARS; i++) { + if(info->dev_type[i/2] == AVA2_ID_AVAX) + continue; + sprintf(buf, "Temperature%d", i + 1); + root = api_add_int(root, buf, &(info->temp[i]), false); + } + for (i = 0; i < 2 * AVA2_DEFAULT_MODULARS; i++) { + if(info->dev_type[i/2] == AVA2_ID_AVAX) + continue; + sprintf(buf, "Fan%d", i + 1); + root = api_add_int(root, buf, &(info->fan[i]), false); + } + for (i = 0; i < AVA2_DEFAULT_MODULARS; i++) { + if(info->dev_type[i] == AVA2_ID_AVAX) + continue; + sprintf(buf, "Voltage%d", i + 1); + root = api_add_int(root, buf, &(info->get_voltage[i]), false); + } + for (i = 0; i < AVA2_DEFAULT_MODULARS; i++) { + if(info->dev_type[i] == AVA2_ID_AVAX) + continue; + sprintf(buf, "Frequency%d", i + 1); + root = api_add_int(root, buf, &(info->get_frequency[i]), false); + } + for (i = 0; i < AVA2_DEFAULT_MODULARS; i++) { + if(info->dev_type[i] == AVA2_ID_AVAX) + continue; + sprintf(buf, "Power good %02x", i + 1); + root = api_add_int(root, buf, &(info->power_good[i]), false); + } + for (i = 0; i < AVA2_DEFAULT_MODULARS; i++) { + if(info->dev_type[i] == AVA2_ID_AVAX) + continue; + sprintf(buf, "Led %02x", i + 1); + root = api_add_int(root, buf, &(info->led_red[i]), false); + } + + return root; +} + +static void avalon2_statline_before(char *buf, size_t bufsiz, struct cgpu_info *avalon2) +{ + struct avalon2_info *info = avalon2->device_data; + int temp = get_current_temp_max(info); + float volts = (float)info->set_voltage / 10000; + + tailsprintf(buf, bufsiz, "%4dMhz %2dC %3d%% %.3fV", info->set_frequency, + temp, info->fan_pct, volts); +} + +static void avalon2_shutdown(struct thr_info *thr) +{ + struct cgpu_info *avalon2 = thr->cgpu; + int interface = usb_interface(avalon2); + + usb_transfer(avalon2, PL2303_CTRL_OUT, PL2303_REQUEST_CTRL, 0, interface, C_SETLINE); +} + +struct device_drv avalon2_drv = { + .drv_id = DRIVER_avalon2, + .dname = "avalon2", + .name = "AV2", + .get_api_stats = avalon2_api_stats, + .get_statline_before = avalon2_statline_before, + .drv_detect = avalon2_detect, + .thread_prepare = avalon2_prepare, + .hash_work = hash_driver_work, + .flush_work = avalon2_update, + .update_work = avalon2_update, + .scanwork = avalon2_scanhash, + .thread_shutdown = avalon2_shutdown, +}; diff --git a/driver-avalon2.h b/driver-avalon2.h new file mode 100644 index 0000000..17f1c2e --- /dev/null +++ b/driver-avalon2.h @@ -0,0 +1,163 @@ +/* + * Copyright 2013-2014 Con Kolivas + * Copyright 2012-2014 Xiangfu + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#ifndef _AVALON2_H_ +#define _AVALON2_H_ + +#include "util.h" +#include "fpgautils.h" + +#ifdef USE_AVALON2 + +#define AVA2_MINER_THREADS 1 + +#define AVA2_RESET_FAULT_DECISECONDS 10 +#define AVA2_IO_SPEED 115200 + +#define AVA2_DEFAULT_MODULARS 4 + +#define AVA2_PWM_MAX 0x3FF +#define AVA2_DEFAULT_FAN_PWM 15 /* % */ +#define AVA2_DEFAULT_FAN_MIN 10 +#define AVA2_DEFAULT_FAN_MAX 85 + +#define AVALON2_TEMP_OVERHEAT 98 +#define AVALON2_DEFAULT_POLLING_DELAY 20 /* ms */ + +#define AVA2_DEFAULT_VOLTAGE_MIN 6000 +#define AVA2_DEFAULT_VOLTAGE_MAX 11000 + +#define AVA2_DEFAULT_FREQUENCY_MIN 300 +#define AVA2_DEFAULT_FREQUENCY_MAX 2000 + +/* Avalon2 default values */ +#define AVA2_DEFAULT_MINERS 10 +#define AVA2_DEFAULT_VOLTAGE 10000 /* v * 10000 */ +#define AVA2_DEFAULT_FREQUENCY 1500 /* In MHs */ + +/* Avalon3 default values */ +#define AVA2_AVA3_MINERS 5 +#define AVA2_AVA3_VOLTAGE 6660 /* 0.666v */ +#define AVA2_AVA3_FREQUENCY 450 /* MHz * 11.8 = MHs: 450MHz means ~5.3GHs */ + +/* Avalon2 protocol package type */ +#define AVA2_H1 'A' +#define AVA2_H2 'V' + +#define AVA2_P_COINBASE_SIZE (6 * 1024) +#define AVA2_P_MERKLES_COUNT 20 + +#define AVA2_P_COUNT 39 +#define AVA2_P_DATA_LEN (AVA2_P_COUNT - 7) + +#define AVA2_P_DETECT 10 +#define AVA2_P_STATIC 11 +#define AVA2_P_JOB_ID 12 +#define AVA2_P_COINBASE 13 +#define AVA2_P_MERKLES 14 +#define AVA2_P_HEADER 15 +#define AVA2_P_POLLING 16 +#define AVA2_P_TARGET 17 +#define AVA2_P_REQUIRE 18 +#define AVA2_P_SET 19 +#define AVA2_P_TEST 20 + +#define AVA2_P_ACK 21 +#define AVA2_P_NAK 22 +#define AVA2_P_NONCE 23 +#define AVA2_P_STATUS 24 +#define AVA2_P_ACKDETECT 25 +#define AVA2_P_TEST_RET 26 +/* Avalon2 protocol package type */ + +/* Avalon2/3 firmware prefix */ +#define AVA2_FW2_PREFIXSTR "20" +#define AVA2_FW3_PREFIXSTR "33" + +#define AVA2_MM_VERNULL "NONE" + +#define AVA2_ID_AVA2 3255 +#define AVA2_ID_AVA3 3233 +#define AVA2_ID_AVAX 3200 + +enum avalon2_fan_fixed { + FAN_FIXED, + FAN_AUTO, +}; + +struct avalon2_pkg { + uint8_t head[2]; + uint8_t type; + uint8_t idx; + uint8_t cnt; + uint8_t data[32]; + uint8_t crc[2]; +}; +#define avalon2_ret avalon2_pkg + +struct avalon2_info { + struct timeval last_stratum; + struct pool pool; + int pool_no; + + int modulars[AVA2_DEFAULT_MODULARS]; + char mm_version[AVA2_DEFAULT_MODULARS][16]; + int dev_type[AVA2_DEFAULT_MODULARS]; + bool enable[AVA2_DEFAULT_MODULARS]; + + int set_frequency; + int set_voltage; + + int get_voltage[AVA2_DEFAULT_MODULARS]; + int get_frequency[AVA2_DEFAULT_MODULARS]; + int power_good[AVA2_DEFAULT_MODULARS]; + + int fan_pwm; + int fan_pct; + int temp_max; + + int fan[2 * AVA2_DEFAULT_MODULARS]; + int temp[2 * AVA2_DEFAULT_MODULARS]; + + int local_works[AVA2_DEFAULT_MODULARS]; + int hw_works[AVA2_DEFAULT_MODULARS]; + + int local_work[AVA2_DEFAULT_MODULARS]; + int hw_work[AVA2_DEFAULT_MODULARS]; + int matching_work[AVA2_DEFAULT_MINERS * AVA2_DEFAULT_MODULARS]; + + int led_red[AVA2_DEFAULT_MODULARS]; + + bool failing; +}; + +#define AVA2_WRITE_SIZE (sizeof(struct avalon2_pkg)) +#define AVA2_READ_SIZE AVA2_WRITE_SIZE + +#define AVA2_GETS_OK 0 +#define AVA2_GETS_TIMEOUT -1 +#define AVA2_GETS_RESTART -2 +#define AVA2_GETS_ERROR -3 + +#define AVA2_SEND_OK 0 +#define AVA2_SEND_ERROR -1 + +#define avalon2_open(devpath, baud, purge) serial_open(devpath, baud, AVA2_RESET_FAULT_DECISECONDS, purge) +#define avalon2_close(fd) close(fd) + +extern char *set_avalon2_fan(char *arg); +extern char *set_avalon2_freq(char *arg); +extern char *set_avalon2_voltage(char *arg); +extern char *set_avalon2_fixed_speed(enum avalon2_fan_fixed *f); +extern enum avalon2_fan_fixed opt_avalon2_fan_fixed; +extern int opt_avalon2_overheat; +extern int opt_avalon2_polling_delay; +#endif /* USE_AVALON2 */ +#endif /* _AVALON2_H_ */ diff --git a/driver-avalon4.c b/driver-avalon4.c new file mode 100644 index 0000000..34329ae --- /dev/null +++ b/driver-avalon4.c @@ -0,0 +1,1637 @@ +/* + * Copyright 2014 Mikeqin + * Copyright 2013-2014 Con Kolivas + * Copyright 2012-2014 Xiangfu + * Copyright 2012 Luke Dashjr + * Copyright 2012 Andrew Smith + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#include "config.h" + +#include "miner.h" +#include "driver-avalon4.h" +#include "crc.h" +#include "sha2.h" +#include "hexdump.c" + +#define get_fan_pwm(v) (AVA4_PWM_MAX - (v) * AVA4_PWM_MAX / 100) + +int opt_avalon4_temp_target = AVA4_DEFAULT_TEMP_TARGET; +int opt_avalon4_overheat = AVA4_DEFAULT_TEMP_OVERHEAT; + +int opt_avalon4_fan_min = AVA4_DEFAULT_FAN_MIN; +int opt_avalon4_fan_max = AVA4_DEFAULT_FAN_MAX; + +bool opt_avalon4_autov; +int opt_avalon4_voltage_min = AVA4_DEFAULT_VOLTAGE; +int opt_avalon4_voltage_max = AVA4_DEFAULT_VOLTAGE; +int opt_avalon4_freq[3] = {AVA4_DEFAULT_FREQUENCY, + AVA4_DEFAULT_FREQUENCY, + AVA4_DEFAULT_FREQUENCY}; + +int opt_avalon4_polling_delay = AVA4_DEFAULT_POLLING_DELAY; + +int opt_avalon4_aucspeed = AVA4_AUC_SPEED; +int opt_avalon4_aucxdelay = AVA4_AUC_XDELAY; + +int opt_avalon4_ntime_offset = AVA4_DEFAULT_ASIC_COUNT; + +#define UNPACK32(x, str) \ +{ \ + *((str) + 3) = (uint8_t) ((x) ); \ + *((str) + 2) = (uint8_t) ((x) >> 8); \ + *((str) + 1) = (uint8_t) ((x) >> 16); \ + *((str) + 0) = (uint8_t) ((x) >> 24); \ +} + +static inline void sha256_prehash(const unsigned char *message, unsigned int len, unsigned char *digest) +{ + sha256_ctx ctx; + int i; + sha256_init(&ctx); + sha256_update(&ctx, message, len); + + for (i = 0; i < 8; i++) { + UNPACK32(ctx.h[i], &digest[i << 2]); + } +} + +static inline uint8_t rev8(uint8_t d) +{ + int i; + uint8_t out = 0; + + /* (from left to right) */ + for (i = 0; i < 8; i++) + if (d & (1 << i)) + out |= (1 << (7 - i)); + + return out; +} + +char *set_avalon4_fan(char *arg) +{ + int val1, val2, ret; + + ret = sscanf(arg, "%d-%d", &val1, &val2); + if (ret < 1) + return "No values passed to avalon4-fan"; + if (ret == 1) + val2 = val1; + + if (val1 < 0 || val1 > 100 || val2 < 0 || val2 > 100 || val2 < val1) + return "Invalid value passed to avalon4-fan"; + + opt_avalon4_fan_min = val1; + opt_avalon4_fan_max = val2; + + return NULL; +} + +char *set_avalon4_freq(char *arg) +{ + char *colon1, *colon2; + int val1 = 0, val2 = 0, val3 = 0; + + if (!(*arg)) + return NULL; + + colon1 = strchr(arg, ':'); + if (colon1) + *(colon1++) = '\0'; + + if (*arg) { + val1 = atoi(arg); + if (val1 < AVA4_DEFAULT_FREQUENCY_MIN || val1 > AVA4_DEFAULT_FREQUENCY_MAX) + return "Invalid value1 passed to avalon4-freq"; + } + + if (colon1 && *colon1) { + colon2 = strchr(colon1, ':'); + if (colon2) + *(colon2++) = '\0'; + + if (*colon1) { + val2 = atoi(colon1); + if (val2 < AVA4_DEFAULT_FREQUENCY_MIN || val2 > AVA4_DEFAULT_FREQUENCY_MAX) + return "Invalid value2 passed to avalon4-freq"; + } + + if (colon2 && *colon2) { + val3 = atoi(colon2); + if (val3 < AVA4_DEFAULT_FREQUENCY_MIN || val3 > AVA4_DEFAULT_FREQUENCY_MAX) + return "Invalid value3 passed to avalon4-freq"; + } + } + + if (!val1) + val3 = val2 = val1 = AVA4_DEFAULT_FREQUENCY; + + if (!val2) + val3 = val2 = val1; + + if (!val3) + val3 = val2; + + opt_avalon4_freq[0] = val1; + opt_avalon4_freq[1] = val2; + opt_avalon4_freq[2] = val3; + + return NULL; +} + +char *set_avalon4_voltage(char *arg) +{ + int val1, val2, ret; + + ret = sscanf(arg, "%d-%d", &val1, &val2); + if (ret < 1) + return "No values passed to avalon4-voltage"; + if (ret == 1) + val2 = val1; + + if (val1 < AVA4_DEFAULT_VOLTAGE_MIN || val1 > AVA4_DEFAULT_VOLTAGE_MAX || + val2 < AVA4_DEFAULT_VOLTAGE_MIN || val2 > AVA4_DEFAULT_VOLTAGE_MAX || + val2 < val1) + return "Invalid value passed to avalon4-voltage"; + + opt_avalon4_voltage_min = val1; + opt_avalon4_voltage_max = val2; + + return NULL; +} + +static int avalon4_init_pkg(struct avalon4_pkg *pkg, uint8_t type, uint8_t idx, uint8_t cnt) +{ + unsigned short crc; + + pkg->head[0] = AVA4_H1; + pkg->head[1] = AVA4_H2; + + pkg->type = type; + pkg->opt = 0; + pkg->idx = idx; + pkg->cnt = cnt; + + crc = crc16(pkg->data, AVA4_P_DATA_LEN); + + pkg->crc[0] = (crc & 0xff00) >> 8; + pkg->crc[1] = crc & 0x00ff; + return 0; +} + +static int job_idcmp(uint8_t *job_id, char *pool_job_id) +{ + int job_id_len; + unsigned short crc, crc_expect; + + if (!pool_job_id) + return 1; + + job_id_len = strlen(pool_job_id); + crc_expect = crc16((unsigned char *)pool_job_id, job_id_len); + + crc = job_id[0] << 8 | job_id[1]; + + if (crc_expect == crc) + return 0; + + applog(LOG_DEBUG, "Avalon4: job_id doesn't match! [%04x:%04x (%s)]", + crc, crc_expect, pool_job_id); + + return 1; +} + +static inline int get_current_temp_max(struct avalon4_info *info) +{ + int i; + int t = info->temp[0]; + + for (i = 1; i < AVA4_DEFAULT_MODULARS; i++) { + if (info->temp[i] > t) + t = info->temp[i]; + } + return t; +} + +/* http://www.onsemi.com/pub_link/Collateral/ADP3208D.PDF */ +static uint32_t encode_voltage_adp3208d(uint32_t v) +{ + return rev8((0x78 - v / 125) << 1 | 1) << 8; +} + +static uint32_t decode_voltage_adp3208d(uint32_t v) +{ + return (0x78 - (rev8(v >> 8) >> 1)) * 125; +} + +/* http://www.onsemi.com/pub/Collateral/NCP5392P-D.PDF */ +static uint32_t encode_voltage_ncp5392p(uint32_t v) +{ + if (v == 0) + return 0xff00; + + return rev8(((0x59 - (v - 5000) / 125) & 0xff) << 1 | 1) << 8; +} + +static uint32_t decode_voltage_ncp5392p(uint32_t v) +{ + if (v == 0xff00) + return 0; + + return (0x59 - (rev8(v >> 8) >> 1)) * 125 + 5000; +} + +static inline uint32_t adjust_fan(struct avalon4_info *info, int id) +{ + uint32_t pwm; + int t = info->temp[id]; + + if (t < opt_avalon4_temp_target - 10) + info->fan_pct[id] = opt_avalon4_fan_min; + else if (t > opt_avalon4_temp_target + 10 || t > opt_avalon4_overheat - 3) + info->fan_pct[id] = opt_avalon4_fan_max; + else if (t > opt_avalon4_temp_target + 1) + info->fan_pct[id] += 2; + else if (t < opt_avalon4_temp_target - 1) + info->fan_pct[id] -= 2; + + if (info->fan_pct[id] < opt_avalon4_fan_min) + info->fan_pct[id] = opt_avalon4_fan_min; + if (info->fan_pct[id] > opt_avalon4_fan_max) + info->fan_pct[id] = opt_avalon4_fan_max; + + pwm = get_fan_pwm(info->fan_pct[id]); + applog(LOG_DEBUG, "[%d], Adjust_fan: %dC-%d%%(%03x)", id, t, info->fan_pct[id], pwm); + + return pwm; +} + +static int decode_pkg(struct thr_info *thr, struct avalon4_ret *ar, int modular_id) +{ + struct cgpu_info *avalon4 = thr->cgpu; + struct avalon4_info *info = avalon4->device_data; + struct pool *pool, *real_pool; + struct pool *pool_stratum0 = &info->pool0; + struct pool *pool_stratum1 = &info->pool1; + struct pool *pool_stratum2 = &info->pool2; + + unsigned int expected_crc; + unsigned int actual_crc; + uint32_t nonce, nonce2, ntime, miner, chip_id, volt, tmp; + uint8_t job_id[4]; + int pool_no; + + if (ar->head[0] != AVA4_H1 && ar->head[1] != AVA4_H2) { + applog(LOG_DEBUG, "Avalon4: H1 %02x, H2 %02x", ar->head[0], ar->head[1]); + hexdump(ar->data, 32); + return 1; + } + + expected_crc = crc16(ar->data, AVA4_P_DATA_LEN); + actual_crc = (ar->crc[0] & 0xff) | ((ar->crc[1] & 0xff) << 8); + if (expected_crc != actual_crc) { + applog(LOG_DEBUG, "Avalon4: %02x: expected crc(%04x), actual_crc(%04x)", + ar->type, expected_crc, actual_crc); + return 1; + } + + switch(ar->type) { + case AVA4_P_NONCE: + applog(LOG_DEBUG, "Avalon4: AVA4_P_NONCE"); + memcpy(&miner, ar->data + 0, 4); + memcpy(&pool_no, ar->data + 4, 4); + memcpy(&nonce2, ar->data + 8, 4); + memcpy(&ntime, ar->data + 12, 4); + memcpy(&nonce, ar->data + 16, 4); + memcpy(job_id, ar->data + 20, 4); + + miner = be32toh(miner); + chip_id = (miner >> 16) & 0xffff; + miner &= 0xffff; + pool_no = be32toh(pool_no); + ntime = be32toh(ntime); + if (miner >= AVA4_DEFAULT_MINERS || + pool_no >= total_pools || pool_no < 0) { + applog(LOG_DEBUG, "Avalon4: Wrong miner/pool_no %d/%d", miner, pool_no); + break; + } else { + info->matching_work[modular_id][miner]++; + info->chipmatching_work[modular_id][miner][chip_id]++; + } + nonce2 = be32toh(nonce2); + nonce = be32toh(nonce); + nonce -= 0x4000; + + applog(LOG_DEBUG, "%s-%d-%d: Found! P:%d - N2:%08x N:%08x NR:%d [M:%d - MW: %d(%d,%d,%d,%d)]", + avalon4->drv->name, avalon4->device_id, modular_id, + pool_no, nonce2, nonce, ntime, + miner, info->matching_work[modular_id][miner], + info->chipmatching_work[modular_id][miner][0], + info->chipmatching_work[modular_id][miner][1], + info->chipmatching_work[modular_id][miner][2], + info->chipmatching_work[modular_id][miner][3]); + + real_pool = pool = pools[pool_no]; + if (job_idcmp(job_id, pool->swork.job_id)) { + if (!job_idcmp(job_id, pool_stratum0->swork.job_id)) { + applog(LOG_DEBUG, "Avalon4: Match to previous stratum0! (%s)", pool_stratum0->swork.job_id); + pool = pool_stratum0; + } else if (!job_idcmp(job_id, pool_stratum1->swork.job_id)) { + applog(LOG_DEBUG, "Avalon4: Match to previous stratum1! (%s)", pool_stratum1->swork.job_id); + pool = pool_stratum1; + } else if (!job_idcmp(job_id, pool_stratum2->swork.job_id)) { + applog(LOG_DEBUG, "Avalon4: Match to previous stratum2! (%s)", pool_stratum2->swork.job_id); + pool = pool_stratum2; + } else { + applog(LOG_ERR, "Avalon4: Cannot match to any stratum! (%s)", pool->swork.job_id); + inc_hw_errors(thr); + break; + } + } + + submit_nonce2_nonce(thr, pool, real_pool, nonce2, nonce, ntime); + break; + case AVA4_P_STATUS: + applog(LOG_DEBUG, "Avalon4: AVA4_P_STATUS"); + hexdump(ar->data, 32); + memcpy(&tmp, ar->data, 4); + tmp = be32toh(tmp); + info->temp[modular_id] = tmp; + + memcpy(&tmp, ar->data + 4, 4); + tmp = be32toh(tmp); + info->fan[modular_id] = tmp; + + memcpy(&(info->get_frequency[modular_id]), ar->data + 8, 4); + memcpy(&(info->get_voltage[modular_id]), ar->data + 12, 4); + memcpy(&(info->local_work[modular_id]), ar->data + 16, 4); + memcpy(&(info->hw_work[modular_id]), ar->data + 20, 4); + memcpy(&(info->power_good[modular_id]), ar->data + 24, 4); + + info->get_frequency[modular_id] = be32toh(info->get_frequency[modular_id]) * 3968 / 65; + info->get_voltage[modular_id] = be32toh(info->get_voltage[modular_id]); + info->local_work[modular_id] = be32toh(info->local_work[modular_id]); + info->hw_work[modular_id] = be32toh(info->hw_work[modular_id]); + info->power_good[modular_id] = be32toh(info->power_good[modular_id]); + + volt = info->get_voltage[modular_id]; + if (info->mod_type[modular_id] == AVA4_TYPE_MM40) + tmp = decode_voltage_adp3208d(volt); + if (info->mod_type[modular_id] == AVA4_TYPE_MM41) + tmp = decode_voltage_ncp5392p(volt); + info->get_voltage[modular_id] = tmp; + + info->local_works[modular_id] += info->local_work[modular_id]; + info->hw_works[modular_id] += info->hw_work[modular_id]; + + info->lw5[modular_id][info->i_1m] += info->local_work[modular_id]; + info->hw5[modular_id][info->i_1m] += info->hw_work[modular_id]; + + avalon4->temp = get_current_temp_max(info); + break; + case AVA4_P_ACKDETECT: + applog(LOG_DEBUG, "Avalon4: AVA4_P_ACKDETECT"); + break; + default: + applog(LOG_DEBUG, "Avalon4: Unknown response"); + break; + } + return 0; +} + +/* + # IIC packet format: length[1]+transId[1]+sesId[1]+req[1]+data[60] + # length: 4+len(data) + # transId: 0 + # sesId: 0 + # req: checkout the header file + # data: + # INIT: clock_rate[4] + reserved[4] + payload[52] + # XFER: txSz[1]+rxSz[1]+options[1]+slaveAddr[1] + payload[56] + */ +static int avalon4_iic_init_pkg(uint8_t *iic_pkg, struct avalon4_iic_info *iic_info, uint8_t *buf, int wlen, int rlen) +{ + memset(iic_pkg, 0, AVA4_AUC_P_SIZE); + + switch (iic_info->iic_op) { + case AVA4_IIC_INIT: + iic_pkg[0] = 12; /* 4 bytes IIC header + 4 bytes speed + 4 bytes xfer delay */ + iic_pkg[3] = AVA4_IIC_INIT; + iic_pkg[4] = iic_info->iic_param.aucParam[0] & 0xff; + iic_pkg[5] = (iic_info->iic_param.aucParam[0] >> 8) & 0xff; + iic_pkg[6] = (iic_info->iic_param.aucParam[0] >> 16) & 0xff; + iic_pkg[7] = iic_info->iic_param.aucParam[0] >> 24; + iic_pkg[8] = iic_info->iic_param.aucParam[1] & 0xff; + iic_pkg[9] = (iic_info->iic_param.aucParam[1] >> 8) & 0xff; + iic_pkg[10] = (iic_info->iic_param.aucParam[1] >> 16) & 0xff; + iic_pkg[11] = iic_info->iic_param.aucParam[1] >> 24; + break; + case AVA4_IIC_XFER: + iic_pkg[0] = 8 + wlen; + iic_pkg[3] = AVA4_IIC_XFER; + iic_pkg[4] = wlen; + iic_pkg[5] = rlen; + iic_pkg[7] = iic_info->iic_param.slave_addr; + if (buf && wlen) + memcpy(iic_pkg + 8, buf, wlen); + break; + case AVA4_IIC_RESET: + case AVA4_IIC_DEINIT: + case AVA4_IIC_INFO: + iic_pkg[0] = 4; + iic_pkg[3] = iic_info->iic_op; + break; + + default: + break; + } + + return 0; +} + +static int avalon4_iic_xfer(struct cgpu_info *avalon4, + uint8_t *wbuf, int wlen, int *write, + uint8_t *rbuf, int rlen, int *read) +{ + int err = -1; + + if (unlikely(avalon4->usbinfo.nodev)) + goto out; + + err = usb_write(avalon4, (char *)wbuf, wlen, write, C_AVA4_WRITE); + if (err || *write != wlen) { + applog(LOG_DEBUG, "Avalon4: AUC xfer %d, w(%d-%d)!", err, wlen, *write); + usb_nodev(avalon4); + goto out; + } + + cgsleep_ms(opt_avalon4_aucxdelay / 4800 + 1); + + rlen += 4; /* Add 4 bytes IIC header */ + err = usb_read(avalon4, (char *)rbuf, rlen, read, C_AVA4_READ); + if (err || *read != rlen) { + applog(LOG_DEBUG, "Avalon4: AUC xfer %d, r(%d-%d)!", err, rlen - 4, *read); + hexdump(rbuf, rlen); + } + + *read = rbuf[0] - 4; /* Remove 4 bytes IIC header */ +out: + return err; +} + +static int avalon4_auc_init(struct cgpu_info *avalon4, char *ver) +{ + struct avalon4_iic_info iic_info; + int err, wlen, rlen; + uint8_t wbuf[AVA4_AUC_P_SIZE]; + uint8_t rbuf[AVA4_AUC_P_SIZE]; + + if (unlikely(avalon4->usbinfo.nodev)) + return 1; + + /* Try to clean the AUC buffer */ + err = usb_read(avalon4, (char *)rbuf, AVA4_AUC_P_SIZE, &rlen, C_AVA4_READ); + applog(LOG_DEBUG, "Avalon4: AUC usb_read %d, %d!", err, rlen); + hexdump(rbuf, AVA4_AUC_P_SIZE); + + /* Reset */ + iic_info.iic_op = AVA4_IIC_RESET; + rlen = 0; + avalon4_iic_init_pkg(wbuf, &iic_info, NULL, 0, rlen); + + memset(rbuf, 0, AVA4_AUC_P_SIZE); + err = avalon4_iic_xfer(avalon4, wbuf, AVA4_AUC_P_SIZE, &wlen, rbuf, rlen, &rlen); + if (err) { + applog(LOG_ERR, "Avalon4: Failed to reset Avalon USB2IIC Converter"); + return 1; + } + + /* Deinit */ + iic_info.iic_op = AVA4_IIC_DEINIT; + rlen = 0; + avalon4_iic_init_pkg(wbuf, &iic_info, NULL, 0, rlen); + + memset(rbuf, 0, AVA4_AUC_P_SIZE); + err = avalon4_iic_xfer(avalon4, wbuf, AVA4_AUC_P_SIZE, &wlen, rbuf, rlen, &rlen); + if (err) { + applog(LOG_ERR, "Avalon4: Failed to deinit Avalon USB2IIC Converter"); + return 1; + } + + /* Init */ + iic_info.iic_op = AVA4_IIC_INIT; + iic_info.iic_param.aucParam[0] = opt_avalon4_aucspeed; + iic_info.iic_param.aucParam[1] = opt_avalon4_aucxdelay; + rlen = AVA4_AUC_VER_LEN; + avalon4_iic_init_pkg(wbuf, &iic_info, NULL, 0, rlen); + + memset(rbuf, 0, AVA4_AUC_P_SIZE); + err = avalon4_iic_xfer(avalon4, wbuf, AVA4_AUC_P_SIZE, &wlen, rbuf, rlen, &rlen); + if (err) { + applog(LOG_ERR, "Avalon4: Failed to init Avalon USB2IIC Converter"); + return 1; + } + + hexdump(rbuf, AVA4_AUC_P_SIZE); + + memcpy(ver, rbuf + 4, AVA4_AUC_VER_LEN); + ver[AVA4_AUC_VER_LEN] = '\0'; + + applog(LOG_DEBUG, "Avalon4: USB2IIC Converter version: %s!", ver); + return 0; +} + +static int avalon4_auc_getinfo(struct cgpu_info *avalon4) +{ + struct avalon4_iic_info iic_info; + int err, wlen, rlen; + uint8_t wbuf[AVA4_AUC_P_SIZE]; + uint8_t rbuf[AVA4_AUC_P_SIZE]; + uint8_t *pdata = rbuf + 4; + uint16_t adc_val; + struct avalon4_info *info = avalon4->device_data; + + iic_info.iic_op = AVA4_IIC_INFO; + /* Device info: (9 bytes) + * tempadc(2), reqRdIndex, reqWrIndex, + * respRdIndex, respWrIndex, tx_flags, state + * */ + rlen = 7; + avalon4_iic_init_pkg(wbuf, &iic_info, NULL, 0, rlen); + + memset(rbuf, 0, AVA4_AUC_P_SIZE); + err = avalon4_iic_xfer(avalon4, wbuf, AVA4_AUC_P_SIZE, &wlen, rbuf, rlen, &rlen); + if (err) { + applog(LOG_ERR, "Avalon4: AUC Failed to get info "); + return 1; + } + + applog(LOG_DEBUG, "Avalon4: AUC tempADC(%03d), reqcnt(%d), respcnt(%d), txflag(%d), state(%d)", + pdata[1] << 8 | pdata[0], + pdata[2], + pdata[3], + pdata[5] << 8 | pdata[4], + pdata[6]); + + adc_val = pdata[1] << 8 | pdata[0]; + + info->auc_temp = 3.3 * adc_val * 10000 / 1023; + return 0; +} + +static int avalon4_iic_xfer_pkg(struct cgpu_info *avalon4, uint8_t slave_addr, + const struct avalon4_pkg *pkg, struct avalon4_ret *ret) +{ + struct avalon4_iic_info iic_info; + int err, wcnt, rcnt, rlen = 0; + uint8_t wbuf[AVA4_AUC_P_SIZE]; + uint8_t rbuf[AVA4_AUC_P_SIZE]; + + struct avalon4_info *info = avalon4->device_data; + + iic_info.iic_op = AVA4_IIC_XFER; + iic_info.iic_param.slave_addr = slave_addr; + if (ret) + rlen = AVA4_READ_SIZE; + + avalon4_iic_init_pkg(wbuf, &iic_info, (uint8_t *)pkg, AVA4_WRITE_SIZE, rlen); + err = avalon4_iic_xfer(avalon4, wbuf, wbuf[0], &wcnt, rbuf, rlen, &rcnt); + if ((pkg->type != AVA4_P_DETECT) && err == -7 && !rcnt && rlen) { + avalon4_iic_init_pkg(wbuf, &iic_info, NULL, 0, rlen); + err = avalon4_iic_xfer(avalon4, wbuf, wbuf[0], &wcnt, rbuf, rlen, &rcnt); + applog(LOG_DEBUG, "Avalon4: IIC read again!(err:%d)", err); + } + if (err || rcnt != rlen) { + if (info->xfer_err_cnt++ == 100) { + applog(LOG_DEBUG, "Avalon4: AUC xfer_err_cnt reach err = %d, rcnt = %d, rlen = %d", err, rcnt, rlen); + + cgsleep_ms(5 * 1000); /* Wait MM reset */ + avalon4_auc_init(avalon4, info->auc_version); + } + return AVA4_SEND_ERROR; + } + + if (ret) + memcpy((char *)ret, rbuf + 4, AVA4_READ_SIZE); + + info->xfer_err_cnt = 0; + return AVA4_SEND_OK; +} + +static int avalon4_send_bc_pkgs(struct cgpu_info *avalon4, const struct avalon4_pkg *pkg) +{ + int ret; + + do { + if (unlikely(avalon4->usbinfo.nodev)) + return -1; + ret = avalon4_iic_xfer_pkg(avalon4, AVA4_MODULE_BROADCAST, pkg, NULL); + } while (ret != AVA4_SEND_OK); + + return 0; +} + +static void avalon4_stratum_pkgs(struct cgpu_info *avalon4, struct pool *pool) +{ + const int merkle_offset = 36; + struct avalon4_pkg pkg; + int i, a, b, tmp; + unsigned char target[32]; + int job_id_len, n2size; + unsigned short crc; + + int coinbase_len_posthash, coinbase_len_prehash; + uint8_t coinbase_prehash[32]; + + /* Send out the first stratum message STATIC */ + applog(LOG_DEBUG, "Avalon4: Pool stratum message STATIC: %d, %d, %d, %d, %d", + pool->coinbase_len, + pool->nonce2_offset, + pool->n2size, + merkle_offset, + pool->merkles); + memset(pkg.data, 0, AVA4_P_DATA_LEN); + tmp = be32toh(pool->coinbase_len); + memcpy(pkg.data, &tmp, 4); + + tmp = be32toh(pool->nonce2_offset); + memcpy(pkg.data + 4, &tmp, 4); + + n2size = pool->n2size >= 4 ? 4 : pool->n2size; + tmp = be32toh(n2size); + memcpy(pkg.data + 8, &tmp, 4); + + tmp = be32toh(merkle_offset); + memcpy(pkg.data + 12, &tmp, 4); + + tmp = be32toh(pool->merkles); + memcpy(pkg.data + 16, &tmp, 4); + + tmp = be32toh((int)pool->swork.diff); + memcpy(pkg.data + 20, &tmp, 4); + + tmp = be32toh((int)pool->pool_no); + memcpy(pkg.data + 24, &tmp, 4); + + avalon4_init_pkg(&pkg, AVA4_P_STATIC, 1, 1); + if (avalon4_send_bc_pkgs(avalon4, &pkg)) + return; + + set_target(target, pool->sdiff); + memcpy(pkg.data, target, 32); + if (opt_debug) { + char *target_str; + target_str = bin2hex(target, 32); + applog(LOG_DEBUG, "Avalon4: Pool stratum target: %s", target_str); + free(target_str); + } + avalon4_init_pkg(&pkg, AVA4_P_TARGET, 1, 1); + if (avalon4_send_bc_pkgs(avalon4, &pkg)) + return; + + memset(pkg.data, 0, AVA4_P_DATA_LEN); + + job_id_len = strlen(pool->swork.job_id); + crc = crc16((unsigned char *)pool->swork.job_id, job_id_len); + applog(LOG_DEBUG, "Avalon4: Pool stratum message JOBS_ID[%04x]: %s", + crc, pool->swork.job_id); + + pkg.data[0] = (crc & 0xff00) >> 8; + pkg.data[1] = crc & 0x00ff; + avalon4_init_pkg(&pkg, AVA4_P_JOB_ID, 1, 1); + if (avalon4_send_bc_pkgs(avalon4, &pkg)) + return; + + coinbase_len_prehash = pool->nonce2_offset - (pool->nonce2_offset % SHA256_BLOCK_SIZE); + coinbase_len_posthash = pool->coinbase_len - coinbase_len_prehash; + sha256_prehash(pool->coinbase, coinbase_len_prehash, coinbase_prehash); + + a = (coinbase_len_posthash / AVA4_P_DATA_LEN) + 1; + b = coinbase_len_posthash % AVA4_P_DATA_LEN; + memcpy(pkg.data, coinbase_prehash, 32); + avalon4_init_pkg(&pkg, AVA4_P_COINBASE, 1, a + (b ? 1 : 0)); + if (avalon4_send_bc_pkgs(avalon4, &pkg)) + return; + applog(LOG_DEBUG, "Avalon4: Pool stratum message modified COINBASE: %d %d", a, b); + for (i = 1; i < a; i++) { + memcpy(pkg.data, pool->coinbase + coinbase_len_prehash + i * 32 - 32, 32); + avalon4_init_pkg(&pkg, AVA4_P_COINBASE, i + 1, a + (b ? 1 : 0)); + if (avalon4_send_bc_pkgs(avalon4, &pkg)) + return; + } + if (b) { + memset(pkg.data, 0, AVA4_P_DATA_LEN); + memcpy(pkg.data, pool->coinbase + coinbase_len_prehash + i * 32 - 32, b); + avalon4_init_pkg(&pkg, AVA4_P_COINBASE, i + 1, i + 1); + if (avalon4_send_bc_pkgs(avalon4, &pkg)) + return; + } + + b = pool->merkles; + applog(LOG_DEBUG, "Avalon4: Pool stratum message MERKLES: %d", b); + for (i = 0; i < b; i++) { + memset(pkg.data, 0, AVA4_P_DATA_LEN); + memcpy(pkg.data, pool->swork.merkle_bin[i], 32); + avalon4_init_pkg(&pkg, AVA4_P_MERKLES, i + 1, b); + if (avalon4_send_bc_pkgs(avalon4, &pkg)) + return; + } + + applog(LOG_DEBUG, "Avalon4: Pool stratum message HEADER: 4"); + for (i = 0; i < 4; i++) { + memset(pkg.data, 0, AVA4_P_DATA_LEN); + memcpy(pkg.data, pool->header_bin + i * 32, 32); + avalon4_init_pkg(&pkg, AVA4_P_HEADER, i + 1, 4); + if (avalon4_send_bc_pkgs(avalon4, &pkg)) + return; + } + + avalon4_auc_getinfo(avalon4); +} + +static struct cgpu_info *avalon4_auc_detect(struct libusb_device *dev, struct usb_find_devices *found) +{ + int i; + struct avalon4_info *info; + struct cgpu_info *avalon4 = usb_alloc_cgpu(&avalon4_drv, 1); + char auc_ver[AVA4_AUC_VER_LEN]; + + if (!usb_init(avalon4, dev, found)) { + applog(LOG_ERR, "Avalon4 failed usb_init"); + avalon4 = usb_free_cgpu(avalon4); + return NULL; + } + + /* Avalon4 prefers not to use zero length packets */ + avalon4->nozlp = true; + + /* We try twice on AUC init */ + if (avalon4_auc_init(avalon4, auc_ver) && avalon4_auc_init(avalon4, auc_ver)) + return NULL; + + /* We have an Avalon4 AUC connected */ + avalon4->threads = 1; + add_cgpu(avalon4); + + update_usb_stats(avalon4); + applog(LOG_INFO, "%s-%d: Found at %s", avalon4->drv->name, avalon4->device_id, + avalon4->device_path); + + avalon4->device_data = calloc(sizeof(struct avalon4_info), 1); + if (unlikely(!(avalon4->device_data))) + quit(1, "Failed to calloc avalon4_info"); + + info = avalon4->device_data; + memcpy(info->auc_version, auc_ver, AVA4_AUC_VER_LEN); + info->auc_version[AVA4_AUC_VER_LEN] = '\0'; + info->auc_speed = opt_avalon4_aucspeed; + info->auc_xdelay = opt_avalon4_aucxdelay; + + info->polling_first = 1; + + info->set_voltage_broadcat = 1; + + for (i = 0; i < AVA4_DEFAULT_MODULARS; i++) { + info->enable[i] = 0; + info->mod_type[i] = AVA4_TYPE_NULL; + info->fan_pct[i] = AVA4_DEFAULT_FAN_START; + info->set_voltage[i] = opt_avalon4_voltage_min; + } + + info->enable[0] = 1; + info->mod_type[0] = AVA4_TYPE_MM40; + + info->set_frequency[0] = opt_avalon4_freq[0]; + info->set_frequency[1] = opt_avalon4_freq[1]; + info->set_frequency[2] = opt_avalon4_freq[2]; + + return avalon4; +} + +static inline void avalon4_detect(bool __maybe_unused hotplug) +{ + usb_detect(&avalon4_drv, avalon4_auc_detect); +} + +static bool avalon4_prepare(struct thr_info *thr) +{ + int i; + struct cgpu_info *avalon4 = thr->cgpu; + struct avalon4_info *info = avalon4->device_data; + + info->polling_first = 1; + + cgtime(&(info->last_fan)); + cgtime(&(info->last_5m)); + cgtime(&(info->last_1m)); + + cglock_init(&info->update_lock); + cglock_init(&info->pool0.data_lock); + cglock_init(&info->pool1.data_lock); + cglock_init(&info->pool2.data_lock); + + info->set_voltage_broadcat = 1; + + for (i = 0; i < AVA4_DEFAULT_MODULARS; i++) + info->fan_pct[i] = AVA4_DEFAULT_FAN_START; + + + return true; +} + +static void detect_modules(struct cgpu_info *avalon4) +{ + struct avalon4_info *info = avalon4->device_data; + struct thr_info *thr = avalon4->thr[0]; + + struct avalon4_pkg detect_pkg; + struct avalon4_ret ret_pkg; + uint32_t tmp; + int i, err; + + /* Detect new modules here */ + for (i = 1; i < AVA4_DEFAULT_MODULARS; i++) { + if (info->enable[i]) + continue; + + /* Send out detect pkg */ + applog(LOG_DEBUG, "%s %d: AVA4_P_DETECT ID[%d]", + avalon4->drv->name, avalon4->device_id, i); + memset(detect_pkg.data, 0, AVA4_P_DATA_LEN); + tmp = be32toh(i); /* ID */ + memcpy(detect_pkg.data + 28, &tmp, 4); + avalon4_init_pkg(&detect_pkg, AVA4_P_DETECT, 1, 1); + err = avalon4_iic_xfer_pkg(avalon4, AVA4_MODULE_BROADCAST, &detect_pkg, &ret_pkg); + if (err == AVA4_SEND_OK) { + if (decode_pkg(thr, &ret_pkg, AVA4_MODULE_BROADCAST)) { + applog(LOG_DEBUG, "%s %d: Should be AVA4_P_ACKDETECT(%d), but %d", + avalon4->drv->name, avalon4->device_id, AVA4_P_ACKDETECT, ret_pkg.type); + continue; + } + } + + if (err != AVA4_SEND_OK) { + applog(LOG_DEBUG, "%s %d: AVA4_P_DETECT: Failed AUC xfer data with err %d", + avalon4->drv->name, avalon4->device_id, err); + break; + } + + applog(LOG_DEBUG, "%s %d: Module detect ID[%d]: %d", + avalon4->drv->name, avalon4->device_id, i, ret_pkg.type); + if (ret_pkg.type != AVA4_P_ACKDETECT) + break; + + cgtime(&info->elapsed[i]); + info->enable[i] = 1; + memcpy(info->mm_dna[i], ret_pkg.data, AVA4_MM_DNA_LEN); + info->mm_dna[i][AVA4_MM_DNA_LEN] = '\0'; + memcpy(info->mm_version[i], ret_pkg.data + AVA4_MM_DNA_LEN, AVA4_MM_VER_LEN); + info->mm_version[i][AVA4_MM_VER_LEN] = '\0'; + if (!strncmp((char *)&(info->mm_version[i]), AVA4_MM40_PREFIXSTR, 2)) + info->mod_type[i] = AVA4_TYPE_MM40; + if (!strncmp((char *)&(info->mm_version[i]), AVA4_MM41_PREFIXSTR, 2)) + info->mod_type[i] = AVA4_TYPE_MM41; + + info->fan_pct[i] = AVA4_DEFAULT_FAN_START; + info->set_voltage[i] = opt_avalon4_voltage_min; + info->led_red[i] = 0; + applog(LOG_NOTICE, "%s %d: New module detect! ID[%d]", + avalon4->drv->name, avalon4->device_id, i); + } +} + +static int polling(struct thr_info *thr, struct cgpu_info *avalon4, struct avalon4_info *info) +{ + struct avalon4_pkg send_pkg; + struct avalon4_ret ar; + int i, j, tmp, ret, decode_err = 0, do_polling = 0; + struct timeval current_fan; + int do_adjust_fan = 0; + uint32_t fan_pwm; + double device_tdiff; + + if (info->polling_first) { + cgsleep_ms(300); + info->polling_first = 0; + } + + cgtime(¤t_fan); + device_tdiff = tdiff(¤t_fan, &(info->last_fan)); + if (device_tdiff > 5.0 || device_tdiff < 0) { + cgtime(&info->last_fan); + do_adjust_fan = 1; + } + + for (i = 1; i < AVA4_DEFAULT_MODULARS; i++) { + if (!info->enable[i]) + continue; + + do_polling = 1; + cgsleep_ms(opt_avalon4_polling_delay); + + memset(send_pkg.data, 0, AVA4_P_DATA_LEN); + /* Red LED */ + tmp = be32toh(info->led_red[i]); + memcpy(send_pkg.data, &tmp, 4); + + /* Adjust fan every 10 seconds*/ + if (do_adjust_fan) { + fan_pwm = adjust_fan(info, i); + fan_pwm |= 0x80000000; + tmp = be32toh(fan_pwm); + memcpy(send_pkg.data + 4, &tmp, 4); + } + + avalon4_init_pkg(&send_pkg, AVA4_P_POLLING, 1, 1); + ret = avalon4_iic_xfer_pkg(avalon4, i, &send_pkg, &ar); + if (ret == AVA4_SEND_OK) + decode_err = decode_pkg(thr, &ar, i); + + if (ret != AVA4_SEND_OK || decode_err) { + info->polling_err_cnt[i]++; + if (info->polling_err_cnt[i] >= 4) { + info->polling_err_cnt[i] = 0; + info->mod_type[i] = AVA4_TYPE_NULL; + info->enable[i] = 0; + info->get_voltage[i] = 0; + info->get_frequency[i] = 0; + info->power_good[i] = 0; + info->local_work[i] = 0; + info->local_works[i] = 0; + info->hw_work[i] = 0; + info->hw_works[i] = 0; + for (j = 0; j < 6; j++) { + info->lw5[i][j] = 0; + info->hw5[i][j] = 0; + } + + for (j = 0; j < AVA4_DEFAULT_MINERS; j++) { + info->matching_work[i][j] = 0; + info->chipmatching_work[i][j][0] = 0; + info->chipmatching_work[i][j][1] = 0; + info->chipmatching_work[i][j][2] = 0; + info->chipmatching_work[i][j][3] = 0; + } + applog(LOG_NOTICE, "%s %d: Module detached! ID[%d]", + avalon4->drv->name, avalon4->device_id, i); + } + } + + if (ret == AVA4_SEND_OK && !decode_err) + info->polling_err_cnt[i] = 0; + } + + if (!do_polling) + detect_modules(avalon4); + + return 0; +} + +static void copy_pool_stratum(struct pool *pool_stratum, struct pool *pool) +{ + int i; + int merkles = pool->merkles; + size_t coinbase_len = pool->coinbase_len; + + if (!pool->swork.job_id) + return; + + if (!job_idcmp((unsigned char *)pool->swork.job_id, pool_stratum->swork.job_id)) + return; + + cg_wlock(&pool_stratum->data_lock); + free(pool_stratum->swork.job_id); + free(pool_stratum->nonce1); + free(pool_stratum->coinbase); + + align_len(&coinbase_len); + pool_stratum->coinbase = calloc(coinbase_len, 1); + if (unlikely(!pool_stratum->coinbase)) + quit(1, "Failed to calloc pool_stratum coinbase in avalon4"); + memcpy(pool_stratum->coinbase, pool->coinbase, coinbase_len); + + + for (i = 0; i < pool_stratum->merkles; i++) + free(pool_stratum->swork.merkle_bin[i]); + if (merkles) { + pool_stratum->swork.merkle_bin = realloc(pool_stratum->swork.merkle_bin, + sizeof(char *) * merkles + 1); + for (i = 0; i < merkles; i++) { + pool_stratum->swork.merkle_bin[i] = malloc(32); + if (unlikely(!pool_stratum->swork.merkle_bin[i])) + quit(1, "Failed to malloc pool_stratum swork merkle_bin"); + memcpy(pool_stratum->swork.merkle_bin[i], pool->swork.merkle_bin[i], 32); + } + } + + pool_stratum->sdiff = pool->sdiff; + pool_stratum->coinbase_len = pool->coinbase_len; + pool_stratum->nonce2_offset = pool->nonce2_offset; + pool_stratum->n2size = pool->n2size; + pool_stratum->merkles = pool->merkles; + + pool_stratum->swork.job_id = strdup(pool->swork.job_id); + pool_stratum->nonce1 = strdup(pool->nonce1); + + memcpy(pool_stratum->ntime, pool->ntime, sizeof(pool_stratum->ntime)); + memcpy(pool_stratum->header_bin, pool->header_bin, sizeof(pool_stratum->header_bin)); + cg_wunlock(&pool_stratum->data_lock); +} + +static void avalon4_stratum_set(struct cgpu_info *avalon4, struct pool *pool, int addr, int cutoff) +{ + struct avalon4_info *info = avalon4->device_data; + struct avalon4_pkg send_pkg; + uint32_t tmp = 0, range, start, volt; + + info->set_frequency[0] = opt_avalon4_freq[0]; + info->set_frequency[1] = opt_avalon4_freq[1]; + info->set_frequency[2] = opt_avalon4_freq[2]; + + /* Set the NTime, Voltage and Frequency */ + memset(send_pkg.data, 0, AVA4_P_DATA_LEN); + + if (opt_avalon4_ntime_offset != AVA4_DEFAULT_ASIC_COUNT) { + tmp = opt_avalon4_ntime_offset | 0x80000000; + tmp = be32toh(tmp); + memcpy(send_pkg.data, &tmp, 4); + } + + volt = info->set_voltage[addr]; + if (cutoff) + volt = 0; + if (info->mod_type[addr] == AVA4_TYPE_MM40) + tmp = encode_voltage_adp3208d(volt); + if (info->mod_type[addr] == AVA4_TYPE_MM41) + tmp = encode_voltage_ncp5392p(volt); + tmp = be32toh(tmp); + memcpy(send_pkg.data + 4, &tmp, 4); + + tmp = info->set_frequency[0] | (info->set_frequency[1] << 10) | (info->set_frequency[2] << 20); + tmp = be32toh(tmp); + memcpy(send_pkg.data + 8, &tmp, 4); + + /* Configure the nonce2 offset and range */ + if (pool->n2size == 3) + range = 0xffffff / (total_devices + 1); + else + range = 0xffffffff / (total_devices + 1); + start = range * (avalon4->device_id + 1); + + tmp = be32toh(start); + memcpy(send_pkg.data + 12, &tmp, 4); + + tmp = be32toh(range); + memcpy(send_pkg.data + 16, &tmp, 4); + + /* Package the data */ + avalon4_init_pkg(&send_pkg, AVA4_P_SET, 1, 1); + if (addr == AVA4_MODULE_BROADCAST) + avalon4_send_bc_pkgs(avalon4, &send_pkg); + else + avalon4_iic_xfer_pkg(avalon4, addr, &send_pkg, NULL); +} + +static void avalon4_stratum_finish(struct cgpu_info *avalon4) +{ + struct avalon4_pkg send_pkg; + + memset(send_pkg.data, 0, AVA4_P_DATA_LEN); + avalon4_init_pkg(&send_pkg, AVA4_P_FINISH, 1, 1); + avalon4_send_bc_pkgs(avalon4, &send_pkg); +} + +static void avalon4_update(struct cgpu_info *avalon4) +{ + struct avalon4_info *info = avalon4->device_data; + struct thr_info *thr = avalon4->thr[0]; + struct work *work; + struct pool *pool; + int coinbase_len_posthash, coinbase_len_prehash; + int i, count = 0; + + applog(LOG_DEBUG, "Avalon4: New stratum: restart: %d, update: %d", + thr->work_restart, thr->work_update); + thr->work_update = false; + thr->work_restart = false; + + /* Step 1: Make sure pool is ready */ + work = get_work(thr, thr->id); + discard_work(work); /* Don't leak memory */ + + /* Step 2: MM protocol check */ + pool = current_pool(); + if (!pool->has_stratum) + quit(1, "Avalon4: MM has to use stratum pools"); + + coinbase_len_prehash = pool->nonce2_offset - (pool->nonce2_offset % SHA256_BLOCK_SIZE); + coinbase_len_posthash = pool->coinbase_len - coinbase_len_prehash; + + if (coinbase_len_posthash + SHA256_BLOCK_SIZE > AVA4_P_COINBASE_SIZE) { + applog(LOG_ERR, "Avalon4: MM pool modified coinbase length(%d) is more than %d", + coinbase_len_posthash + SHA256_BLOCK_SIZE, AVA4_P_COINBASE_SIZE); + return; + } + if (pool->merkles > AVA4_P_MERKLES_COUNT) { + applog(LOG_ERR, "Avalon4: MM merkles has to be less then %d", AVA4_P_MERKLES_COUNT); + return; + } + if (pool->n2size < 3) { + applog(LOG_ERR, "Avalon4: MM nonce2 size has to be >= 3 (%d)", pool->n2size); + return; + } + + /* Step 3: Send out stratum pkgs */ + cg_wlock(&info->update_lock); + cg_rlock(&pool->data_lock); + + cgtime(&info->last_stratum); + info->pool_no = pool->pool_no; + copy_pool_stratum(&info->pool2, &info->pool1); + copy_pool_stratum(&info->pool1, &info->pool0); + copy_pool_stratum(&info->pool0, pool); + avalon4_stratum_pkgs(avalon4, pool); + + cg_runlock(&pool->data_lock); + cg_wunlock(&info->update_lock); + + /* Step 4: Try to detect new modules */ + detect_modules(avalon4); + + /* Step 5: Configure the parameter from outside */ + avalon4_stratum_set(avalon4, pool, AVA4_MODULE_BROADCAST, 0); + + if (!info->set_voltage_broadcat) { + for (i = 1; i < AVA4_DEFAULT_MODULARS; i++) { + if (!info->enable[i]) + continue; + if (info->set_voltage[i] == info->set_voltage[0]) + continue; + + avalon4_stratum_set(avalon4, pool, i, 0); + } + } else { + for (i = 1; i < AVA4_DEFAULT_MODULARS; i++) { + if (!info->enable[i]) + continue; + if (info->mod_type[i] == AVA4_TYPE_MM40) + continue; + + avalon4_stratum_set(avalon4, pool, i, 0); + } + } + + for (i = 1; i < AVA4_DEFAULT_MODULARS; i++) { + if (!info->enable[i]) + continue; + + count++; + if (info->temp[i] < opt_avalon4_overheat) + continue; + + avalon4_stratum_set(avalon4, pool, i, 1); + } + info->mm_count = count; + + /* Step 6: Send out finish pkg */ + avalon4_stratum_finish(avalon4); +} + +static int64_t avalon4_scanhash(struct thr_info *thr) +{ + struct cgpu_info *avalon4 = thr->cgpu; + struct avalon4_info *info = avalon4->device_data; + struct timeval current; + double device_tdiff, hwp; + uint32_t a = 0, b = 0; + uint64_t h; + int i, j; + + if (unlikely(avalon4->usbinfo.nodev)) { + applog(LOG_ERR, "%s-%d: Device disappeared, shutting down thread", + avalon4->drv->name, avalon4->device_id); + return -1; + } + + /* Stop polling the device if there is no stratum in 3 minutes, network is down */ + cgtime(¤t); + if (tdiff(¤t, &(info->last_stratum)) > 180.0) + return 0; + + cg_rlock(&info->update_lock); + polling(thr, avalon4, info); + cg_runlock(&info->update_lock); + + cgtime(¤t); + device_tdiff = tdiff(¤t, &(info->last_1m)); + if (device_tdiff >= 60.0 || device_tdiff < 0) { + copy_time(&info->last_1m, ¤t); + if (info->i_1m++ >= 6) + info->i_1m = 0; + + for (i = 1; i < AVA4_DEFAULT_MODULARS; i++) { + if (!info->enable[i]) + continue; + + info->lw5[i][info->i_1m] = 0; + info->hw5[i][info->i_1m] = 0; + } + } + + cgtime(¤t); + device_tdiff = tdiff(¤t, &(info->last_5m)); + if (opt_avalon4_autov && (device_tdiff > 480.0 || device_tdiff < 0)) { + copy_time(&info->last_5m, ¤t); + + for (i = 1; i < AVA4_DEFAULT_MODULARS; i++) { + if (!info->enable[i]) + continue; + + a = 0; + b = 0; + for (j = 0; j < 6; j++) { + a += info->lw5[i][j]; + b += info->hw5[i][j]; + } + + hwp = a ? (double)b / (double)a : 0; + if (hwp > AVA4_DH_INC && (info->set_voltage[i] < info->set_voltage[0] + 125)) { + info->set_voltage[i] += 125; + applog(LOG_NOTICE, "%s %d: Automatic increase module[%d] voltage to %d", + avalon4->drv->name, avalon4->device_id, i, info->set_voltage[i]); + } + if (hwp < AVA4_DH_DEC && (info->set_voltage[i] > info->set_voltage[0] - (4 * 125))) { + info->set_voltage[i] -= 125; + applog(LOG_NOTICE, "%s %d: Automatic decrease module[%d] voltage to %d", + avalon4->drv->name, avalon4->device_id, i, info->set_voltage[i]); + } + + } + } + + for (i = 1; i < AVA4_DEFAULT_MODULARS; i++) { + if (info->set_voltage[i] != info->set_voltage[0]) + break; + } + + if (i < AVA4_DEFAULT_MODULARS) + info->set_voltage_broadcat = 0; + else + info->set_voltage_broadcat = 1; + + h = 0; + for (i = 1; i < AVA4_DEFAULT_MODULARS; i++) { + h += info->enable[i] ? (info->local_work[i] - info->hw_work[i]) : 0; + info->local_work[i] = 0; + info->hw_work[i] = 0; + } + return h * 0xffffffffull; +} + +#define STATBUFLEN 512 +static struct api_data *avalon4_api_stats(struct cgpu_info *cgpu) +{ + struct api_data *root = NULL; + struct avalon4_info *info = cgpu->device_data; + int i, j; + uint32_t a,b ; + double hwp, diff; + char buf[256]; + char statbuf[AVA4_DEFAULT_MODULARS][STATBUFLEN]; + struct timeval current; + + memset(statbuf, 0, AVA4_DEFAULT_MODULARS * STATBUFLEN); + + for (i = 1; i < AVA4_DEFAULT_MODULARS; i++) { + if(info->mod_type[i] == AVA4_TYPE_NULL) + continue; + sprintf(buf, "Ver[%s]", info->mm_version[i]); + strcat(statbuf[i], buf); + } + + for (i = 1; i < AVA4_DEFAULT_MODULARS; i++) { + if(info->mod_type[i] == AVA4_TYPE_NULL) + continue; + sprintf(buf, " DNA[%02x%02x%02x%02x%02x%02x%02x%02x]", + info->mm_dna[i][0], + info->mm_dna[i][1], + info->mm_dna[i][2], + info->mm_dna[i][3], + info->mm_dna[i][4], + info->mm_dna[i][5], + info->mm_dna[i][6], + info->mm_dna[i][7]); + strcat(statbuf[i], buf); + } + + for (i = 1; i < AVA4_DEFAULT_MODULARS; i++) { + struct timeval now; + if (info->mod_type[i] == AVA4_TYPE_NULL) + continue; + + cgtime(&now); + sprintf(buf, " Elapsed[%.0f]", tdiff(&now, &(info->elapsed[i]))); + strcat(statbuf[i], buf); + } + +#if 0 + for (i = 1; i < AVA4_DEFAULT_MODULARS; i++) { + if (info->mod_type[i] == AVA4_TYPE_NULL) + continue; + + strcat(statbuf[i], " MW["); + for (j = 0; j < AVA4_DEFAULT_MINERS; j++) { + sprintf(buf, "%d ", info->matching_work[i][j]); + strcat(statbuf[i], buf); + } + statbuf[i][strlen(statbuf[i]) - 1] = ']'; + } +#endif + + for (i = 1; i < AVA4_DEFAULT_MODULARS; i++) { + if(info->mod_type[i] == AVA4_TYPE_NULL) + continue; + sprintf(buf, " LW[%"PRIu64"]", info->local_works[i]); + strcat(statbuf[i], buf); + } + for (i = 1; i < AVA4_DEFAULT_MODULARS; i++) { + if(info->mod_type[i] == AVA4_TYPE_NULL) + continue; + sprintf(buf, " HW[%"PRIu64"]", info->hw_works[i]); + strcat(statbuf[i], buf); + } + for (i = 1; i < AVA4_DEFAULT_MODULARS; i++) { + if(info->mod_type[i] == AVA4_TYPE_NULL) + continue; + a = info->hw_works[i]; + b = info->local_works[i]; + hwp = b ? ((double)a / (double)b) * 100: 0; + + sprintf(buf, " DH[%.3f%%]", hwp); + strcat(statbuf[i], buf); + } + for (i = 1; i < AVA4_DEFAULT_MODULARS; i++) { + if(info->mod_type[i] == AVA4_TYPE_NULL) + continue; + + a = 0; + b = 0; + for (j = 0; j < 6; j++) { + a += info->lw5[i][j]; + b += info->hw5[i][j]; + } + + cgtime(¤t); + diff = tdiff(¤t, &(info->last_1m)) + 300.0; + + hwp = a ? (double)b / (double)a * 100 : 0; + + sprintf(buf, " GHS5m[%.2f] DH5m[%.3f%%]", ((double)a - (double)b) * 4.295 / diff, hwp); + strcat(statbuf[i], buf); + } + for (i = 1; i < AVA4_DEFAULT_MODULARS; i++) { + if(info->mod_type[i] == AVA4_TYPE_NULL) + continue; + sprintf(buf, " Temp[%d]", info->temp[i]); + strcat(statbuf[i], buf); + } + for (i = 1; i < AVA4_DEFAULT_MODULARS; i++) { + if(info->mod_type[i] == AVA4_TYPE_NULL) + continue; + sprintf(buf, " Fan[%d]", info->fan[i]); + strcat(statbuf[i], buf); + } + for (i = 1; i < AVA4_DEFAULT_MODULARS; i++) { + if(info->mod_type[i] == AVA4_TYPE_NULL) + continue; + sprintf(buf, " Vol[%.4f]", (float)info->get_voltage[i] / 10000); + strcat(statbuf[i], buf); + } + for (i = 1; i < AVA4_DEFAULT_MODULARS; i++) { + if(info->mod_type[i] == AVA4_TYPE_NULL) + continue; + sprintf(buf, " Freq[%.2f]", (float)info->get_frequency[i] / 1000); + strcat(statbuf[i], buf); + } + for (i = 1; i < AVA4_DEFAULT_MODULARS; i++) { + if(info->mod_type[i] == AVA4_TYPE_NULL) + continue; + sprintf(buf, " PG[%d]", info->power_good[i]); + strcat(statbuf[i], buf); + } + for (i = 1; i < AVA4_DEFAULT_MODULARS; i++) { + if(info->mod_type[i] == AVA4_TYPE_NULL) + continue; + sprintf(buf, " Led[%d]", info->led_red[i]); + strcat(statbuf[i], buf); + } + + for (i = 1; i < AVA4_DEFAULT_MODULARS; i++) { + if(info->mod_type[i] == AVA4_TYPE_NULL) + continue; + sprintf(buf, "MM ID%d", i); + root = api_add_string(root, buf, statbuf[i], true); + } + + root = api_add_int(root, "MM Count", &(info->mm_count), true); + root = api_add_bool(root, "Automatic Voltage", &opt_avalon4_autov, true); + root = api_add_string(root, "AUC VER", info->auc_version, false); + root = api_add_int(root, "AUC I2C Speed", &(info->auc_speed), true); + root = api_add_int(root, "AUC I2C XDelay", &(info->auc_xdelay), true); + root = api_add_int(root, "AUC ADC", &(info->auc_temp), true); + + return root; +} + +static char *avalon4_set_device(struct cgpu_info *avalon4, char *option, char *setting, char *replybuf) +{ + int val, i; + struct avalon4_info *info = avalon4->device_data; + + if (strcasecmp(option, "help") == 0) { + sprintf(replybuf, "led|fan|voltage|frequency|pdelay"); + return replybuf; + } + + if (strcasecmp(option, "pdelay") == 0) { + if (!setting || !*setting) { + sprintf(replybuf, "missing polling delay setting"); + return replybuf; + } + + val = atoi(setting); + if (val < 1 || val > 65535) { + sprintf(replybuf, "invalid polling delay: %d, valid range 1-65535", val); + return replybuf; + } + + opt_avalon4_polling_delay = val; + + applog(LOG_NOTICE, "%s %d: Update polling delay to: %d", + avalon4->drv->name, avalon4->device_id, val); + + return NULL; + } + + if (strcasecmp(option, "fan") == 0) { + if (!setting || !*setting) { + sprintf(replybuf, "missing fan value"); + return replybuf; + } + + if (set_avalon4_fan(setting)) { + sprintf(replybuf, "invalid fan value, valid range 0-100"); + return replybuf; + } + + applog(LOG_NOTICE, "%s %d: Update fan to %d-%d", + avalon4->drv->name, avalon4->device_id, + opt_avalon4_fan_min, opt_avalon4_fan_max); + + return NULL; + } + + if (strcasecmp(option, "frequency") == 0) { + if (!setting || !*setting) { + sprintf(replybuf, "missing frequency value"); + return replybuf; + } + + if (set_avalon4_freq(setting)) { + sprintf(replybuf, "invalid frequency value, valid range %d-%d", + AVA4_DEFAULT_FREQUENCY_MIN, AVA4_DEFAULT_FREQUENCY_MAX); + return replybuf; + } + + applog(LOG_NOTICE, "%s %d: Update frequency to %d", + avalon4->drv->name, avalon4->device_id, + (opt_avalon4_freq[0] * 4 + opt_avalon4_freq[1] * 4 + opt_avalon4_freq[2]) / 9); + + return NULL; + } + + if (strcasecmp(option, "led") == 0) { + if (!setting || !*setting) { + sprintf(replybuf, "missing module_id setting"); + return replybuf; + } + + val = atoi(setting); + if (val < 1 || val >= AVA4_DEFAULT_MODULARS) { + sprintf(replybuf, "invalid module_id: %d, valid range 1-%d", val, AVA4_DEFAULT_MODULARS); + return replybuf; + } + + if (!info->enable[val]) { + sprintf(replybuf, "the current module was disabled %d", val); + return replybuf; + } + + info->led_red[val] = !info->led_red[val]; + + applog(LOG_NOTICE, "%s %d: Module:%d, LED: %s", + avalon4->drv->name, avalon4->device_id, + val, info->led_red[val] ? "on" : "off"); + + return NULL; + } + + if (strcasecmp(option, "voltage") == 0) { + int val_mod, val_volt, ret; + + if (!setting || !*setting) { + sprintf(replybuf, "missing voltage value"); + return replybuf; + } + + ret = sscanf(setting, "%d-%d", &val_mod, &val_volt); + if (ret != 2) { + sprintf(replybuf, "invalid voltage parameter, format: moduleid-voltage"); + return replybuf; + } + + if (val_mod < 0 || val_mod >= AVA4_DEFAULT_MODULARS || + val_volt < AVA4_DEFAULT_VOLTAGE_MIN || val_volt > AVA4_DEFAULT_VOLTAGE_MAX) { + sprintf(replybuf, "invalid module_id or voltage value, valid module_id range %d-%d, valid voltage range %d-%d", + 0, AVA4_DEFAULT_MODULARS, + AVA4_DEFAULT_VOLTAGE_MIN, AVA4_DEFAULT_VOLTAGE_MAX); + return replybuf; + } + + if (!info->enable[val_mod]) { + sprintf(replybuf, "the current module was disabled %d", val_mod); + return replybuf; + } + + info->set_voltage[val_mod] = val_volt; + + if (val_mod == AVA4_MODULE_BROADCAST) { + for (i = 1; i < AVA4_DEFAULT_MODULARS; i++) + info->set_voltage[i] = val_volt; + info->set_voltage_broadcat = 1; + } else + info->set_voltage_broadcat = 0; + + applog(LOG_NOTICE, "%s %d: Update module[%d] voltage to %d", + avalon4->drv->name, avalon4->device_id, val_mod, val_volt); + + return NULL; + } + + sprintf(replybuf, "Unknown option: %s", option); + return replybuf; +} + +static void avalon4_statline_before(char *buf, size_t bufsiz, struct cgpu_info *avalon4) +{ + struct avalon4_info *info = avalon4->device_data; + int temp = get_current_temp_max(info); + int voltsmin = AVA4_DEFAULT_VOLTAGE_MAX, voltsmax = AVA4_DEFAULT_VOLTAGE_MIN; + int fanmin = AVA4_DEFAULT_FAN_MAX, fanmax = AVA4_DEFAULT_FAN_MIN; + int i, frequency; + + for (i = 1; i < AVA4_DEFAULT_MODULARS; i++) { + if (!info->enable[i]) + continue; + + if (fanmax <= info->fan_pct[i]) + fanmax = info->fan_pct[i]; + if (fanmin >= info->fan_pct[i]) + fanmin = info->fan_pct[i]; + + if (voltsmax <= info->get_voltage[i]) + voltsmax = info->get_voltage[i]; + if (voltsmin >= info->get_voltage[i]) + voltsmin = info->get_voltage[i]; + } +#if 0 + tailsprintf(buf, bufsiz, "%2dMMs %.4fV-%.4fV %4dMhz %2dC %3d%%-%3d%%", + info->mm_count, (float)voltsmin / 10000, (float)voltsmax / 10000, + (info->set_frequency[0] * 4 + info->set_frequency[1] * 4 + info->set_frequency[2]) / 9, + temp, fanmin, fanmax); +#endif + frequency = (info->set_frequency[0] * 4 + info->set_frequency[1] * 4 + info->set_frequency[2]) / 9; + tailsprintf(buf, bufsiz, "%4dMhz %2dC %3d%% %.3fV", frequency, + temp, fanmin, (float)voltsmax / 10000); +} + +struct device_drv avalon4_drv = { + .drv_id = DRIVER_avalon4, + .dname = "avalon4", + .name = "AV4", + .set_device = avalon4_set_device, + .get_api_stats = avalon4_api_stats, + .get_statline_before = avalon4_statline_before, + .drv_detect = avalon4_detect, + .thread_prepare = avalon4_prepare, + .hash_work = hash_driver_work, + .flush_work = avalon4_update, + .update_work = avalon4_update, + .scanwork = avalon4_scanhash, +}; diff --git a/driver-avalon4.h b/driver-avalon4.h new file mode 100644 index 0000000..3bc7da0 --- /dev/null +++ b/driver-avalon4.h @@ -0,0 +1,200 @@ +/* + * Copyright 2013-2014 Con Kolivas + * Copyright 2012-2014 Xiangfu + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#ifndef _AVALON4_H_ +#define _AVALON4_H_ + +#include "util.h" + +#ifdef USE_AVALON4 + +#define AVA4_DEFAULT_FAN_MIN 5 /* % */ +#define AVA4_DEFAULT_FAN_MAX 85 +/* Percentage required to make sure fan starts spinning, then we can go down */ +#define AVA4_DEFAULT_FAN_START 15 + +#define AVA4_DEFAULT_TEMP_TARGET 42 +#define AVA4_DEFAULT_TEMP_OVERHEAT 65 + +#define AVA4_DEFAULT_VOLTAGE_MIN 4000 +#define AVA4_DEFAULT_VOLTAGE_MAX 9000 + +#define AVA4_DEFAULT_FREQUENCY_MIN 100 +#define AVA4_DEFAULT_FREQUENCY_MAX 1000 + +#define AVA4_DEFAULT_MODULARS 64 +#define AVA4_DEFAULT_MINERS 10 +#define AVA4_DEFAULT_ASIC_COUNT 4 + +#define AVA4_DEFAULT_VOLTAGE 6875 +#define AVA4_DEFAULT_FREQUENCY 200 +#define AVA4_DEFAULT_POLLING_DELAY 20 /* ms */ + +#define AVA4_DH_INC 0.03 +#define AVA4_DH_DEC 0.001 + +#define AVA4_PWM_MAX 0x3FF + +#define AVA4_AUC_VER_LEN 12 /* Version length: 12 (AUC-YYYYMMDD) */ +#define AVA4_AUC_SPEED 400000 +#define AVA4_AUC_XDELAY 9600 /* 4800 = 1ms in AUC (11U14) */ +#define AVA4_AUC_P_SIZE 64 + + +/* Avalon4 protocol package type from MM protocol.h + * https://github.com/Canaan-Creative/MM/blob/avalon4/firmware/protocol.h */ +#define AVA4_MM_VER_LEN 15 +#define AVA4_MM_DNA_LEN 8 +#define AVA4_H1 'C' +#define AVA4_H2 'N' + +#define AVA4_P_COINBASE_SIZE (6 * 1024 + 64) +#define AVA4_P_MERKLES_COUNT 30 + +#define AVA4_P_COUNT 40 +#define AVA4_P_DATA_LEN 32 + +/* Broadcase with block iic_write*/ +#define AVA4_P_DETECT 0x10 + +/* Broadcase With non-block iic_write*/ +#define AVA4_P_STATIC 0x11 +#define AVA4_P_JOB_ID 0x12 +#define AVA4_P_COINBASE 0x13 +#define AVA4_P_MERKLES 0x14 +#define AVA4_P_HEADER 0x15 +#define AVA4_P_TARGET 0x16 + +/* Broadcase or Address */ +#define AVA4_P_SET 0x20 +#define AVA4_P_FINISH 0x21 + +/* Have to with I2C address */ +#define AVA4_P_POLLING 0x30 +#define AVA4_P_REQUIRE 0x31 +#define AVA4_P_TEST 0x32 + +/* Back to host */ +#define AVA4_P_ACKDETECT 0x40 +#define AVA4_P_STATUS 0x41 +#define AVA4_P_NONCE 0x42 +#define AVA4_P_TEST_RET 0x43 + +#define AVA4_MODULE_BROADCAST 0 +/* Endof Avalon4 protocol package type */ + +#define AVA4_MM40_PREFIXSTR "40" +#define AVA4_MM41_PREFIXSTR "41" +#define AVA4_MM_VERNULL "NONE" + +#define AVA4_TYPE_MM40 40 +#define AVA4_TYPE_MM41 41 +#define AVA4_TYPE_NULL 00 + +#define AVA4_IIC_RESET 0xa0 +#define AVA4_IIC_INIT 0xa1 +#define AVA4_IIC_DEINIT 0xa2 +#define AVA4_IIC_XFER 0xa5 +#define AVA4_IIC_INFO 0xa6 + +struct avalon4_pkg { + uint8_t head[2]; + uint8_t type; + uint8_t opt; + uint8_t idx; + uint8_t cnt; + uint8_t data[32]; + uint8_t crc[2]; +}; +#define avalon4_ret avalon4_pkg + +struct avalon4_info { + cglock_t update_lock; + + int polling_first; + int polling_err_cnt[AVA4_DEFAULT_MODULARS]; + int xfer_err_cnt; + + int pool_no; + struct pool pool0; + struct pool pool1; + struct pool pool2; + + struct timeval last_fan; + struct timeval last_stratum; + + char auc_version[AVA4_AUC_VER_LEN + 1]; + int auc_speed; + int auc_xdelay; + int auc_temp; + + int mm_count; + + int set_frequency[3]; + int set_voltage[AVA4_DEFAULT_MODULARS]; + int set_voltage_broadcat; + + int mod_type[AVA4_DEFAULT_MODULARS]; + bool enable[AVA4_DEFAULT_MODULARS]; + + struct timeval elapsed[AVA4_DEFAULT_MODULARS]; + char mm_version[AVA4_DEFAULT_MODULARS][AVA4_MM_VER_LEN + 1]; + uint8_t mm_dna[AVA4_DEFAULT_MODULARS][AVA4_MM_DNA_LEN + 1]; + int get_voltage[AVA4_DEFAULT_MODULARS]; + int get_frequency[AVA4_DEFAULT_MODULARS]; + int power_good[AVA4_DEFAULT_MODULARS]; + int fan_pct[AVA4_DEFAULT_MODULARS]; + int fan[AVA4_DEFAULT_MODULARS]; + int temp[AVA4_DEFAULT_MODULARS]; + int led_red[AVA4_DEFAULT_MODULARS]; + + uint64_t local_works[AVA4_DEFAULT_MODULARS]; + uint64_t hw_works[AVA4_DEFAULT_MODULARS]; + + uint32_t local_work[AVA4_DEFAULT_MODULARS]; + uint32_t hw_work[AVA4_DEFAULT_MODULARS]; + + uint32_t lw5[AVA4_DEFAULT_MODULARS][6]; + uint32_t hw5[AVA4_DEFAULT_MODULARS][6]; + int i_1m; + struct timeval last_5m; + struct timeval last_1m; + + int matching_work[AVA4_DEFAULT_MODULARS][AVA4_DEFAULT_MINERS]; + int chipmatching_work[AVA4_DEFAULT_MODULARS][AVA4_DEFAULT_MINERS][4]; +}; + +struct avalon4_iic_info { + uint8_t iic_op; + union { + uint32_t aucParam[2]; + uint8_t slave_addr; + } iic_param; +}; + +#define AVA4_WRITE_SIZE (sizeof(struct avalon4_pkg)) +#define AVA4_READ_SIZE AVA4_WRITE_SIZE + +#define AVA4_SEND_OK 0 +#define AVA4_SEND_ERROR -1 + +extern char *set_avalon4_fan(char *arg); +extern char *set_avalon4_temp(char *arg); +extern char *set_avalon4_freq(char *arg); +extern char *set_avalon4_voltage(char *arg); +extern bool opt_avalon4_autov; +extern int opt_avalon4_temp_target; +extern int opt_avalon4_overheat; +extern int opt_avalon4_polling_delay; +extern int opt_avalon4_aucspeed; +extern int opt_avalon4_aucxdelay; +extern int opt_avalon4_ntime_offset; +#endif /* USE_AVALON4 */ +#endif /* _AVALON4_H_ */ diff --git a/driver-bab.c b/driver-bab.c new file mode 100644 index 0000000..1bba8e0 --- /dev/null +++ b/driver-bab.c @@ -0,0 +1,3062 @@ +/* + * Copyright 2013-2014 Andrew Smith + * Copyright 2013 bitfury + * + * BitFury GPIO code originally based on chainminer code: + * https://github.com/bfsb/chainminer + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#include "config.h" +#include "compat.h" +#include "miner.h" +#include "sha2.h" +#include "klist.h" +#include + +/* + * Tested on RPi running both Raspbian and Arch + * with BlackArrow BitFury V1 & V2 GPIO Controller + * with 16 chip BlackArrow BitFury boards + */ + +#ifndef LINUX +static void bab_detect(__maybe_unused bool hotplug) +{ +} +#else + +#include +#include +#include +#include +#include + +#define BAB_SPI_BUS 0 +#define BAB_SPI_CHIP 0 + +#define BAB_SPI_SPEED 96000 +#define BAB_SPI_BUFSIZ 1024 + +#define BAB_DELAY_USECS 0 +#define BAB_TRF_DELAY 0 + +#define BAB_ADDR(_n) (*((babinfo->gpio) + (_n))) + +#define BAB_INP_GPIO(_n) BAB_ADDR((_n) / 10) &= (~(7 << (((_n) % 10) * 3))) +#define BAB_OUT_GPIO(_n) BAB_ADDR((_n) / 10) |= (1 << (((_n) % 10) * 3)) +#define BAB_OUT_GPIO_V(_n, _v) BAB_ADDR((_n) / 10) |= (((_v) <= 3 ? (_v) + 4 : \ + ((_v) == 4 ? 3 : 2)) << (((_n) % 10) * 3)) + +#define BAB_GPIO_SET BAB_ADDR(7) +#define BAB_GPIO_CLR BAB_ADDR(10) +#define BAB_GPIO_LEVEL BAB_ADDR(13) + +// If the V1 test of this many chips finds no chips it will try V2 +#define BAB_V1_CHIP_TEST 32 + +//maximum number of chips per board +#define BAB_BOARDCHIPS 16 +#define BAB_MAXBUF (BAB_MAXCHIPS * 512) +#define BAB_V1_BANK 0 +//maximum number of alternative banks +#define BAB_MAXBANKS 4 +//maximum number of boards in a bank +#define BAB_BANKBOARDS 6 +//maximum number of chips on alternative bank +#define BAB_BANKCHIPS (BAB_BOARDCHIPS * BAB_BANKBOARDS) +//maximum number of chips +#define BAB_MAXCHIPS (BAB_MAXBANKS * BAB_BANKCHIPS) +#define BAB_CORES 16 +#define BAB_X_COORD 21 +#define BAB_Y_COORD 36 + +#define BAB_NOOP 0 +#define BAB_BREAK ((uint8_t *)"\04") +#define BAB_ASYNC ((uint8_t *)"\05") +#define BAB_SYNC ((uint8_t *)"\06") + +#define BAB_FFL " - from %s %s() line %d" +#define BAB_FFL_HERE __FILE__, __func__, __LINE__ +#define BAB_FFL_PASS file, func, line + +#define bab_reset(_bank, _times) _bab_reset(babcgpu, babinfo, _bank, _times) +#define bab_txrx(_item, _det) _bab_txrx(babcgpu, babinfo, _item, _det, BAB_FFL_HERE) +#define bab_add_buf(_item, _data) _bab_add_buf(_item, _data, sizeof(_data)-1, BAB_FFL_HERE) +#define BAB_ADD_BREAK(_item) _bab_add_buf(_item, BAB_BREAK, 1, BAB_FFL_HERE) +#define BAB_ADD_ASYNC(_item) _bab_add_buf(_item, BAB_ASYNC, 1, BAB_FFL_HERE) +#define bab_config_reg(_item, _reg, _ena) _bab_config_reg(_item, _reg, _ena, BAB_FFL_HERE) +#define bab_add_data(_item, _addr, _data, _siz) _bab_add_data(_item, _addr, (const uint8_t *)(_data), _siz, BAB_FFL_HERE) + +#define BAB_ADD_NOOPs(_item, _count) _bab_add_noops(_item, _count, BAB_FFL_HERE) + +#define BAB_ADD_MIN 4 +#define BAB_ADD_MAX 128 + +#define BAB_BASEA 4 +#define BAB_BASEB 61 +#define BAB_COUNTERS 16 +static const uint8_t bab_counters[BAB_COUNTERS] = { + 64, 64, + BAB_BASEA, BAB_BASEA+4, + BAB_BASEA+2, BAB_BASEA+2+16, + BAB_BASEA, BAB_BASEA+1, + (BAB_BASEB)%65, (BAB_BASEB+1)%65, + (BAB_BASEB+3)%65, (BAB_BASEB+3+16)%65, + (BAB_BASEB+4)%65, (BAB_BASEB+4+4)%65, + (BAB_BASEB+3+3)%65, (BAB_BASEB+3+1+3)%65 +}; + +#define BAB_W1 16 +static const uint32_t bab_w1[BAB_W1] = { + 0, 0, 0, 0xffffffff, + 0x80000000, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0x00000280 +}; + +#define BAB_W2 8 +static const uint32_t bab_w2[BAB_W2] = { + 0x80000000, 0, 0, 0, + 0, 0, 0, 0x00000100 +}; + +#define BAB_TEST_DATA 19 +static const uint32_t bab_test_data[BAB_TEST_DATA] = { + 0xb0e72d8e, 0x1dc5b862, 0xe9e7c4a6, 0x3050f1f5, + 0x8a1a6b7e, 0x7ec384e8, 0x42c1c3fc, 0x8ed158a1, + 0x8a1a6b7e, 0x6f484872, 0x4ff0bb9b, 0x12c97f07, + 0xb0e72d8e, 0x55d979bc, 0x39403296, 0x40f09e84, + 0x8a0bb7b7, 0x33af304f, 0x0b290c1a //, 0xf0c4e61f +}; + +/* + * maximum chip speed available for auto tuner + * speed/nrate/hrate/watt + * 53/ 97/ 100/ 84 + * 54/ 98/ 107/ 88 + * 55/ 99/ 115/ 93 + * 56/ 101/ 125/ 99 + */ +#define BAB_MAXSPEED 57 +#define BAB_DEFMAXSPEED 55 +#define BAB_DEFSPEED 53 +#define BAB_MINSPEED 52 +#define BAB_ABSMINSPEED 32 + +/* + * % of errors to tune the speed up or down + * 1.0 to 10.0 should average around 5.5% errors + */ +#define BAB_TUNEUP 1.0 +#define BAB_TUNEDOWN 10.0 + +#define MIDSTATE_BYTES 32 +#define MERKLE_OFFSET 64 +#define MERKLE_BYTES 12 +#define BLOCK_HEADER_BYTES 80 + +#define MIDSTATE_UINTS (MIDSTATE_BYTES / sizeof(uint32_t)) +#define DATA_UINTS ((BLOCK_HEADER_BYTES / sizeof(uint32_t)) - 1) + +// Auto adjust +#define BAB_AUTO_REG 0 +#define BAB_AUTO_VAL 0x01 +// iclk +#define BAB_ICLK_REG 1 +#define BAB_ICLK_VAL 0x02 +// No fast clock +#define BAB_FAST_REG 2 +#define BAB_FAST_VAL 0x04 +// Divide by 2 +#define BAB_DIV2_REG 3 +#define BAB_DIV2_VAL 0x08 +// Slow Clock +#define BAB_SLOW_REG 4 +#define BAB_SLOW_VAL 0x10 +// No oclk +#define BAB_OCLK_REG 6 +#define BAB_OCLK_VAL 0x20 +// Has configured +#define BAB_CFGD_VAL 0x40 + +#define BAB_DEFCONF (BAB_AUTO_VAL | \ + BAB_ICLK_VAL | \ + BAB_DIV2_VAL | \ + BAB_SLOW_VAL) + +#define BAB_REG_CLR_FROM 7 +#define BAB_REG_CLR_TO 11 + +#define BAB_AUTO_SET(_c) ((_c) & BAB_AUTO_VAL) +#define BAB_ICLK_SET(_c) ((_c) & BAB_ICLK_VAL) +#define BAB_FAST_SET(_c) ((_c) & BAB_FAST_VAL) +#define BAB_DIV2_SET(_c) ((_c) & BAB_DIV2_VAL) +#define BAB_SLOW_SET(_c) ((_c) & BAB_SLOW_VAL) +#define BAB_OCLK_SET(_c) ((_c) & BAB_OCLK_VAL) +#define BAB_CFGD_SET(_c) ((_c) & BAB_CFGD_VAL) + +#define BAB_AUTO_BIT(_c) (BAB_AUTO_SET(_c) ? true : false) +#define BAB_ICLK_BIT(_c) (BAB_ICLK_SET(_c) ? false : true) +#define BAB_FAST_BIT(_c) (BAB_FAST_SET(_c) ? true : false) +#define BAB_DIV2_BIT(_c) (BAB_DIV2_SET(_c) ? false : true) +#define BAB_SLOW_BIT(_c) (BAB_SLOW_SET(_c) ? true : false) +#define BAB_OCLK_BIT(_c) (BAB_OCLK_SET(_c) ? true : false) + +#define BAB_COUNT_ADDR 0x0100 +#define BAB_W1A_ADDR 0x1000 +#define BAB_W1B_ADDR 0x1400 +#define BAB_W2_ADDR 0x1900 +#define BAB_INP_ADDR 0x3000 +#define BAB_OSC_ADDR 0x6000 +#define BAB_REG_ADDR 0x7000 + +/* + * valid: 0x01 0x03 0x07 0x0F 0x1F 0x3F 0x7F 0xFF + * max { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x00 } + * max { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0x00 } + * avg { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x00 } + * slo { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0x00 } + * min { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } + * good: 0x1F (97) 0x3F (104) 0x7F (109) 0xFF (104) + */ + +#define BAB_OSC 8 +static const uint8_t bab_osc_bits[BAB_OSC] = + { 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF }; + +static const uint8_t bab_reg_ena[4] = { 0xc1, 0x6a, 0x59, 0xe3 }; +static const uint8_t bab_reg_dis[4] = { 0x00, 0x00, 0x00, 0x00 }; + +#define BAB_NONCE_OFFSETS 3 +#define BAB_OFF_0x1C_STA 2 +#define BAB_OFF_0x1C_FIN 2 +#define BAB_OFF_OTHER_STA 0 +#define BAB_OFF_OTHER_FIN 1 + +#define BAB_EVIL_NONCE 0xe0 +#define BAB_EVIL_MASK 0xff + +static const uint32_t bab_nonce_offsets[] = {-0x800000, 0, -0x400000}; + +struct bab_work_send { + uint32_t midstate[MIDSTATE_UINTS]; + uint32_t ms3steps[MIDSTATE_UINTS]; + uint32_t merkle7; + uint32_t ntime; + uint32_t bits; +}; + +#define BAB_REPLY_NONCES 16 +struct bab_work_reply { + uint32_t nonce[BAB_REPLY_NONCES]; + uint32_t jobsel; + uint32_t spichk; +}; + +#define BAB_CHIP_MIN (sizeof(struct bab_work_reply)+16) + +#define ALLOC_WITEMS 1024 +#define LIMIT_WITEMS 0 + +// Work +typedef struct witem { + struct work *work; + struct bab_work_send chip_input; + bool ci_setup; + bool rolled; + int nonces; + struct timeval work_start; +} WITEM; + +#define ALLOC_SITEMS 8 +#define LIMIT_SITEMS 0 + +// SPI I/O +typedef struct sitem { + uint32_t siz; + uint8_t wbuf[BAB_MAXBUF]; + uint8_t rbuf[BAB_MAXBUF]; + uint32_t chip_off[BAB_MAXCHIPS+1]; + uint32_t bank_off[BAB_MAXBANKS+2]; + // WITEMs used to build the work + K_ITEM *witems[BAB_MAXCHIPS]; + struct timeval work_start; +} SITEM; + +#define ALLOC_RITEMS 256 +#define LIMIT_RITEMS 0 + +// Results +typedef struct ritem { + int chip; + int nonces; + uint32_t nonce[BAB_REPLY_NONCES]; + bool not_first_reply; + struct timeval when; +} RITEM; + +#define ALLOC_NITEMS 102400 +#define LIMIT_NITEMS 0 + +// Nonce History +typedef struct nitem { + struct timeval found; +} NITEM; + +#define DATAW(_item) ((WITEM *)(_item->data)) +#define DATAS(_item) ((SITEM *)(_item->data)) +#define DATAR(_item) ((RITEM *)(_item->data)) +#define DATAN(_item) ((NITEM *)(_item->data)) + +// Record the number of each band between work sends +#define BAB_DELAY_BANDS 10 +#define BAB_DELAY_BASE 0.5 +#define BAB_DELAY_STEP 0.2 + +#define BAB_CHIP_SPEEDS 6 +// less than or equal GH/s +static double chip_speed_ranges[BAB_CHIP_SPEEDS - 1] = + { 0.0, 0.8, 1.6, 2.2, 2.8 }; +// Greater than the last one above means it's the last speed +static char *chip_speed_names[BAB_CHIP_SPEEDS] = + { "Bad", "V.Slow", "Slow", "OK", "Good", "Fast" }; + +/* + * This is required to do chip tuning + * If disabled, it will simply run the chips at default speed + * unless they never return valid results + */ +#define UPDATE_HISTORY 1 + +struct bab_info { + struct thr_info spi_thr; + struct thr_info res_thr; + + pthread_mutex_t did_lock; + pthread_mutex_t nonce_lock; + + // All GPIO goes through this + volatile unsigned *gpio; + + int version; + int spifd; + int chips; + int chips_per_bank[BAB_MAXBANKS+1]; + int missing_chips_per_bank[BAB_MAXBANKS+1]; + int bank_first_chip[BAB_MAXBANKS+1]; + int bank_last_chip[BAB_MAXBANKS+1]; + int boards; + int banks; + uint32_t chip_spis[BAB_MAXCHIPS+1]; + + int reply_wait; + uint64_t reply_waits; + + cgsem_t scan_work; + cgsem_t spi_work; + cgsem_t spi_reply; + cgsem_t process_reply; + + bool disabled[BAB_MAXCHIPS]; + int total_disabled; + + struct bab_work_reply chip_results[BAB_MAXCHIPS]; + struct bab_work_reply chip_prev[BAB_MAXCHIPS]; + + uint8_t chip_fast[BAB_MAXCHIPS]; + uint8_t chip_conf[BAB_MAXCHIPS]; + uint8_t old_fast[BAB_MAXCHIPS]; + uint8_t old_conf[BAB_MAXCHIPS]; + uint8_t chip_bank[BAB_MAXCHIPS+1]; + + uint8_t osc[BAB_OSC]; + + /* + * Ignore errors in the first work reply since + * they may be from a previous run or random junk + * There can be >100 with just one 16 chip board + */ + uint32_t initial_ignored; + bool not_first_reply[BAB_MAXCHIPS]; + + // Stats + uint64_t core_good[BAB_MAXCHIPS][BAB_CORES]; + uint64_t core_bad[BAB_MAXCHIPS][BAB_CORES]; + uint64_t chip_spie[BAB_MAXCHIPS]; // spi errors + uint64_t chip_miso[BAB_MAXCHIPS]; // msio errors + uint64_t chip_nonces[BAB_MAXCHIPS]; + uint64_t chip_good[BAB_MAXCHIPS]; + uint64_t chip_bad[BAB_MAXCHIPS]; + uint64_t chip_ncore[BAB_MAXCHIPS][BAB_X_COORD][BAB_Y_COORD]; + + uint64_t chip_cont_bad[BAB_MAXCHIPS]; + uint64_t chip_max_bad[BAB_MAXCHIPS]; + + uint64_t discarded_e0s; + + uint64_t untested_nonces; + uint64_t tested_nonces; + + uint64_t new_nonces; + uint64_t ok_nonces; + + uint64_t nonce_offset_count[BAB_NONCE_OFFSETS]; + uint64_t total_tests; + uint64_t max_tests_per_nonce; + uint64_t total_links; + uint64_t total_proc_links; + uint64_t max_links; + uint64_t max_proc_links; + uint64_t total_work_links; + + uint64_t fail; + uint64_t fail_total_tests; + uint64_t fail_total_links; + uint64_t fail_total_work_links; + + uint64_t ign_total_tests; + uint64_t ign_total_links; + uint64_t ign_total_work_links; + + struct timeval last_sent_work; + uint64_t delay_count; + double delay_min; + double delay_max; + /* + * 0 is below band ranges + * BAB_DELAY_BANDS+1 is above band ranges + */ + uint64_t delay_bands[BAB_DELAY_BANDS+2]; + + uint64_t send_count; + double send_total; + double send_min; + double send_max; + + // Work + K_LIST *wfree_list; + K_STORE *available_work; + K_STORE *chip_work[BAB_MAXCHIPS]; + + // SPI I/O + K_LIST *sfree_list; + // Waiting to send + K_STORE *spi_list; + // Sent + K_STORE *spi_sent; + + // Results + K_LIST *rfree_list; + K_STORE *res_list; + + // Nonce History + K_LIST *nfree_list; + K_STORE *good_nonces[BAB_MAXCHIPS]; + K_STORE *bad_nonces[BAB_MAXCHIPS]; + + struct timeval first_work[BAB_MAXCHIPS]; +#if UPDATE_HISTORY + uint32_t work_count[BAB_MAXCHIPS]; + struct timeval last_tune[BAB_MAXCHIPS]; + uint8_t bad_fast[BAB_MAXCHIPS]; + bool bad_msg[BAB_MAXCHIPS]; +#endif + uint64_t work_unrolled; + uint64_t work_rolled; + + // bab-options (in order) + uint8_t max_speed; + uint8_t def_speed; + uint8_t min_speed; + double tune_up; + double tune_down; + uint32_t speed_hz; + uint16_t delay_usecs; + uint64_t trf_delay; + + struct timeval last_did; + + bool initialised; +}; + +/* + * Amount of time for history + * Older items in nonce_history are discarded + * 300s / 5 minutes + */ +#define HISTORY_TIME_S 300 + +/* + * If the SPI I/O thread waits longer than this long for work + * it will report an error saying how long it's waiting + * and again every BAB_STD_WAIT_mS after that + */ +#define BAB_LONG_uS 1200000 + +/* + * If work wasn't available early enough, + * report every BAB_LONG_WAIT_mS until it is + */ +#define BAB_LONG_WAIT_mS 888 + +/* + * Some amount of time to wait for work + * before checking how long we've waited + */ +#define BAB_STD_WAIT_mS 888 + +/* + * How long to wait for the ioctl() to complete (per BANK) + * This is a failsafe in case the ioctl() fails + * since bab_txrx() will already post a wakeup when it completes + * V1 is set to this x 2 + * V2 is set to this x active banks + */ +#define BAB_REPLY_WAIT_mS 160 + +/* + * Work items older than this should not expect results + * It has to allow for the result buffer returned with the next result + * 0.75GH/s takes 5.727s to do a full nonce range + * If HW is too high, consider increasing this to see if work is being + * expired too early (due to slow chips) + */ +#define BAB_WORK_EXPIRE_mS 7800 + +// Don't send work more often than this +#define BAB_EXPECTED_WORK_DELAY_mS 899 + +/* + * If a chip only has bad results after this time limit in seconds, + * then switch it down to min_speed + */ +#define BAB_BAD_TO_MIN (HISTORY_TIME_S + 10) + +/* + * Also, just to be sure it's actually mining, it must have got this + * many bad results before considering disabling it + */ +#define BAB_BAD_COUNT 100 + +/* + * If a chip only has bad results after this time limit in seconds, + * then disable it + * A chip only returning bad results will use a lot more CPU than + * an ok chip since all results will be tested against all unexpired + * work that's been sent to the chip + */ +#define BAB_BAD_DEAD (BAB_BAD_TO_MIN * 2) + +/* + * Maximum bab_queue_full() will roll work if it is allowed to + * Since work can somtimes (rarely) queue up with many chips, + * limit it to avoid it getting too much range in the pending work + */ +#define BAB_MAX_ROLLTIME 42 + +static void bab_ms3steps(uint32_t *p) +{ + uint32_t a, b, c, d, e, f, g, h, new_e, new_a; + int i; + + a = p[0]; + b = p[1]; + c = p[2]; + d = p[3]; + e = p[4]; + f = p[5]; + g = p[6]; + h = p[7]; + for (i = 0; i < 3; i++) { + new_e = p[i+16] + sha256_k[i] + h + CH(e,f,g) + SHA256_F2(e) + d; + new_a = p[i+16] + sha256_k[i] + h + CH(e,f,g) + SHA256_F2(e) + + SHA256_F1(a) + MAJ(a,b,c); + d = c; + c = b; + b = a; + a = new_a; + h = g; + g = f; + f = e; + e = new_e; + } + p[15] = a; + p[14] = b; + p[13] = c; + p[12] = d; + p[11] = e; + p[10] = f; + p[9] = g; + p[8] = h; +} + +static uint32_t bab_decnonce(uint32_t in) +{ + uint32_t out; + + /* First part load */ + out = (in & 0xFF) << 24; + in >>= 8; + + /* Byte reversal */ + in = (((in & 0xaaaaaaaa) >> 1) | ((in & 0x55555555) << 1)); + in = (((in & 0xcccccccc) >> 2) | ((in & 0x33333333) << 2)); + in = (((in & 0xf0f0f0f0) >> 4) | ((in & 0x0f0f0f0f) << 4)); + + out |= (in >> 2) & 0x3FFFFF; + + /* Extraction */ + if (in & 1) + out |= (1 << 23); + if (in & 2) + out |= (1 << 22); + + out -= 0x800004; + return out; +} + +static void cleanup_older(struct cgpu_info *babcgpu, int chip, K_ITEM *witem) +{ + struct bab_info *babinfo = (struct bab_info *)(babcgpu->device_data); + struct timeval now; + bool expired_item; + K_ITEM *tail; + + cgtime(&now); + + K_WLOCK(babinfo->chip_work[chip]); + tail = babinfo->chip_work[chip]->tail; + expired_item = false; + // Discard expired work + while (tail) { + if (ms_tdiff(&now, &(DATAW(tail)->work_start)) < BAB_WORK_EXPIRE_mS) + break; + + if (tail == witem) + expired_item = true; + + k_unlink_item(babinfo->chip_work[chip], tail); + K_WUNLOCK(babinfo->chip_work[chip]); + if (DATAW(tail)->rolled) + free_work(DATAW(tail)->work); + else + work_completed(babcgpu, DATAW(tail)->work); + K_WLOCK(babinfo->chip_work[chip]); + k_add_head(babinfo->wfree_list, tail); + tail = babinfo->chip_work[chip]->tail; + } + // If we didn't expire witem, then remove all older than it + if (!expired_item && witem && witem->next) { + tail = babinfo->chip_work[chip]->tail; + while (tail && tail != witem) { + k_unlink_item(babinfo->chip_work[chip], tail); + K_WUNLOCK(babinfo->chip_work[chip]); + if (DATAW(tail)->rolled) + free_work(DATAW(tail)->work); + else + work_completed(babcgpu, DATAW(tail)->work); + K_WLOCK(babinfo->chip_work[chip]); + k_add_head(babinfo->wfree_list, tail); + tail = babinfo->chip_work[chip]->tail; + } + } + K_WUNLOCK(babinfo->chip_work[chip]); +} + +static void _bab_reset(__maybe_unused struct cgpu_info *babcgpu, struct bab_info *babinfo, int bank, int times) +{ + const int banks[BAB_MAXBANKS] = { 18, 23, 24, 25 }; + int i; + + BAB_INP_GPIO(10); + BAB_OUT_GPIO(10); + BAB_INP_GPIO(11); + BAB_OUT_GPIO(11); + + if (bank) { + for (i = 0; i < BAB_MAXBANKS; i++) { + BAB_INP_GPIO(banks[i]); + BAB_OUT_GPIO(banks[i]); + if (bank == i+1) + BAB_GPIO_SET = 1 << banks[i]; + else + BAB_GPIO_CLR = 1 << banks[i]; + } + cgsleep_us(4096); + } else { + for (i = 0; i < BAB_MAXBANKS; i++) + BAB_INP_GPIO(banks[i]); + } + + BAB_GPIO_SET = 1 << 11; + for (i = 0; i < times; i++) { // 1us = 1MHz + BAB_GPIO_SET = 1 << 10; + cgsleep_us(1); + BAB_GPIO_CLR = 1 << 10; + cgsleep_us(1); + } + BAB_GPIO_CLR = 1 << 11; + BAB_INP_GPIO(11); + BAB_INP_GPIO(10); + BAB_INP_GPIO(9); + BAB_OUT_GPIO_V(11, 0); + BAB_OUT_GPIO_V(10, 0); + BAB_OUT_GPIO_V(9, 0); +} + +// TODO: handle a false return where this is called? +static bool _bab_txrx(struct cgpu_info *babcgpu, struct bab_info *babinfo, K_ITEM *item, bool detect_ignore, const char *file, const char *func, const int line) +{ + int bank, i, count, chip1, chip2; + uint32_t siz, pos; + struct spi_ioc_transfer tran; + uintptr_t rbuf, wbuf; + + wbuf = (uintptr_t)(DATAS(item)->wbuf); + rbuf = (uintptr_t)(DATAS(item)->rbuf); + siz = (uint32_t)(DATAS(item)->siz); + + memset(&tran, 0, sizeof(tran)); + tran.speed_hz = babinfo->speed_hz; + tran.delay_usecs = babinfo->delay_usecs; + + i = 0; + pos = 0; + for (bank = 0; bank <= BAB_MAXBANKS; bank++) { + if (DATAS(item)->bank_off[bank]) { + bab_reset(bank, 64); + break; + } + } + + if (unlikely(bank > BAB_MAXBANKS)) { + applog(LOG_ERR, "%s%d: %s() failed to find a bank" BAB_FFL, + babcgpu->drv->name, babcgpu->device_id, + __func__, BAB_FFL_PASS); + return false; + } + + count = 0; + while (siz > 0) { + tran.tx_buf = wbuf; + tran.rx_buf = rbuf; + tran.speed_hz = BAB_SPI_SPEED; + if (pos == DATAS(item)->bank_off[bank]) { + for (; ++bank <= BAB_MAXBANKS; ) { + if (DATAS(item)->bank_off[bank] > pos) { + bab_reset(bank, 64); + break; + } + } + } + if (siz < BAB_SPI_BUFSIZ) + tran.len = siz; + else + tran.len = BAB_SPI_BUFSIZ; + + if (pos < DATAS(item)->bank_off[bank] && + DATAS(item)->bank_off[bank] < (pos + tran.len)) + tran.len = DATAS(item)->bank_off[bank] - pos; + + for (; i < babinfo->chips; i++) { + if (!DATAS(item)->chip_off[i]) + continue; + if (DATAS(item)->chip_off[i] >= pos + tran.len) { + tran.speed_hz = babinfo->chip_spis[i]; + break; + } + } + + if (unlikely(i > babinfo->chips)) { + applog(LOG_ERR, "%s%d: %s() failed to find chip" BAB_FFL, + babcgpu->drv->name, babcgpu->device_id, + __func__, BAB_FFL_PASS); + return false; + } + + if (unlikely(babinfo->chip_spis[i] == BAB_SPI_SPEED)) { + applog(LOG_DEBUG, "%s%d: %s() chip[%d] speed %d shouldn't be %d" BAB_FFL, + babcgpu->drv->name, babcgpu->device_id, + __func__, i, (int)babinfo->chip_spis[i], + BAB_SPI_SPEED, BAB_FFL_PASS); + } + + if (unlikely(tran.speed_hz == BAB_SPI_SPEED)) { + applog(LOG_DEBUG, "%s%d: %s() transfer speed %d shouldn't be %d" BAB_FFL, + babcgpu->drv->name, babcgpu->device_id, + __func__, (int)tran.speed_hz, + BAB_SPI_SPEED, BAB_FFL_PASS); + } + + count++; + if (ioctl(babinfo->spifd, SPI_IOC_MESSAGE(1), (void *)&tran) < 0) { + if (!detect_ignore || errno != 110) { + for (bank = BAB_MAXBANKS; bank >= 0; bank--) { + if (DATAS(item)->bank_off[bank] && + pos >= DATAS(item)->bank_off[bank]) { + break; + } + } + for (chip1 = babinfo->chips-1; chip1 >= 0; chip1--) { + if (DATAS(item)->chip_off[chip1] && + pos >= DATAS(item)->chip_off[chip1]) { + break; + } + } + for (chip2 = babinfo->chips-1; chip2 >= 0; chip2--) { + if (DATAS(item)->chip_off[chip2] && + (pos + tran.len) >= DATAS(item)->chip_off[chip2]) { + break; + } + } + applog(LOG_ERR, "%s%d: ioctl (%d) siz=%d bank=%d chip=%d-%d" + " failed err=%d" BAB_FFL, + babcgpu->drv->name, + babcgpu->device_id, + count, (int)(tran.len), + bank, chip1, chip2, + errno, BAB_FFL_PASS); + } + return false; + } + + siz -= tran.len; + wbuf += tran.len; + rbuf += tran.len; + pos += tran.len; + + if (siz > 0 && babinfo->trf_delay > 0) + cgsleep_us(babinfo->trf_delay); + } + cgtime(&(DATAS(item)->work_start)); + mutex_lock(&(babinfo->did_lock)); + cgtime(&(babinfo->last_did)); + mutex_unlock(&(babinfo->did_lock)); + return true; +} + +static void _bab_add_buf_rev(K_ITEM *item, const uint8_t *data, uint32_t siz, const char *file, const char *func, const int line) +{ + uint32_t now_used, i; + uint8_t tmp; + + now_used = DATAS(item)->siz; + if (now_used + siz >= BAB_MAXBUF) { + quitfrom(1, file, func, line, + "%s() buffer limit of %d exceeded=%d siz=%d", + __func__, BAB_MAXBUF, (int)(now_used + siz), (int)siz); + } + + for (i = 0; i < siz; i++) { + tmp = data[i]; + tmp = ((tmp & 0xaa)>>1) | ((tmp & 0x55) << 1); + tmp = ((tmp & 0xcc)>>2) | ((tmp & 0x33) << 2); + tmp = ((tmp & 0xf0)>>4) | ((tmp & 0x0f) << 4); + DATAS(item)->wbuf[now_used + i] = tmp; + } + + DATAS(item)->siz += siz; +} + +static void _bab_add_buf(K_ITEM *item, const uint8_t *data, size_t siz, const char *file, const char *func, const int line) +{ + uint32_t now_used; + + now_used = DATAS(item)->siz; + if (now_used + siz >= BAB_MAXBUF) { + quitfrom(1, file, func, line, + "%s() DATAS buffer limit of %d exceeded=%d siz=%d", + __func__, BAB_MAXBUF, (int)(now_used + siz), (int)siz); + } + + memcpy(&(DATAS(item)->wbuf[now_used]), data, siz); + DATAS(item)->siz += siz; +} + +static void _bab_add_noops(K_ITEM *item, size_t siz, const char *file, const char *func, const int line) +{ + uint32_t now_used; + + now_used = DATAS(item)->siz; + if (now_used + siz >= BAB_MAXBUF) { + quitfrom(1, file, func, line, + "%s() DATAS buffer limit of %d exceeded=%d siz=%d", + __func__, BAB_MAXBUF, (int)(now_used + siz), (int)siz); + } + + memset(&(DATAS(item)->wbuf[now_used]), BAB_NOOP, siz); + DATAS(item)->siz += siz; +} + +static void _bab_add_data(K_ITEM *item, uint32_t addr, const uint8_t *data, size_t siz, const char *file, const char *func, const int line) +{ + uint8_t tmp[3]; + int trf_siz; + + if (siz < BAB_ADD_MIN || siz > BAB_ADD_MAX) { + quitfrom(1, file, func, line, + "%s() called with invalid siz=%d (min=%d max=%d)", + __func__, (int)siz, BAB_ADD_MIN, BAB_ADD_MAX); + } + trf_siz = siz / 4; + tmp[0] = (trf_siz - 1) | 0xE0; + tmp[1] = (addr >> 8) & 0xff; + tmp[2] = addr & 0xff; + _bab_add_buf(item, tmp, sizeof(tmp), BAB_FFL_PASS); + _bab_add_buf_rev(item, data, siz, BAB_FFL_PASS); +} + +static void _bab_config_reg(K_ITEM *item, uint32_t reg, bool enable, const char *file, const char *func, const int line) +{ + if (enable) { + _bab_add_data(item, BAB_REG_ADDR + reg*32, + bab_reg_ena, sizeof(bab_reg_ena), BAB_FFL_PASS); + } else { + _bab_add_data(item, BAB_REG_ADDR + reg*32, + bab_reg_dis, sizeof(bab_reg_dis), BAB_FFL_PASS); + } + +} + +static void bab_set_osc(struct bab_info *babinfo, int chip) +{ + int fast, i; + + fast = babinfo->chip_fast[chip]; + + for (i = 0; i < BAB_OSC && fast > BAB_OSC; i++, fast -= BAB_OSC) { + babinfo->osc[i] = 0xff; + } + if (i < BAB_OSC && fast > 0 && fast <= BAB_OSC) + babinfo->osc[i++] = bab_osc_bits[fast - 1]; + for (; i < BAB_OSC; i++) + babinfo->osc[i] = 0x00; + + applog(LOG_DEBUG, "@osc(chip=%d) fast=%d 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x", chip, fast, babinfo->osc[0], babinfo->osc[1], babinfo->osc[2], babinfo->osc[3], babinfo->osc[4], babinfo->osc[5], babinfo->osc[6], babinfo->osc[7]); +} + +static void bab_put(struct bab_info *babinfo, K_ITEM *sitem) +{ + struct bab_work_send *chip_input; + int i, reg, bank = 0; + size_t chip_siz; + + BAB_ADD_BREAK(sitem); + for (i = 0; i < babinfo->chips; i++) { + if (babinfo->chip_bank[i] != bank) { + DATAS(sitem)->bank_off[bank] = DATAS(sitem)->siz; + bank = babinfo->chip_bank[i]; + BAB_ADD_BREAK(sitem); + } + if (!(babinfo->disabled[i])) { + if (BAB_CFGD_SET(babinfo->chip_conf[i]) || !babinfo->chip_conf[i]) { + bab_set_osc(babinfo, i); + bab_add_data(sitem, BAB_OSC_ADDR, babinfo->osc, sizeof(babinfo->osc)); + bab_config_reg(sitem, BAB_ICLK_REG, BAB_ICLK_BIT(babinfo->chip_conf[i])); + bab_config_reg(sitem, BAB_FAST_REG, BAB_FAST_BIT(babinfo->chip_conf[i])); + bab_config_reg(sitem, BAB_DIV2_REG, BAB_DIV2_BIT(babinfo->chip_conf[i])); + bab_config_reg(sitem, BAB_SLOW_REG, BAB_SLOW_BIT(babinfo->chip_conf[i])); + bab_config_reg(sitem, BAB_OCLK_REG, BAB_OCLK_BIT(babinfo->chip_conf[i])); + for (reg = BAB_REG_CLR_FROM; reg <= BAB_REG_CLR_TO; reg++) + bab_config_reg(sitem, reg, false); + if (babinfo->chip_conf[i]) { + bab_add_data(sitem, BAB_COUNT_ADDR, bab_counters, sizeof(bab_counters)); + bab_add_data(sitem, BAB_W1A_ADDR, bab_w1, sizeof(bab_w1)); + bab_add_data(sitem, BAB_W1B_ADDR, bab_w1, sizeof(bab_w1)/2); + bab_add_data(sitem, BAB_W2_ADDR, bab_w2, sizeof(bab_w2)); + babinfo->chip_conf[i] ^= BAB_CFGD_VAL; + } + babinfo->old_fast[i] = babinfo->chip_fast[i]; + babinfo->old_conf[i] = babinfo->chip_conf[i]; + } else { + if (babinfo->old_fast[i] != babinfo->chip_fast[i]) { + bab_set_osc(babinfo, i); + bab_add_data(sitem, BAB_OSC_ADDR, babinfo->osc, sizeof(babinfo->osc)); + babinfo->old_fast[i] = babinfo->chip_fast[i]; + } + if (babinfo->old_conf[i] != babinfo->chip_conf[i]) { + if (BAB_ICLK_SET(babinfo->old_conf[i]) != + BAB_ICLK_SET(babinfo->chip_conf[i])) + bab_config_reg(sitem, BAB_ICLK_REG, + BAB_ICLK_BIT(babinfo->chip_conf[i])); + if (BAB_FAST_SET(babinfo->old_conf[i]) != + BAB_FAST_SET(babinfo->chip_conf[i])) + bab_config_reg(sitem, BAB_FAST_REG, + BAB_FAST_BIT(babinfo->chip_conf[i])); + if (BAB_DIV2_SET(babinfo->old_conf[i]) != + BAB_DIV2_SET(babinfo->chip_conf[i])) + bab_config_reg(sitem, BAB_DIV2_REG, + BAB_DIV2_BIT(babinfo->chip_conf[i])); + if (BAB_SLOW_SET(babinfo->old_conf[i]) != + BAB_SLOW_SET(babinfo->chip_conf[i])) + bab_config_reg(sitem, BAB_SLOW_REG, + BAB_SLOW_BIT(babinfo->chip_conf[i])); + if (BAB_OCLK_SET(babinfo->old_conf[i]) != + BAB_OCLK_SET(babinfo->chip_conf[i])) + bab_config_reg(sitem, BAB_OCLK_REG, + BAB_OCLK_BIT(babinfo->chip_conf[i])); + babinfo->old_conf[i] = babinfo->chip_conf[i]; + } + } + DATAS(sitem)->chip_off[i] = DATAS(sitem)->siz + 3; + chip_input = &(DATAW(DATAS(sitem)->witems[i])->chip_input); + + if (babinfo->chip_conf[i]) + bab_add_data(sitem, BAB_INP_ADDR, (uint8_t *)chip_input, sizeof(*chip_input)); + + chip_siz = DATAS(sitem)->siz - babinfo->chip_conf[i]; + if (chip_siz < BAB_CHIP_MIN) + BAB_ADD_NOOPs(sitem, BAB_CHIP_MIN - chip_siz); + } + BAB_ADD_ASYNC(sitem); + } + DATAS(sitem)->chip_off[i] = DATAS(sitem)->siz; + DATAS(sitem)->bank_off[bank] = DATAS(sitem)->siz; + + K_WLOCK(babinfo->spi_list); + k_add_head(babinfo->spi_list, sitem); + K_WUNLOCK(babinfo->spi_list); + + cgsem_post(&(babinfo->spi_work)); +} + +static bool bab_get(__maybe_unused struct cgpu_info *babcgpu, struct bab_info *babinfo, struct timeval *when) +{ + K_ITEM *item; + bool delayed; + int i; + + item = NULL; + delayed = false; + while (item == NULL) { + cgsem_mswait(&(babinfo->spi_reply), babinfo->reply_wait); + + K_WLOCK(babinfo->spi_sent); + item = k_unlink_tail(babinfo->spi_sent); + K_WUNLOCK(babinfo->spi_sent); + + if (!item) { + if (!delayed) { + applog(LOG_WARNING, "%s%d: Delay getting work reply ...", + babcgpu->drv->name, + babcgpu->device_id); + delayed = true; + babinfo->reply_waits++; + } + } + } + + for (i = 0; i < babinfo->chips; i++) { + if (babinfo->chip_conf[i] & 0x7f) { + memcpy((void *)&(babinfo->chip_results[i]), + (void *)(DATAS(item)->rbuf + DATAS(item)->chip_off[i]), + sizeof(babinfo->chip_results[0])); + } + } + + // work_start is also the time the results were read + memcpy(when, &(DATAS(item)->work_start), sizeof(*when)); + + K_WLOCK(babinfo->sfree_list); + k_add_head(babinfo->sfree_list, item); + K_WUNLOCK(babinfo->sfree_list); + + return true; +} + +void bab_detect_chips(struct cgpu_info *babcgpu, struct bab_info *babinfo, int bank, int first, int last) +{ + int i, reg, j; + K_ITEM *item; + + if (sizeof(struct bab_work_send) != sizeof(bab_test_data)) { + quithere(1, "struct bab_work_send (%d) and bab_test_data (%d)" + " must be the same size", + (int)sizeof(struct bab_work_send), + (int)sizeof(bab_test_data)); + } + + K_WLOCK(babinfo->sfree_list); + item = k_unlink_head_zero(babinfo->sfree_list); + K_WUNLOCK(babinfo->sfree_list); + BAB_ADD_BREAK(item); + for (i = first; i < last && i < BAB_MAXCHIPS; i++) { + bab_set_osc(babinfo, i); + bab_add_data(item, BAB_OSC_ADDR, babinfo->osc, sizeof(babinfo->osc)); + bab_config_reg(item, BAB_ICLK_REG, BAB_ICLK_BIT(babinfo->chip_conf[i])); + bab_config_reg(item, BAB_FAST_REG, BAB_FAST_BIT(babinfo->chip_conf[i])); + bab_config_reg(item, BAB_DIV2_REG, BAB_DIV2_BIT(babinfo->chip_conf[i])); + bab_config_reg(item, BAB_SLOW_REG, BAB_SLOW_BIT(babinfo->chip_conf[i])); + bab_config_reg(item, BAB_OCLK_REG, BAB_OCLK_BIT(babinfo->chip_conf[i])); + for (reg = BAB_REG_CLR_FROM; reg <= BAB_REG_CLR_TO; reg++) + bab_config_reg(item, reg, false); + bab_add_data(item, BAB_COUNT_ADDR, bab_counters, sizeof(bab_counters)); + bab_add_data(item, BAB_W1A_ADDR, bab_w1, sizeof(bab_w1)); + bab_add_data(item, BAB_W1B_ADDR, bab_w1, sizeof(bab_w1)/2); + bab_add_data(item, BAB_W2_ADDR, bab_w2, sizeof(bab_w2)); + DATAS(item)->chip_off[i] = DATAS(item)->siz + 3; + bab_add_data(item, BAB_INP_ADDR, bab_test_data, sizeof(bab_test_data)); + DATAS(item)->chip_off[i+1] = DATAS(item)->siz; + DATAS(item)->bank_off[bank] = DATAS(item)->siz; + babinfo->chips = i + 1; + bab_txrx(item, false); + DATAS(item)->siz = 0; + BAB_ADD_BREAK(item); + for (j = first; j <= i; j++) { + DATAS(item)->chip_off[j] = DATAS(item)->siz + 3; + BAB_ADD_ASYNC(item); + } + } + + memset(item->data, 0, babinfo->sfree_list->siz); + BAB_ADD_BREAK(item); + for (i = first; i < last && i < BAB_MAXCHIPS; i++) { + DATAS(item)->chip_off[i] = DATAS(item)->siz + 3; + bab_add_data(item, BAB_INP_ADDR, bab_test_data, sizeof(bab_test_data)); + BAB_ADD_ASYNC(item); + } + DATAS(item)->chip_off[i] = DATAS(item)->siz; + DATAS(item)->bank_off[bank] = DATAS(item)->siz; + babinfo->chips = i; + bab_txrx(item, true); + DATAS(item)->siz = 0; + babinfo->chips = first; + for (i = first; i < last && i < BAB_MAXCHIPS; i++) { + uint32_t tmp[DATA_UINTS-1]; + memcpy(tmp, DATAS(item)->rbuf + DATAS(item)->chip_off[i], sizeof(tmp)); + DATAS(item)->chip_off[i] = 0; + for (j = 0; j < BAB_REPLY_NONCES; j++) { + if (tmp[j] != 0xffffffff && tmp[j] != 0x00000000) { + babinfo->chip_bank[i] = bank; + babinfo->chips = i + 1; + break; + } + } + } + for (i = first ; i < babinfo->chips; i++) + babinfo->chip_bank[i] = bank; + + K_WLOCK(babinfo->sfree_list); + k_add_head(babinfo->sfree_list, item); + K_WUNLOCK(babinfo->sfree_list); +} + +static const char *bab_modules[] = { + "i2c-dev", + "i2c-bcm2708", + "spidev", + "spi-bcm2708", + NULL +}; + +static const char *bab_memory = "/dev/mem"; + +static int bab_memory_addr = 0x20200000; + +// TODO: add --bab-options for SPEED_HZ, tran.delay_usecs and an inter transfer delay (default 0) +static struct { + int request; + int value; +} bab_ioc[] = { + { SPI_IOC_RD_MODE, 0 }, + { SPI_IOC_WR_MODE, 0 }, + { SPI_IOC_RD_BITS_PER_WORD, 8 }, + { SPI_IOC_WR_BITS_PER_WORD, 8 }, + { SPI_IOC_RD_MAX_SPEED_HZ, 1000000 }, + { SPI_IOC_WR_MAX_SPEED_HZ, 1000000 }, + { -1, -1 } +}; + +static bool bab_init_gpio(struct cgpu_info *babcgpu, struct bab_info *babinfo, int bus, int chip) +{ + int i, err, memfd, data; + char buf[64]; + + bab_ioc[4].value = (int)(babinfo->speed_hz); + bab_ioc[5].value = (int)(babinfo->speed_hz); + + for (i = 0; bab_modules[i]; i++) { + snprintf(buf, sizeof(buf), "modprobe %s", bab_modules[i]); + err = system(buf); + if (err) { + applog(LOG_ERR, "%s failed to modprobe %s (%d) - you need to be root?", + babcgpu->drv->dname, + bab_modules[i], err); + goto bad_out; + } + } + + memfd = open(bab_memory, O_RDWR | O_SYNC); + if (memfd < 0) { + applog(LOG_ERR, "%s failed open %s (%d)", + babcgpu->drv->dname, + bab_memory, errno); + goto bad_out; + } + + babinfo->gpio = (volatile unsigned *)mmap(NULL, BAB_SPI_BUFSIZ, + PROT_READ | PROT_WRITE, + MAP_SHARED, memfd, + bab_memory_addr); + if (babinfo->gpio == MAP_FAILED) { + close(memfd); + applog(LOG_ERR, "%s failed mmap gpio (%d)", + babcgpu->drv->dname, + errno); + goto bad_out; + } + + close(memfd); + + snprintf(buf, sizeof(buf), "/dev/spidev%d.%d", bus, chip); + babinfo->spifd = open(buf, O_RDWR); + if (babinfo->spifd < 0) { + applog(LOG_ERR, "%s failed to open spidev (%d)", + babcgpu->drv->dname, + errno); + goto map_out; + } + + babcgpu->device_path = strdup(buf); + + for (i = 0; bab_ioc[i].value != -1; i++) { + data = bab_ioc[i].value; + err = ioctl(babinfo->spifd, bab_ioc[i].request, (void *)&data); + if (err < 0) { + applog(LOG_ERR, "%s failed ioctl (%d) (%d)", + babcgpu->drv->dname, + i, errno); + goto close_out; + } + } + + for (i = 0; i < BAB_MAXCHIPS; i++) + babinfo->chip_spis[i] = (int)((1000000.0 / (100.0 + 31.0 * (i + 1))) * 1000); + + return true; + +close_out: + close(babinfo->spifd); + babinfo->spifd = 0; + free(babcgpu->device_path); + babcgpu->device_path = NULL; +map_out: + munmap((void *)(babinfo->gpio), BAB_SPI_BUFSIZ); + babinfo->gpio = NULL; +bad_out: + return false; +} + +static void bab_init_chips(struct cgpu_info *babcgpu, struct bab_info *babinfo) +{ + int chip, chipoff, bank, chips, new_chips, boards, mis; + + applog(LOG_WARNING, "%s V1 first test for %d chips ...", + babcgpu->drv->dname, BAB_V1_CHIP_TEST); + + bab_detect_chips(babcgpu, babinfo, 0, 0, BAB_V1_CHIP_TEST); + if (babinfo->chips > 0) { + babinfo->version = 1; + babinfo->banks = 0; + if (babinfo->chips == BAB_V1_CHIP_TEST) { + applog(LOG_WARNING, "%s V1 test for %d more chips ...", + babcgpu->drv->dname, BAB_MAXCHIPS - BAB_V1_CHIP_TEST); + + bab_detect_chips(babcgpu, babinfo, 0, BAB_V1_CHIP_TEST, BAB_MAXCHIPS); + } + babinfo->chips_per_bank[BAB_V1_BANK] = babinfo->chips; + babinfo->bank_first_chip[BAB_V1_BANK] = 0; + babinfo->bank_last_chip[BAB_V1_BANK] = babinfo->chips - 1; + babinfo->boards = (int)((float)(babinfo->chips - 1) / BAB_BOARDCHIPS) + 1; + babinfo->reply_wait = BAB_REPLY_WAIT_mS * 2; + + if ((chip = (babinfo->chips_per_bank[BAB_V1_BANK] % BAB_BOARDCHIPS))) { + mis = BAB_BOARDCHIPS - chip; + babinfo->missing_chips_per_bank[BAB_V1_BANK] = mis; + applog(LOG_WARNING, "%s V1: missing %d chip%s", + babcgpu->drv->dname, mis, + (mis == 1) ? "" : "s"); + } + } else { + applog(LOG_WARNING, "%s no chips found with V1", babcgpu->drv->dname); + applog(LOG_WARNING, "%s V2 test %d banks %d chips ...", + babcgpu->drv->dname, BAB_MAXBANKS, BAB_MAXCHIPS); + + chips = 0; + babinfo->version = 2; + babinfo->banks = 0; + for (bank = 1; bank <= BAB_MAXBANKS; bank++) { + for (chipoff = 0; chipoff < BAB_BANKCHIPS; chipoff++) { + chip = babinfo->chips + chipoff; + babinfo->chip_spis[chip] = 625000; + } + bab_reset(bank, 64); + bab_detect_chips(babcgpu, babinfo, bank, babinfo->chips, babinfo->chips + BAB_BANKCHIPS); + new_chips = babinfo->chips - chips; + babinfo->chips_per_bank[bank] = new_chips; + if (new_chips > 0) { + babinfo->bank_first_chip[bank] = babinfo->chips - new_chips; + babinfo->bank_last_chip[bank] = babinfo->chips - 1; + } + chips = babinfo->chips; + if (new_chips == 0) + boards = 0; + else { + boards = (int)((float)(new_chips - 1) / BAB_BOARDCHIPS) + 1; + babinfo->banks++; + } + applog(LOG_WARNING, "%s V2 bank %d: %d chips %d board%s", + babcgpu->drv->dname, bank, new_chips, + boards, (boards == 1) ? "" : "s"); + babinfo->boards += boards; + + if ((chip = (babinfo->chips_per_bank[bank] % BAB_BOARDCHIPS))) { + mis = BAB_BOARDCHIPS - chip; + babinfo->missing_chips_per_bank[bank] = mis; + applog(LOG_WARNING, "%s V2: bank %d missing %d chip%s", + babcgpu->drv->dname, bank, + mis, (mis == 1) ? "" : "s"); + } + } + babinfo->reply_wait = BAB_REPLY_WAIT_mS * babinfo->banks; + bab_reset(0, 8); + } + + memcpy(babinfo->old_conf, babinfo->chip_conf, sizeof(babinfo->old_conf)); + memcpy(babinfo->old_fast, babinfo->chip_fast, sizeof(babinfo->old_fast)); +} + +static char *bab_options[] = { + "MaxSpeed", + "DefaultSpeed", + "MinSpeed", + "TuneUp", + "TuneDown", + "SPISpeed", + "SPIDelayuS", + "TransferDelayuS" +}; + +#define INVOP " Invalid Option " + +static void bab_get_options(struct cgpu_info *babcgpu, struct bab_info *babinfo) +{ + char *ptr, *colon; + int which, val; + double fval; + long lval; + + if (opt_bab_options == NULL) + return; + + which = 0; + ptr = opt_bab_options; + while (ptr && *ptr) { + colon = strchr(ptr, ':'); + if (colon) + *(colon++) = '\0'; + + switch (which) { + case 0: + if (*ptr && tolower(*ptr) != 'd') { + val = atoi(ptr); + if (!isdigit(*ptr) || val < BAB_ABSMINSPEED || val > BAB_MAXSPEED) { + quit(1, "%s"INVOP"%s '%s' must be %d <= %s <= %d", + babcgpu->drv->dname, + bab_options[which], + ptr, BAB_ABSMINSPEED, + bab_options[which], + BAB_MAXSPEED); + } + babinfo->max_speed = (uint8_t)val; + // Adjust def,min down if they are above max specified + if (babinfo->def_speed > babinfo->max_speed) + babinfo->def_speed = babinfo->max_speed; + if (babinfo->min_speed > babinfo->max_speed) + babinfo->min_speed = babinfo->max_speed; + } + break; + case 1: + if (*ptr && tolower(*ptr) != 'd') { + val = atoi(ptr); + if (!isdigit(*ptr) || val < BAB_ABSMINSPEED || val > babinfo->max_speed) { + quit(1, "%s"INVOP"%s '%s' must be %d <= %s <= %d", + babcgpu->drv->dname, + bab_options[which], + ptr, BAB_ABSMINSPEED, + bab_options[which], + babinfo->max_speed); + } + babinfo->def_speed = (uint8_t)val; + // Adjust min down if is is above def specified + if (babinfo->min_speed > babinfo->def_speed) + babinfo->min_speed = babinfo->def_speed; + } + break; + case 2: + if (*ptr && tolower(*ptr) != 'd') { + val = atoi(ptr); + if (!isdigit(*ptr) || val < BAB_ABSMINSPEED || val > babinfo->def_speed) { + quit(1, "%s"INVOP"%s '%s' must be %d <= %s <= %d", + babcgpu->drv->dname, + bab_options[which], + ptr, BAB_ABSMINSPEED, + bab_options[which], + babinfo->def_speed); + } + babinfo->min_speed = (uint8_t)val; + } + break; + case 3: + if (*ptr && tolower(*ptr) != 'd') { + fval = atof(ptr); + if (!isdigit(*ptr) || fval < 0.0 || fval > 100.0) { + quit(1, "%s"INVOP"%s '%s' must be 0.0 <= %s <= 100.0", + babcgpu->drv->dname, + bab_options[which], ptr, + bab_options[which]); + } + babinfo->tune_up = fval; + } + break; + case 4: + if (*ptr && tolower(*ptr) != 'd') { + fval = atof(ptr); + if (!isdigit(*ptr) || fval < 0.0 || fval > 100.0) { + quit(1, "%s"INVOP"%s '%s' must be %f <= %s <= 100.0", + babcgpu->drv->dname, + bab_options[which], + ptr, babinfo->tune_up, + bab_options[which]); + } + babinfo->tune_down = fval; + } + break; + case 5: + if (*ptr && tolower(*ptr) != 'd') { + val = atoi(ptr); + if (!isdigit(*ptr) || val < 10000 || val > 10000000) { + quit(1, "%s"INVOP"%s '%s' must be 10,000 <= %s <= 10,000,000", + babcgpu->drv->dname, + bab_options[which], ptr, + bab_options[which]); + } + babinfo->speed_hz = (uint32_t)val; + } + break; + case 6: + if (*ptr && tolower(*ptr) != 'd') { + val = atoi(ptr); + if (!isdigit(*ptr) || val < 0 || val > 65535) { + quit(1, "%s"INVOP"%s '%s' must be 0 <= %s <= 65535", + babcgpu->drv->dname, + bab_options[which], ptr, + bab_options[which]); + } + babinfo->delay_usecs = (uint16_t)val; + } + break; + case 7: + if (*ptr && tolower(*ptr) != 'd') { + lval = atol(ptr); + if (!isdigit(*ptr) || lval < 0) { + quit(1, "%s"INVOP"%s '%s' must be %s >= 0", + babcgpu->drv->dname, + bab_options[which], ptr, + bab_options[which]); + } + babinfo->trf_delay = (uint64_t)lval; + } + break; + default: + break; + } + ptr = colon; + which++; + } +} + +static void bab_detect(bool hotplug) +{ + struct cgpu_info *babcgpu = NULL; + struct bab_info *babinfo = NULL; + int i; + + if (hotplug) + return; + + babcgpu = calloc(1, sizeof(*babcgpu)); + if (unlikely(!babcgpu)) + quithere(1, "Failed to calloc babcgpu"); + + babcgpu->drv = &bab_drv; + babcgpu->deven = DEV_ENABLED; + babcgpu->threads = 1; + + babinfo = calloc(1, sizeof(*babinfo)); + if (unlikely(!babinfo)) + quithere(1, "Failed to calloc babinfo"); + babcgpu->device_data = (void *)babinfo; + + babinfo->max_speed = BAB_DEFMAXSPEED; + babinfo->def_speed = BAB_DEFSPEED; + babinfo->min_speed = BAB_ABSMINSPEED; + + babinfo->tune_up = BAB_TUNEUP; + babinfo->tune_down = BAB_TUNEDOWN; + + babinfo->speed_hz = BAB_SPI_SPEED; + babinfo->delay_usecs = BAB_DELAY_USECS; + babinfo->trf_delay = BAB_TRF_DELAY; + + bab_get_options(babcgpu, babinfo); + + for (i = 0; i < BAB_MAXCHIPS; i++) { + babinfo->chip_conf[i] = BAB_DEFCONF; + babinfo->chip_fast[i] = babinfo->def_speed; +#if UPDATE_HISTORY + babinfo->bad_fast[i] = babinfo->max_speed + 1; +#endif + } + + if (!bab_init_gpio(babcgpu, babinfo, BAB_SPI_BUS, BAB_SPI_CHIP)) + goto unalloc; + + babinfo->sfree_list = k_new_list("SPI I/O", sizeof(SITEM), + ALLOC_SITEMS, LIMIT_SITEMS, true); + babinfo->spi_list = k_new_store(babinfo->sfree_list); + babinfo->spi_sent = k_new_store(babinfo->sfree_list); + + for (i = 0; i <= BAB_MAXBANKS; i++) { + babinfo->bank_first_chip[i] = -1; + babinfo->bank_last_chip[i] = -1; + } + + bab_init_chips(babcgpu, babinfo); + + if (babinfo->boards) { + applog(LOG_WARNING, "%s found %d chips %d board%s", + babcgpu->drv->dname, babinfo->chips, + babinfo->boards, + (babinfo->boards == 1) ? "" : "s"); + } else { + applog(LOG_WARNING, "%s found %d chips", + babcgpu->drv->dname, babinfo->chips); + } + + if (babinfo->chips == 0) + goto cleanup; + + if (!add_cgpu(babcgpu)) + goto cleanup; + + cgsem_init(&(babinfo->scan_work)); + cgsem_init(&(babinfo->spi_work)); + cgsem_init(&(babinfo->spi_reply)); + cgsem_init(&(babinfo->process_reply)); + + mutex_init(&babinfo->did_lock); + mutex_init(&babinfo->nonce_lock); + + babinfo->rfree_list = k_new_list("Results", sizeof(RITEM), + ALLOC_RITEMS, LIMIT_RITEMS, true); + babinfo->res_list = k_new_store(babinfo->rfree_list); + + babinfo->wfree_list = k_new_list("Work", sizeof(WITEM), + ALLOC_WITEMS, LIMIT_WITEMS, true); + babinfo->available_work = k_new_store(babinfo->wfree_list); + for (i = 0; i < BAB_MAXCHIPS; i++) + babinfo->chip_work[i] = k_new_store(babinfo->wfree_list); + + babinfo->nfree_list = k_new_list("Nonce History", sizeof(WITEM), + ALLOC_NITEMS, LIMIT_NITEMS, true); + for (i = 0; i < BAB_MAXCHIPS; i++) { + babinfo->good_nonces[i] = k_new_store(babinfo->nfree_list); + babinfo->bad_nonces[i] = k_new_store(babinfo->nfree_list); + } + + // Exclude detection + cgtime(&(babcgpu->dev_start_tv)); + // Ignore detection tests + babinfo->last_did.tv_sec = 0; + + babinfo->initialised = true; + + return; + +cleanup: + close(babinfo->spifd); + munmap((void *)(babinfo->gpio), BAB_SPI_BUFSIZ); +unalloc: + free(babinfo); + free(babcgpu); +} + +static void bab_identify(__maybe_unused struct cgpu_info *babcgpu) +{ +} + +// thread to do spi txrx +static void *bab_spi(void *userdata) +{ + struct cgpu_info *babcgpu = (struct cgpu_info *)userdata; + struct bab_info *babinfo = (struct bab_info *)(babcgpu->device_data); + struct timeval start, stop, send, now; + K_ITEM *sitem, *witem; + double wait, delay; + int chip, band; + + applog(LOG_DEBUG, "%s%i: SPIing...", + babcgpu->drv->name, babcgpu->device_id); + + // Wait until we're ready + while (babcgpu->shutdown == false) { + if (babinfo->initialised) { + break; + } + cgsleep_ms(3); + } + + cgtime(&start); + while (babcgpu->shutdown == false) { + K_WLOCK(babinfo->spi_list); + sitem = k_unlink_tail(babinfo->spi_list); + K_WUNLOCK(babinfo->spi_list); + + if (!sitem) { + cgtime(&stop); + wait = us_tdiff(&stop, &start); + if (wait > BAB_LONG_uS) { + applog(LOG_WARNING, "%s%i: SPI waiting %fs ...", + babcgpu->drv->name, + babcgpu->device_id, + (float)wait / 1000000.0); + cgsem_mswait(&(babinfo->spi_work), BAB_LONG_WAIT_mS); + } else + cgsem_mswait(&(babinfo->spi_work), (int)((BAB_LONG_uS - wait) / 1000)); + continue; + } + + // TODO: need an LP/urgent flag to skip this possible cgsem_mswait() + // maybe zero last_sent_work.tv_sec ? + while (babinfo->last_sent_work.tv_sec) { + cgtime(&now); + delay = tdiff(&now, &(babinfo->last_sent_work)) * 1000.0; + if (delay < BAB_EXPECTED_WORK_DELAY_mS) + cgsem_mswait(&(babinfo->spi_work), BAB_EXPECTED_WORK_DELAY_mS - delay); + else + break; + } + + /* + * TODO: handle if an LP happened after bab_do_work() started + * i.e. we don't want to send the work + * Have an LP counter that at this point would show the work + * is stale - so don't send it + */ + cgtime(&send); + bab_txrx(sitem, false); + cgtime(&start); + + // The work isn't added to the chip until it has been sent + K_WLOCK(babinfo->wfree_list); + for (chip = 0; chip < babinfo->chips; chip++) { + witem = DATAS(sitem)->witems[chip]; + if (witem) { + memcpy(&(DATAW(witem)->work_start), &(DATAS(sitem)->work_start), + sizeof(DATAW(witem)->work_start)); + k_add_head(babinfo->chip_work[chip], witem); +#if UPDATE_HISTORY + babinfo->work_count[chip]++; +#endif + if (babinfo->first_work[chip].tv_sec == 0) + memcpy(&(babinfo->first_work[chip]), &send, sizeof(send)); + } + } + K_WUNLOCK(babinfo->wfree_list); + + K_WLOCK(babinfo->spi_sent); + k_add_head(babinfo->spi_sent, sitem); + K_WUNLOCK(babinfo->spi_sent); + + cgsem_post(&(babinfo->spi_reply)); + + // Store stats + if (babinfo->last_sent_work.tv_sec) { + delay = tdiff(&send, &(babinfo->last_sent_work)); + babinfo->delay_count++; + if (babinfo->delay_min == 0 || babinfo->delay_min > delay) + babinfo->delay_min = delay; + if (babinfo->delay_max < delay) + babinfo->delay_max = delay; + if (delay < BAB_DELAY_BASE) + band = 0; + else if (delay >= (BAB_DELAY_BASE+BAB_DELAY_STEP*(BAB_DELAY_BANDS+1))) + band = BAB_DELAY_BANDS+1; + else + band = (int)(((double)delay - BAB_DELAY_BASE) / BAB_DELAY_STEP) + 1; + babinfo->delay_bands[band]++; + } + memcpy(&(babinfo->last_sent_work), &send, sizeof(start)); + + delay = tdiff(&start, &send); + babinfo->send_count++; + babinfo->send_total += delay; + if (babinfo->send_min == 0 || babinfo->send_min > delay) + babinfo->send_min = delay; + if (babinfo->send_max < delay) + babinfo->send_max = delay; + + cgsem_mswait(&(babinfo->spi_work), BAB_STD_WAIT_mS); + } + + return NULL; +} + +static void bab_flush_work(struct cgpu_info *babcgpu) +{ + struct bab_info *babinfo = (struct bab_info *)(babcgpu->device_data); + + applog(LOG_DEBUG, "%s%i: flushing work", + babcgpu->drv->name, babcgpu->device_id); + + mutex_lock(&(babinfo->did_lock)); + babinfo->last_did.tv_sec = 0; + mutex_unlock(&(babinfo->did_lock)); + + cgsem_post(&(babinfo->scan_work)); +} + +#define DATA_MERKLE7 16 +#define DATA_NTIME 17 +#define DATA_BITS 18 +#define DATA_NONCE 19 + +#define WORK_MERKLE7 (16*4) +#define WORK_NTIME (17*4) +#define WORK_BITS (18*4) +#define WORK_NONCE (19*4) + +#if UPDATE_HISTORY +static void process_history(struct cgpu_info *babcgpu, int chip, struct timeval *when, bool good, struct timeval *now) +{ + struct bab_info *babinfo = (struct bab_info *)(babcgpu->device_data); + uint64_t good_nonces, bad_nonces; + uint8_t chip_fast; + double tune; + K_ITEM *item; + int i; + + K_WLOCK(babinfo->nfree_list); + item = k_unlink_head(babinfo->nfree_list); + memcpy(&(DATAN(item)->found), when, sizeof(*when)); + if (good) + k_add_head(babinfo->good_nonces[chip], item); + else + k_add_head(babinfo->bad_nonces[chip], item); + + // Remove all expired history + for (i = 0; i < babinfo->chips; i++) { + item = babinfo->good_nonces[i]->tail; + while (item) { + if (tdiff(now, &(DATAN(item)->found)) < HISTORY_TIME_S) + break; + + k_unlink_item(babinfo->good_nonces[i], item); + k_add_head(babinfo->nfree_list, item); + + item = babinfo->good_nonces[i]->tail; + } + + item = babinfo->bad_nonces[i]->tail; + while (item) { + if (tdiff(now, &(DATAN(item)->found)) < HISTORY_TIME_S) + break; + + k_unlink_item(babinfo->bad_nonces[i], item); + k_add_head(babinfo->nfree_list, item); + + item = babinfo->bad_nonces[i]->tail; + } + } + good_nonces = babinfo->good_nonces[chip]->count; + bad_nonces = babinfo->bad_nonces[chip]->count; + + K_WUNLOCK(babinfo->nfree_list); + + // Tuning ... + if (tdiff(now, &(babinfo->first_work[chip])) >= HISTORY_TIME_S && + tdiff(now, &(babinfo->last_tune[chip])) >= HISTORY_TIME_S && + (good_nonces + bad_nonces) > 0) { + + chip_fast = babinfo->chip_fast[chip]; + + /* + * If bad then step it down and remember the speed + * TODO: does a speed change reset the chip? Or is there a reset? + */ + if (good_nonces == 0) { + if (chip_fast > babinfo->min_speed) { + if (babinfo->bad_fast[chip] > chip_fast) + babinfo->bad_fast[chip] = chip_fast; + + babinfo->chip_fast[chip]--; + + applog(LOG_WARNING, "%s%d: Chip %d BAD - speed down from %d to %d", + babcgpu->drv->name, babcgpu->device_id, + chip, (int)chip_fast, (int)chip_fast - 1); + } else { + /* + * Permanently BAD since we're already at the minumum speed + * but only getting bad nonces + */ + if (babinfo->bad_msg[chip] == false) { + applog(LOG_WARNING, "%s%d: Chip %d BAD - at min speed %d", + babcgpu->drv->name, babcgpu->device_id, + chip, (int)chip_fast); + + babinfo->bad_msg[chip] = true; + } + } + goto tune_over; + } + + /* + * It 'was' permanently BAD but a good nonce came back! + */ + if (babinfo->bad_msg[chip]) { + applog(LOG_WARNING, "%s%d: Chip %d REVIVED - at speed %d", + babcgpu->drv->name, babcgpu->device_id, + chip, (int)chip_fast); + + babinfo->bad_msg[chip] = false; + } + + /* + * Since we have found 'some' nonces - + * make sure bad_fast is higher than current chip_fast + */ + if (babinfo->bad_fast[chip] <= chip_fast) + babinfo->bad_fast[chip] = chip_fast + 1; + + tune = (double)bad_nonces / (double)(good_nonces + bad_nonces) * 100.0; + + /* + * TODO: it appears some chips just get a % bad at low speeds + * so we should handle them by weighting the speed reduction vs + * the HW% gained from the reduction (i.e. GH/s) + * Maybe handle that when they hit min_speed, then do a gradual speed + * up verifying if it is really making GH/s worse or better + */ + + // Tune it down if error rate is too high (and it's above min) + if (tune >= babinfo->tune_down && chip_fast > babinfo->min_speed) { + babinfo->chip_fast[chip]--; + + applog(LOG_WARNING, "%s%d: Chip %d High errors %.2f%% - speed down %d to %d", + babcgpu->drv->name, babcgpu->device_id, + chip, tune, (int)chip_fast, (int)chip_fast - 1); + + goto tune_over; + } + + /* + * TODO: if we are at bad_fast-1 and tune_up + * and bad_fast was set more than some time limit ago + * then consider increasing bad_fast by 1? + */ + + // Tune it up if error rate is low enough + if (tune <= babinfo->tune_up && + chip_fast < babinfo->max_speed && + chip_fast < (babinfo->bad_fast[chip] - 1)) { + babinfo->chip_fast[chip]++; + + applog(LOG_WARNING, "%s%d: Chip %d Low errors %.2f%% - speed up %d to %d", + babcgpu->drv->name, babcgpu->device_id, + chip, tune, (int)chip_fast, (int)chip_fast + 1); + + goto tune_over; + } +tune_over: + cgtime(&(babinfo->last_tune[chip])); + } +} +#endif + +/* + * Find the matching work item by checking each nonce against + * work items for the nonces chip + */ +static K_ITEM *process_nonce(struct thr_info *thr, struct cgpu_info *babcgpu, K_ITEM *ritem, uint32_t raw_nonce, K_ITEM *newest_witem) +{ + struct bab_info *babinfo = (struct bab_info *)(babcgpu->device_data); + unsigned int links, proc_links, work_links, tests; + int try_sta, try_fin, offset; + K_ITEM *witem, *wtail; + struct timeval now; + bool not_first_reply; + uint32_t nonce; + int chip; + + chip = DATAR(ritem)->chip; + not_first_reply = DATAR(ritem)->not_first_reply; + + babinfo->chip_nonces[chip]++; + + /* + * We can grab the head of the chip work queue and then release + * the lock and follow it to the end and back, since the other + * thread will only add items above the head - it wont touch + * any of the prev/next pointers from the head to the end - + * except the head->prev pointer may get changed + */ + K_RLOCK(babinfo->chip_work[chip]); + witem = babinfo->chip_work[chip]->head; + K_RUNLOCK(babinfo->chip_work[chip]); + + if (!witem) { + applog(LOG_ERR, "%s%i: chip %d has no work, 1 nonce discarded!", + babcgpu->drv->name, babcgpu->device_id, chip); + babinfo->untested_nonces++; + return newest_witem; + } + + babinfo->tested_nonces++; + + if ((raw_nonce & 0xff) < 0x1c) { + // Will only be this offset + try_sta = BAB_OFF_0x1C_STA; + try_fin = BAB_OFF_0x1C_FIN; + } else { + // Will only be one of the other offsets + try_sta = BAB_OFF_OTHER_STA; + try_fin = BAB_OFF_OTHER_FIN; + } + + nonce = bab_decnonce(raw_nonce); + + cgtime(&now); + + tests = links = proc_links = work_links = 0; + wtail = witem; + while (wtail && wtail->next) { + work_links++; + wtail = wtail->next; + } + while (wtail) { + if (!(DATAW(wtail)->work)) { + applog(LOG_ERR, "%s%i: chip %d witem links %d has no work!", + babcgpu->drv->name, + babcgpu->device_id, + chip, links); + } else { + if (ms_tdiff(&now, &(DATAW(wtail)->work_start)) >= BAB_WORK_EXPIRE_mS) + proc_links--; + else { + for (offset = try_sta; offset <= try_fin; offset++) { + tests++; + if (test_nonce(DATAW(wtail)->work, nonce + bab_nonce_offsets[offset])) { + submit_tested_work(thr, DATAW(wtail)->work); + babinfo->nonce_offset_count[offset]++; + babinfo->chip_good[chip]++; + DATAW(wtail)->nonces++; + + mutex_lock(&(babinfo->nonce_lock)); + babinfo->new_nonces++; + mutex_unlock(&(babinfo->nonce_lock)); + + babinfo->ok_nonces++; + babinfo->total_tests += tests; + if (babinfo->max_tests_per_nonce < tests) + babinfo->max_tests_per_nonce = tests; + babinfo->total_links += links; + babinfo->total_proc_links += proc_links; + if (babinfo->max_links < links) + babinfo->max_links = links; + if (babinfo->max_proc_links < proc_links) + babinfo->max_proc_links = proc_links; + babinfo->total_work_links += work_links; + + babinfo->chip_cont_bad[chip] = 0; +#if UPDATE_HISTORY + process_history(babcgpu, chip, + &(DATAR(ritem)->when), + true, &now); +#endif + + if (newest_witem == NULL || + ms_tdiff(&(DATAW(wtail)->work_start), + &(DATAW(newest_witem)->work_start)) < 0) + return wtail; + + return newest_witem; + } + } + } + } + if (wtail == witem) + break; + wtail = wtail->prev; + links++; + proc_links++; + } + + if (not_first_reply) { + babinfo->chip_bad[chip]++; + inc_hw_errors(thr); + + babinfo->fail++; + babinfo->fail_total_tests += tests; + babinfo->fail_total_links += links; + babinfo->fail_total_work_links += work_links; + + babinfo->chip_cont_bad[chip]++; + if (babinfo->chip_max_bad[chip] < babinfo->chip_cont_bad[chip]) + babinfo->chip_max_bad[chip] = babinfo->chip_cont_bad[chip]; + + // Handle chips with only bad results + if (babinfo->disabled[chip] == false && + babinfo->chip_good[chip] == 0 && + babinfo->chip_bad[chip] >= BAB_BAD_COUNT && + tdiff(&now, &(babinfo->first_work[chip])) >= BAB_BAD_TO_MIN) { + if (babinfo->chip_fast[chip] > babinfo->min_speed) + babinfo->chip_fast[chip] = babinfo->min_speed; + else if (tdiff(&now, &(babinfo->first_work[chip])) > BAB_BAD_DEAD) { + babinfo->disabled[chip] = true; + babinfo->total_disabled++; + applog(LOG_ERR, "%s%i: chip %d disabled!", + babcgpu->drv->name, + babcgpu->device_id, + chip); + } + } +#if UPDATE_HISTORY + process_history(babcgpu, chip, &(DATAR(ritem)->when), false, &now); +#endif + } else { + babinfo->initial_ignored++; + babinfo->ign_total_tests += tests; + babinfo->ign_total_links += links; + babinfo->ign_total_work_links += work_links; + } + + return newest_witem; +} + +/* + * On completion discard any work items older than BAB_WORK_EXPIRE_mS + * and any work items of the chip older than the work of the newest nonce work item + */ +static void oknonces(struct thr_info *thr, struct cgpu_info *babcgpu, K_ITEM *ritem) +{ + uint32_t raw_nonce; + K_ITEM *witem; + int nonces; + + witem = NULL; + + for (nonces = 0; nonces < DATAR(ritem)->nonces; nonces++) { + raw_nonce = DATAR(ritem)->nonce[nonces]; + + witem = process_nonce(thr, babcgpu, ritem, raw_nonce, witem); + } + + cleanup_older(babcgpu, DATAR(ritem)->chip, witem); +} + +// Check at least every ... +#define BAB_RESULT_DELAY_mS 999 + +// Results checking thread +static void *bab_res(void *userdata) +{ + struct cgpu_info *babcgpu = (struct cgpu_info *)userdata; + struct bab_info *babinfo = (struct bab_info *)(babcgpu->device_data); + struct thr_info *thr = babcgpu->thr[0]; + K_ITEM *ritem; + + applog(LOG_DEBUG, "%s%i: Results...", + babcgpu->drv->name, babcgpu->device_id); + + // Wait until we're ready + while (babcgpu->shutdown == false) { + if (babinfo->initialised) { + break; + } + cgsleep_ms(3); + } + + ritem = NULL; + while (babcgpu->shutdown == false) { + K_WLOCK(babinfo->res_list); + if (ritem) { + // Release the old one + k_add_head(babinfo->rfree_list, ritem); + ritem = NULL; + } + // Check for a new one + ritem = k_unlink_tail(babinfo->res_list); + K_WUNLOCK(babinfo->res_list); + + if (!ritem) { + cgsem_mswait(&(babinfo->process_reply), BAB_RESULT_DELAY_mS); + continue; + } + + oknonces(thr, babcgpu, ritem); + } + + return NULL; +} + +/* + * 1.0s per nonce = 4.2GH/s + * 0.9s per nonce = 4.8GH/s + * On a slow machine, reducing this may resolve: + * BaB0: SPI waiting 1.2...s + */ +#define BAB_STD_WORK_DELAY_uS 900000 + +static bool bab_do_work(struct cgpu_info *babcgpu) +{ + struct bab_info *babinfo = (struct bab_info *)(babcgpu->device_data); + int work_items = 0; + K_ITEM *witem, *sitem, *ritem; + struct timeval when, now; + double delay; + int chip, rep, j, nonces, spie = 0, miso = 0; + uint32_t nonce, spichk; + bool res; + + cgtime(&now); + mutex_lock(&(babinfo->did_lock)); + delay = us_tdiff(&now, &(babinfo->last_did)); + mutex_unlock(&(babinfo->did_lock)); + if (delay < BAB_STD_WORK_DELAY_uS) + return false; + + K_WLOCK(babinfo->sfree_list); + sitem = k_unlink_head_zero(babinfo->sfree_list); + K_WUNLOCK(babinfo->sfree_list); + + for (chip = 0; chip < babinfo->chips; chip++) { + if (!(babinfo->disabled[chip])) { + // TODO: ignore stale work + K_WLOCK(babinfo->available_work); + witem = k_unlink_tail(babinfo->available_work); + K_WUNLOCK(babinfo->available_work); + if (!witem) { + applog(LOG_ERR, "%s%i: short work list (%d) %d expected %d - reset", + babcgpu->drv->name, babcgpu->device_id, + chip, work_items, + babinfo->chips - babinfo->total_disabled); + + // Put them back in the order they were taken + K_WLOCK(babinfo->available_work); + for (j = chip-1; j >= 0; j--) { + witem = DATAS(sitem)->witems[j]; + if (witem) + k_add_tail(babinfo->available_work, witem); + } + K_WUNLOCK(babinfo->available_work); + + K_WLOCK(babinfo->sfree_list); + k_add_head(babinfo->sfree_list, sitem); + K_WUNLOCK(babinfo->sfree_list); + + return false; + } + + /* + * TODO: do this when we get work except on LP? + * (not LP so we only do ms3steps for work required) + * Though that may more likely trigger the applog(short work list) above? + */ + if (DATAW(witem)->ci_setup == false) { + memcpy((void *)&(DATAW(witem)->chip_input.midstate[0]), + DATAW(witem)->work->midstate, + sizeof(DATAW(witem)->work->midstate)); + memcpy((void *)&(DATAW(witem)->chip_input.merkle7), + (void *)&(DATAW(witem)->work->data[WORK_MERKLE7]), + MERKLE_BYTES); + + bab_ms3steps((void *)&(DATAW(witem)->chip_input)); + + DATAW(witem)->ci_setup = true; + } + + DATAS(sitem)->witems[chip] = witem; + work_items++; + } + } + + // Send + bab_put(babinfo, sitem); + + // Receive + res = bab_get(babcgpu, babinfo, &when); + if (!res) { + applog(LOG_DEBUG, "%s%i: didn't get work reply ...", + babcgpu->drv->name, babcgpu->device_id); + return false; + } + + applog(LOG_DEBUG, "%s%i: Did get work reply ...", + babcgpu->drv->name, babcgpu->device_id); + + for (chip = 0; chip < babinfo->chips; chip++) { + if (!(babinfo->disabled[chip])) { + K_WLOCK(babinfo->rfree_list); + ritem = k_unlink_head(babinfo->rfree_list); + K_WUNLOCK(babinfo->rfree_list); + + DATAR(ritem)->chip = chip; + DATAR(ritem)->not_first_reply = babinfo->not_first_reply[chip]; + memcpy(&(DATAR(ritem)->when), &when, sizeof(when)); + + spichk = babinfo->chip_results[chip].spichk; + if (spichk != 0 && spichk != 0xffffffff) { + babinfo->chip_spie[chip]++; + spie++; + // Test the results anyway + } + + nonces = 0; + for (rep = 0; rep < BAB_REPLY_NONCES; rep++) { + nonce = babinfo->chip_results[chip].nonce[rep]; + if (nonce != babinfo->chip_prev[chip].nonce[rep]) { + if ((nonce & BAB_EVIL_MASK) == BAB_EVIL_NONCE) + babinfo->discarded_e0s++; + else + DATAR(ritem)->nonce[nonces++] = nonce; + } + } + + if (nonces == BAB_REPLY_NONCES) { + babinfo->chip_miso[chip]++; + miso++; + // Test the results anyway + } + + /* + * Send even with zero nonces + * so cleanup_older() is called for the chip + */ + DATAR(ritem)->nonces = nonces; + K_WLOCK(babinfo->res_list); + k_add_head(babinfo->res_list, ritem); + K_WUNLOCK(babinfo->res_list); + + cgsem_post(&(babinfo->process_reply)); + + babinfo->not_first_reply[chip] = true; + + memcpy((void *)(&(babinfo->chip_prev[chip])), + (void *)(&(babinfo->chip_results[chip])), + sizeof(struct bab_work_reply)); + } + + } + + applog(LOG_DEBUG, "Work: items:%d spie:%d miso:%d", work_items, spie, miso); + + return true; +} + +static bool bab_thread_prepare(struct thr_info *thr) +{ + struct cgpu_info *babcgpu = thr->cgpu; + struct bab_info *babinfo = (struct bab_info *)(babcgpu->device_data); + + if (thr_info_create(&(babinfo->spi_thr), NULL, bab_spi, (void *)babcgpu)) { + applog(LOG_ERR, "%s%i: SPI thread create failed", + babcgpu->drv->name, babcgpu->device_id); + return false; + } + pthread_detach(babinfo->spi_thr.pth); + + /* + * We require a seperate results checking thread since there is a lot + * of work done checking the results multiple times - thus we don't + * want that delay affecting sending/receiving work to/from the device + */ + if (thr_info_create(&(babinfo->res_thr), NULL, bab_res, (void *)babcgpu)) { + applog(LOG_ERR, "%s%i: Results thread create failed", + babcgpu->drv->name, babcgpu->device_id); + return false; + } + pthread_detach(babinfo->res_thr.pth); + + return true; +} + +static void bab_shutdown(struct thr_info *thr) +{ + struct cgpu_info *babcgpu = thr->cgpu; + struct bab_info *babinfo = (struct bab_info *)(babcgpu->device_data); + int i; + + applog(LOG_DEBUG, "%s%i: shutting down", + babcgpu->drv->name, babcgpu->device_id); + + for (i = 0; i < babinfo->chips; i++) +// TODO: bab_shutdown(babcgpu, babinfo, i); + ; + + babcgpu->shutdown = true; +} + +static bool bab_queue_full(struct cgpu_info *babcgpu) +{ + struct bab_info *babinfo = (struct bab_info *)(babcgpu->device_data); + int roll, roll_limit = BAB_MAX_ROLLTIME; + struct work *work, *usework; + K_ITEM *item; + int count, need; + bool ret, rolled; + + K_RLOCK(babinfo->available_work); + count = babinfo->available_work->count; + K_RUNLOCK(babinfo->available_work); + + if (count >= (babinfo->chips - babinfo->total_disabled)) + ret = true; + else { + need = (babinfo->chips - babinfo->total_disabled) - count; + work = get_queued(babcgpu); + if (work) { + if (roll_limit > work->drv_rolllimit) + roll_limit = work->drv_rolllimit; + roll = 0; + do { + if (roll == 0) { + usework = work; + babinfo->work_unrolled++; + rolled = false; + } else { + usework = copy_work_noffset(work, roll); + babinfo->work_rolled++; + rolled = true; + } + + K_WLOCK(babinfo->wfree_list); + item = k_unlink_head_zero(babinfo->wfree_list); + DATAW(item)->work = usework; + DATAW(item)->rolled = rolled; + k_add_head(babinfo->available_work, item); + K_WUNLOCK(babinfo->wfree_list); + } while (--need > 0 && ++roll <= roll_limit); + } else { + // Avoid a hard loop when we can't get work fast enough + cgsleep_us(42); + } + + if (need > 0) + ret = false; + else + ret = true; + } + + return ret; +} + +#define BAB_STD_DELAY_mS 100 + +/* + * TODO: allow this to run through more than once - the second+ + * time not sending any new work unless a flush occurs since: + * at the moment we have BAB_STD_WORK_mS latency added to earliest replies + */ +static int64_t bab_scanwork(__maybe_unused struct thr_info *thr) +{ + struct cgpu_info *babcgpu = thr->cgpu; + struct bab_info *babinfo = (struct bab_info *)(babcgpu->device_data); + int64_t hashcount = 0; + int count; + + bab_do_work(babcgpu); + + K_RLOCK(babinfo->available_work); + count = babinfo->available_work->count; + K_RUNLOCK(babinfo->available_work); + + if (count >= babinfo->chips) + cgsem_mswait(&(babinfo->scan_work), BAB_STD_DELAY_mS); + + mutex_lock(&(babinfo->nonce_lock)); + if (babinfo->new_nonces) { + hashcount += 0xffffffffull * babinfo->new_nonces; + babinfo->new_nonces = 0; + } + mutex_unlock(&(babinfo->nonce_lock)); + + return hashcount; +} + +#define CHIPS_PER_STAT 16 +#define FMT_RANGE "%d-%d" + +static struct api_data *bab_api_stats(struct cgpu_info *babcgpu) +{ + struct bab_info *babinfo = (struct bab_info *)(babcgpu->device_data); + uint64_t history_good[BAB_MAXCHIPS], history_bad[BAB_MAXCHIPS]; + uint64_t his_good_tot, his_bad_tot; + double history_elapsed[BAB_MAXCHIPS], diff; + bool elapsed_is_good[BAB_MAXCHIPS]; + int speeds[BAB_CHIP_SPEEDS]; + struct api_data *root = NULL; + char data[2048]; + char buf[32]; + int spi_work, chip_work, sp, chip, bank, chip_off, board, last_board; + int i, to, j, k; + bool bad; + struct timeval now; + double elapsed, ghs; + float ghs_sum, his_ghs_tot; + float tot, hw; + K_ITEM *item; + + if (babinfo->initialised == false) + return NULL; + + memset(&speeds, 0, sizeof(speeds)); + + root = api_add_int(root, "Version", &(babinfo->version), true); + root = api_add_int(root, "Chips", &(babinfo->chips), true); + root = api_add_int(root, "Boards", &(babinfo->boards), true); + root = api_add_int(root, "Banks", &(babinfo->banks), true); + + data[0] = '\0'; + for (i = 0; i <= BAB_MAXBANKS; i++) { + snprintf(buf, sizeof(buf), "%s%d", + (i == 0) ? "" : " ", + babinfo->chips_per_bank[i]); + strcat(data, buf); + } + root = api_add_string(root, "Chips Per Bank", data, true); + + data[0] = '\0'; + for (i = 0; i <= BAB_MAXBANKS; i++) { + snprintf(buf, sizeof(buf), "%s%d", + (i == 0) ? "" : " ", + babinfo->missing_chips_per_bank[i]); + strcat(data, buf); + } + root = api_add_string(root, "Missing Chips Per Bank", data, true); + + cgtime(&now); + elapsed = tdiff(&now, &(babcgpu->dev_start_tv)); + + root = api_add_elapsed(root, "Device Elapsed", &elapsed, true); + + root = api_add_string(root, "History Enabled", +#if UPDATE_HISTORY + "true", +#else + "false", +#endif + true); + + int chs = HISTORY_TIME_S; + root = api_add_int(root, "Chip History Limit", &chs, true); + + K_RLOCK(babinfo->nfree_list); + for (i = 0; i < babinfo->chips; i++) { + item = babinfo->good_nonces[i]->tail; + elapsed_is_good[i] = true; + if (!item) + history_elapsed[i] = 0; + else + history_elapsed[i] = tdiff(&now, &(DATAN(item)->found)); + + item = babinfo->bad_nonces[i]->tail; + if (item) { + diff = tdiff(&now, &(DATAN(item)->found)); + if (history_elapsed[i] < diff) { + history_elapsed[i] = diff; + elapsed_is_good[i] = false; + } + } + history_good[i] = babinfo->good_nonces[i]->count; + history_bad[i] = babinfo->bad_nonces[i]->count; + } + K_RUNLOCK(babinfo->nfree_list); + + his_ghs_tot = 0; + for (i = 0; i < babinfo->chips; i += CHIPS_PER_STAT) { + to = i + CHIPS_PER_STAT - 1; + if (to >= babinfo->chips) + to = babinfo->chips - 1; + + data[0] = '\0'; + for (j = i; j <= to; j++) { + snprintf(buf, sizeof(buf), + "%s%"PRIu64, + j == i ? "" : " ", + babinfo->chip_nonces[j]); + strcat(data, buf); + } + snprintf(buf, sizeof(buf), "Nonces "FMT_RANGE, i, to); + root = api_add_string(root, buf, data, true); + + data[0] = '\0'; + for (j = i; j <= to; j++) { + snprintf(buf, sizeof(buf), + "%s%"PRIu64, + j == i ? "" : " ", + babinfo->chip_good[j]); + strcat(data, buf); + } + snprintf(buf, sizeof(buf), "Good "FMT_RANGE, i, to); + root = api_add_string(root, buf, data, true); + + data[0] = '\0'; + for (j = i; j <= to; j++) { + snprintf(buf, sizeof(buf), + "%s%"PRIu64, + j == i ? "" : " ", + babinfo->chip_bad[j]); + strcat(data, buf); + } + snprintf(buf, sizeof(buf), "Bad "FMT_RANGE, i, to); + root = api_add_string(root, buf, data, true); + + data[0] = '\0'; + for (j = i; j <= to; j++) { + snprintf(buf, sizeof(buf), + "%s0x%02x", + j == i ? "" : " ", + (int)(babinfo->chip_conf[j])); + strcat(data, buf); + } + snprintf(buf, sizeof(buf), "Conf "FMT_RANGE, i, to); + root = api_add_string(root, buf, data, true); + + data[0] = '\0'; + for (j = i; j <= to; j++) { + snprintf(buf, sizeof(buf), + "%s%d", + j == i ? "" : " ", + (int)(babinfo->chip_fast[j])); + strcat(data, buf); + } + snprintf(buf, sizeof(buf), "Fast "FMT_RANGE, i, to); + root = api_add_string(root, buf, data, true); + + data[0] = '\0'; + for (j = i; j <= to; j++) { + snprintf(buf, sizeof(buf), + "%s%d", + j == i ? "" : " ", + (int)(babinfo->chip_spie[j])); + strcat(data, buf); + } + snprintf(buf, sizeof(buf), "Spie "FMT_RANGE, i, to); + root = api_add_string(root, buf, data, true); + + data[0] = '\0'; + for (j = i; j <= to; j++) { + snprintf(buf, sizeof(buf), + "%s%d", + j == i ? "" : " ", + (int)(babinfo->chip_miso[j])); + strcat(data, buf); + } + snprintf(buf, sizeof(buf), "Miso "FMT_RANGE, i, to); + root = api_add_string(root, buf, data, true); + + data[0] = '\0'; + for (j = i; j <= to; j++) { + tot = (float)(babinfo->chip_good[j] + babinfo->chip_bad[j]); + if (tot != 0) + hw = 100.0 * (float)(babinfo->chip_bad[j]) / tot; + else + hw = 0; + snprintf(buf, sizeof(buf), + "%s%.3f", + j == i ? "" : " ", hw); + strcat(data, buf); + } + snprintf(buf, sizeof(buf), "HW%% "FMT_RANGE, i, to); + root = api_add_string(root, buf, data, true); + + ghs_sum = 0; + data[0] = '\0'; + for (j = i; j <= to; j++) { + if (elapsed > 0) { + ghs = (double)(babinfo->chip_good[j]) * 0xffffffffull / + elapsed / 1000000000.0; + } else + ghs = 0; + + snprintf(buf, sizeof(buf), + "%s%.3f", + j == i ? "" : " ", ghs); + strcat(data, buf); + ghs_sum += (float)ghs; + } + snprintf(buf, sizeof(buf), "GHs "FMT_RANGE, i, to); + root = api_add_string(root, buf, data, true); + + snprintf(buf, sizeof(buf), "Sum GHs "FMT_RANGE, i, to); + root = api_add_avg(root, buf, &ghs_sum, true); + + data[0] = '\0'; + for (j = i; j <= to; j++) { + snprintf(buf, sizeof(buf), + "%s%"PRIu64, + j == i ? "" : " ", + babinfo->chip_cont_bad[j]); + strcat(data, buf); + } + snprintf(buf, sizeof(buf), "Cont-Bad "FMT_RANGE, i, to); + root = api_add_string(root, buf, data, true); + + data[0] = '\0'; + for (j = i; j <= to; j++) { + snprintf(buf, sizeof(buf), + "%s%"PRIu64, + j == i ? "" : " ", + babinfo->chip_max_bad[j]); + strcat(data, buf); + } + snprintf(buf, sizeof(buf), "Max-Bad "FMT_RANGE, i, to); + root = api_add_string(root, buf, data, true); + + data[0] = '\0'; + for (j = i; j <= to; j++) { + snprintf(buf, sizeof(buf), + "%s%"PRIu64, + j == i ? "" : " ", + history_good[j]); + strcat(data, buf); + } + snprintf(buf, sizeof(buf), "History Good "FMT_RANGE, i, to); + root = api_add_string(root, buf, data, true); + + data[0] = '\0'; + for (j = i; j <= to; j++) { + snprintf(buf, sizeof(buf), + "%s%"PRIu64, + j == i ? "" : " ", + history_bad[j]); + strcat(data, buf); + } + snprintf(buf, sizeof(buf), "History Bad "FMT_RANGE, i, to); + root = api_add_string(root, buf, data, true); + + data[0] = '\0'; + for (j = i; j <= to; j++) { + tot = (float)(history_good[j] + history_bad[j]); + if (tot != 0) + hw = 100.0 * (float)(history_bad[j]) / tot; + else + hw = 0; + snprintf(buf, sizeof(buf), + "%s%.3f", + j == i ? "" : " ", hw); + strcat(data, buf); + } + snprintf(buf, sizeof(buf), "History HW%% "FMT_RANGE, i, to); + root = api_add_string(root, buf, data, true); + + ghs_sum = 0; + data[0] = '\0'; + for (j = i; j <= to; j++) { + if (history_elapsed[j] > 0) { + double num = history_good[j]; + // exclude the first nonce? + if (elapsed_is_good[j]) + num--; + ghs = num * 0xffffffffull / + history_elapsed[j] / 1000000000.0; + } else + ghs = 0; + + snprintf(buf, sizeof(buf), + "%s%.3f", + j == i ? "" : " ", ghs); + strcat(data, buf); + + ghs_sum += (float)ghs; + + // Setup speed range data + for (sp = 0; sp < BAB_CHIP_SPEEDS - 1; sp++) { + if (ghs <= chip_speed_ranges[sp]) { + speeds[sp]++; + break; + } + } + if (sp >= (BAB_CHIP_SPEEDS - 1)) + speeds[BAB_CHIP_SPEEDS - 1]++; + } + snprintf(buf, sizeof(buf), "History GHs "FMT_RANGE, i, to); + root = api_add_string(root, buf, data, true); + + snprintf(buf, sizeof(buf), "Sum History GHs "FMT_RANGE, i, to); + root = api_add_avg(root, buf, &ghs_sum, true); + + his_ghs_tot += ghs_sum; + } + + root = api_add_avg(root, "Total History GHs", &his_ghs_tot, true); + + his_good_tot = his_bad_tot = 0; + for (i = 0; i < babinfo->chips; i++) { + his_good_tot += history_good[i]; + his_bad_tot += history_bad[i]; + } + if (his_good_tot + his_bad_tot) + tot = 100.0 * (float)his_bad_tot / (float)(his_good_tot + his_bad_tot); + else + tot = 0.0; + root = api_add_avg(root, "Total History HW%", &tot, true); + + for (sp = 0; sp < BAB_CHIP_SPEEDS; sp++) { + if (sp < (BAB_CHIP_SPEEDS - 1)) + ghs = chip_speed_ranges[sp]; + else + ghs = chip_speed_ranges[BAB_CHIP_SPEEDS - 2]; + + snprintf(buf, sizeof(buf), "History Speed %s%.1f %s", + (sp < (BAB_CHIP_SPEEDS - 1)) ? "" : ">", + ghs, chip_speed_names[sp]); + + root = api_add_int(root, buf, &(speeds[sp]), true); + } + + int len, str, siz = 1024; + char *tmp = malloc(siz); + if (!tmp) + quithere(1, "OOM tmp1"); + for (sp = 0; sp < 2; sp++) { + tmp[0] = '\0'; + len = 0; + for (i = 0; i < babinfo->chips; i++) { + if (history_elapsed[i] > 0) { + double num = history_good[i]; + // exclude the first nonce? + if (elapsed_is_good[i]) + num--; + ghs = num * 0xffffffffull / + history_elapsed[i] / 1000000000.0; + } else + ghs = 0; + + if ((sp == 0 || ghs > chip_speed_ranges[sp-1]) && + (ghs <= chip_speed_ranges[sp])) { + bank = babinfo->chip_bank[i]; + chip_off = i; + for (j = 0; j < babinfo->chip_bank[i]; j++) + chip_off -= babinfo->chips_per_bank[j]; + /* + * Bank/Board/Chip are all 1 based + * except V1 Bank = BAB_V1_BANK (0) + * If the bank has any missing chips then a "?" + * is placed after the board number + */ + snprintf(buf, sizeof(buf), "%s%d/%d%s/%d", + len ? " " : "", bank, + (int)(chip_off / BAB_BOARDCHIPS)+1, + babinfo->missing_chips_per_bank[bank] ? + "?" : "", + (chip_off % BAB_BOARDCHIPS)+1); + str = strlen(buf); + while ((len + str + 1) > siz) { + siz += 1024; + tmp = realloc(tmp, siz); + if (!tmp) + quithere(1, "OOM tmp2"); + } + strcpy(tmp + len, buf); + len += str; + } + } + snprintf(buf, sizeof(buf), "History %s", chip_speed_names[sp]); + + root = api_add_string(root, buf, len ? tmp : "None", true); + } + free(tmp); + tmp = NULL; + + switch (babinfo->version) { + case 1: + i = j = BAB_V1_BANK; + break; + case 2: + i = 1; + j = BAB_MAXBANKS; + break; + } + data[0] = '\0'; + for (bank = i; bank <= j; bank++) { + if (babinfo->bank_first_chip[bank] >= 0) { + chip = babinfo->bank_first_chip[bank]; + to = babinfo->bank_last_chip[bank]; + for (; chip <= to; chip += BAB_BOARDCHIPS) { + bad = true; + for (k = chip; (k <= to) && (k < (chip+BAB_BOARDCHIPS)); k++) { + if (history_elapsed[k] > 0) { + double num = history_good[k]; + // exclude the first nonce? + if (elapsed_is_good[k]) + num--; + ghs = num * 0xffffffffull / + history_elapsed[k] / 1000000000.0; + } else + ghs = 0; + + if (ghs > 0.0) { + bad = false; + break; + } + } + if (bad) { + board = (int)((float)(chip - babinfo->bank_first_chip[bank]) / + BAB_BOARDCHIPS) + 1; + snprintf(buf, sizeof(buf), + "%s%d/%d%s", + data[0] ? " " : "", + bank, board, + babinfo->missing_chips_per_bank[bank] ? + "?" : ""); + strcat(data, buf); + } + } + } + } + root = api_add_string(root, "History Bad Boards", data[0] ? data : "None", true); + + data[0] = '\0'; + for (bank = i; bank <= j; bank++) { + if (babinfo->bank_first_chip[bank] >= 0) { + to = babinfo->bank_first_chip[bank]; + chip = babinfo->bank_last_chip[bank]; + for (; chip >= to; chip--) { + bad = true; + if (history_elapsed[chip] > 0) { + double num = history_good[chip]; + // exclude the first nonce? + if (elapsed_is_good[chip]) + num--; + ghs = num * 0xffffffffull / + history_elapsed[chip] / 1000000000.0; + } else + ghs = 0; + + if (ghs > 0.0) + break; + } + /* + * The output here is: a/b+c/d + * a/b is the SPI/board that starts the Bad Chain + * c is the number of boards after a + * d is the total number of chips in the Bad Chain + * A Bad Chain is a continous set of bad chips that + * finish at the end of an SPI chain of boards + * This might be caused by the first board, or the cables attached + * to the first board, in the Bad Chain i.e. a/b + * If c is zero, it's just the last board, so it's the same as any + * other board having bad chips + */ + if (chip < babinfo->bank_last_chip[bank]) { + board = (int)((float)(chip - babinfo->bank_first_chip[bank]) / + BAB_BOARDCHIPS) + 1; + last_board = (int)((float)(babinfo->bank_last_chip[bank] - + babinfo->bank_first_chip[bank]) / + BAB_BOARDCHIPS) + 1; + snprintf(buf, sizeof(buf), + "%s%d/%d%s+%d/%d", + data[0] ? " " : "", + bank, board, + babinfo->missing_chips_per_bank[bank] ? + "?" : "", + last_board - board, + babinfo->bank_last_chip[bank] - chip); + strcat(data, buf); + } + } + } + root = api_add_string(root, "History Bad Chains", data[0] ? data : "None", true); + + root = api_add_int(root, "Disabled Chips", &(babinfo->total_disabled), true); + + for (i = 0; i < BAB_NONCE_OFFSETS; i++) { + snprintf(buf, sizeof(buf), "Nonce Offset 0x%08x", bab_nonce_offsets[i]); + root = api_add_uint64(root, buf, &(babinfo->nonce_offset_count[i]), true); + } + + root = api_add_uint64(root, "Discarded E0s", &(babinfo->discarded_e0s), true); + root = api_add_uint64(root, "Tested", &(babinfo->tested_nonces), true); + root = api_add_uint64(root, "OK", &(babinfo->ok_nonces), true); + root = api_add_uint64(root, "Total Tests", &(babinfo->total_tests), true); + root = api_add_uint64(root, "Max Tests", &(babinfo->max_tests_per_nonce), true); + float avg = babinfo->ok_nonces ? (float)(babinfo->total_tests) / + (float)(babinfo->ok_nonces) : 0; + root = api_add_avg(root, "Avg Tests", &avg, true); + root = api_add_uint64(root, "Untested", &(babinfo->untested_nonces), true); + + root = api_add_uint64(root, "Work Links", &(babinfo->total_links), true); + root = api_add_uint64(root, "Work Processed Links", &(babinfo->total_proc_links), true); + root = api_add_uint64(root, "Max Links", &(babinfo->max_links), true); + root = api_add_uint64(root, "Max Processed Links", &(babinfo->max_proc_links), true); + root = api_add_uint64(root, "Total Work Links", &(babinfo->total_work_links), true); + avg = babinfo->ok_nonces ? (float)(babinfo->total_links) / + (float)(babinfo->ok_nonces) : 0; + root = api_add_avg(root, "Avg Links", &avg, true); + avg = babinfo->ok_nonces ? (float)(babinfo->total_proc_links) / + (float)(babinfo->ok_nonces) : 0; + root = api_add_avg(root, "Avg Proc Links", &avg, true); + avg = babinfo->ok_nonces ? (float)(babinfo->total_work_links) / + (float)(babinfo->ok_nonces) : 0; + root = api_add_avg(root, "Avg Work Links", &avg, true); + + root = api_add_uint64(root, "Fail", &(babinfo->fail), true); + root = api_add_uint64(root, "Fail Total Tests", &(babinfo->fail_total_tests), true); + avg = babinfo->fail ? (float)(babinfo->fail_total_tests) / + (float)(babinfo->fail) : 0; + root = api_add_avg(root, "Fail Avg Tests", &avg, true); + root = api_add_uint64(root, "Fail Work Links", &(babinfo->fail_total_links), true); + root = api_add_uint64(root, "Fail Total Work Links", &(babinfo->fail_total_work_links), true); + + root = api_add_uint32(root, "Initial Ignored", &(babinfo->initial_ignored), true); + root = api_add_uint64(root, "Ign Total Tests", &(babinfo->ign_total_tests), true); + root = api_add_uint64(root, "Ign Work Links", &(babinfo->ign_total_links), true); + root = api_add_uint64(root, "Ign Total Work Links", &(babinfo->ign_total_work_links), true); + + chip_work = 0; + for (i = 0; i < babinfo->chips; i++) + chip_work += babinfo->chip_work[i]->count; + spi_work = babinfo->spi_list->count * babinfo->chips; + + root = api_add_int(root, "WFree Total", &(babinfo->wfree_list->total), true); + root = api_add_int(root, "WFree Count", &(babinfo->wfree_list->count), true); + root = api_add_int(root, "Available Work", &(babinfo->available_work->count), true); + root = api_add_int(root, "SPI Work", &spi_work, true); + root = api_add_int(root, "Chip Work", &chip_work, true); + + root = api_add_int(root, "SFree Total", &(babinfo->sfree_list->total), true); + root = api_add_int(root, "SFree Count", &(babinfo->sfree_list->count), true); + root = api_add_int(root, "SPI Waiting", &(babinfo->spi_list->count), true); + root = api_add_int(root, "SPI Sent", &(babinfo->spi_sent->count), true); + + root = api_add_int(root, "RFree Total", &(babinfo->rfree_list->total), true); + root = api_add_int(root, "RFree Count", &(babinfo->rfree_list->count), true); + root = api_add_int(root, "Result Count", &(babinfo->res_list->count), true); + + int used = babinfo->nfree_list->total - babinfo->nfree_list->count; + root = api_add_int(root, "NFree Total", &(babinfo->nfree_list->total), true); + root = api_add_int(root, "NFree Used", &used, true); + + root = api_add_uint64(root, "Delay Count", &(babinfo->delay_count), true); + root = api_add_double(root, "Delay Min", &(babinfo->delay_min), true); + root = api_add_double(root, "Delay Max", &(babinfo->delay_max), true); + + data[0] = '\0'; + for (i = 0; i <= BAB_DELAY_BANDS; i++) { + snprintf(buf, sizeof(buf), + "%s<%.1f=%"PRIu64, + i == 0 ? "" : " ", + BAB_DELAY_BASE+(BAB_DELAY_STEP*i), + babinfo->delay_bands[i]); + strcat(data, buf); + } + snprintf(buf, sizeof(buf), + " >=%.1f=%"PRIu64, + BAB_DELAY_BASE+BAB_DELAY_STEP*(BAB_DELAY_BANDS+1), + babinfo->delay_bands[BAB_DELAY_BANDS+1]); + strcat(data, buf); + root = api_add_string(root, "Delay Bands", data, true); + + root = api_add_uint64(root, "Send Count", &(babinfo->send_count), true); + root = api_add_double(root, "Send Total", &(babinfo->send_total), true); + avg = babinfo->send_count ? (float)(babinfo->send_total) / + (float)(babinfo->send_count) : 0; + root = api_add_avg(root, "Send Avg", &avg, true); + root = api_add_double(root, "Send Min", &(babinfo->send_min), true); + root = api_add_double(root, "Send Max", &(babinfo->send_max), true); + + root = api_add_int(root, "Reply Wait", &(babinfo->reply_wait), true); + root = api_add_uint64(root, "Reply Waits", &(babinfo->reply_waits), true); + + root = api_add_uint64(root, "Work Unrolled", &(babinfo->work_unrolled), true); + root = api_add_uint64(root, "Work Rolled", &(babinfo->work_rolled), true); + + i = (int)(babinfo->max_speed); + root = api_add_int(root, bab_options[0], &i, true); + i = (int)(babinfo->def_speed); + root = api_add_int(root, bab_options[1], &i, true); + i = (int)(babinfo->min_speed); + root = api_add_int(root, bab_options[2], &i, true); + root = api_add_double(root, bab_options[3], &(babinfo->tune_up), true); + root = api_add_double(root, bab_options[4], &(babinfo->tune_down), true); + i = (int)(babinfo->speed_hz); + root = api_add_int(root, bab_options[5], &i, true); + i = (int)(babinfo->delay_usecs); + root = api_add_int(root, bab_options[6], &i, true); + root = api_add_uint64(root, bab_options[7], &(babinfo->trf_delay), true); + + return root; +} + +static void bab_get_statline_before(char *buf, size_t bufsiz, struct cgpu_info *babcgpu) +{ + struct bab_info *babinfo = (struct bab_info *)(babcgpu->device_data); +#if UPDATE_HISTORY + struct timeval now; + double elapsed; + int i, bad = 0; + + cgtime(&now); + elapsed = tdiff(&now, &(babcgpu->dev_start_tv)); + + // At least get 15s of nonces before saying anything is bad + if (elapsed > 15.0) { + K_RLOCK(babinfo->nfree_list); + for (i = 0; i < babinfo->chips; i++) { + if (babinfo->good_nonces[i]->count == 0 && + babinfo->bad_nonces[i]->count > 1) + bad++; + } + K_RUNLOCK(babinfo->nfree_list); + } + + tailsprintf(buf, bufsiz, "%d.%02d.%03d B:%03d D:%03d", + babinfo->banks, + babinfo->boards, + babinfo->chips, + bad, + babinfo->total_disabled); +#else + tailsprintf(buf, bufsiz, "%d.%02d.%03d D:%03d", + babinfo->banks, + babinfo->boards, + babinfo->chips, + babinfo->total_disabled); +#endif +} +#endif + +struct device_drv bab_drv = { + .drv_id = DRIVER_bab, + .dname = "BlackArrowBitFuryGPIO", + .name = "BaB", + .drv_detect = bab_detect, +#ifdef LINUX + .get_api_stats = bab_api_stats, + .get_statline_before = bab_get_statline_before, + .identify_device = bab_identify, + .thread_prepare = bab_thread_prepare, + .hash_work = hash_queued_work, + .scanwork = bab_scanwork, + .queue_full = bab_queue_full, + .flush_work = bab_flush_work, + .thread_shutdown = bab_shutdown +#endif +}; diff --git a/driver-bflsc.c b/driver-bflsc.c new file mode 100644 index 0000000..f14538e --- /dev/null +++ b/driver-bflsc.c @@ -0,0 +1,2370 @@ +/* + * Copyright 2013 Andrew Smith + * Copyright 2013-2014 Con Kolivas + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef WIN32 +#include +#endif + +#include "compat.h" +#include "miner.h" +#include "usbutils.h" +#include "uthash.h" +#include "driver-bflsc.h" + +int opt_bflsc_overheat = BFLSC_TEMP_OVERHEAT; + +static const char *blank = ""; + +static enum driver_version drv_ver(struct cgpu_info *bflsc, const char *ver) +{ + char *tmp; + + if (strstr(ver, "1.0.0")) + return BFLSC_DRV1; + + if (strstr(ver, "1.0.") || strstr(ver, "1.1.")) { + applog(LOG_WARNING, "%s detect (%s) Warning assuming firmware '%s' is Ver1", + bflsc->drv->dname, bflsc->device_path, ver); + return BFLSC_DRV1; + } + + if (strstr(ver, "1.2.")) + return BFLSC_DRV2; + + tmp = str_text((char *)ver); + applog(LOG_INFO, "%s detect (%s) Warning unknown firmware '%s' using Ver2", + bflsc->drv->dname, bflsc->device_path, tmp); + free(tmp); + return BFLSC_DRV2; +} + +static void xlinkstr(char *xlink, size_t siz, int dev, struct bflsc_info *sc_info) +{ + if (dev > 0) + snprintf(xlink, siz, " x-%d", dev); + else { + if (sc_info->sc_count > 1) + strcpy(xlink, " master"); + else + *xlink = '\0'; + } +} + +static void bflsc_applog(struct cgpu_info *bflsc, int dev, enum usb_cmds cmd, int amount, int err) +{ + struct bflsc_info *sc_info = (struct bflsc_info *)(bflsc->device_data); + char xlink[17]; + + xlinkstr(xlink, sizeof(xlink), dev, sc_info); + + usb_applog(bflsc, cmd, xlink, amount, err); +} + +// Break an input up into lines with LFs removed +// false means an error, but if *lines > 0 then data was also found +// error would be no data or missing LF at the end +static bool tolines(struct cgpu_info *bflsc, int dev, char *buf, int *lines, char ***items, enum usb_cmds cmd) +{ + bool ok = false; + char *ptr; + +#define p_lines (*lines) +#define p_items (*items) + + p_lines = 0; + p_items = NULL; + + if (!buf || !(*buf)) { + applog(LOG_DEBUG, "USB: %s%i: (%d) empty %s", + bflsc->drv->name, bflsc->device_id, dev, usb_cmdname(cmd)); + return ok; + } + + ptr = strdup(buf); + while (ptr && *ptr) { + p_items = realloc(p_items, ++p_lines * sizeof(*p_items)); + if (unlikely(!p_items)) + quit(1, "Failed to realloc p_items in tolines"); + p_items[p_lines-1] = ptr; + ptr = strchr(ptr, '\n'); + if (ptr) + *(ptr++) = '\0'; + else { + applog(LOG_DEBUG, "USB: %s%i: (%d) missing lf(s) in %s", + bflsc->drv->name, bflsc->device_id, dev, usb_cmdname(cmd)); + return ok; + } + } + ok = true; + + return ok; +} + +static void freetolines(int *lines, char ***items) +{ + if (*lines > 0) { + free(**items); + free(*items); + } + *lines = 0; + *items = NULL; +} + +enum breakmode { + NOCOLON, + ONECOLON, + ALLCOLON // Temperature uses this +}; + +// Break down a single line into 'fields' +// 'lf' will be a pointer to the final LF if it is there (or NULL) +// firstname will be the allocated buf copy pointer which is also +// the string before ':' for ONECOLON and ALLCOLON +// If any string is missing the ':' when it was expected, false is returned +static bool breakdown(enum breakmode mode, char *buf, int *count, char **firstname, char ***fields, char **lf) +{ + char *ptr, *colon, *comma; + bool ok = false; + +#define p_count (*count) +#define p_firstname (*firstname) +#define p_fields (*fields) +#define p_lf (*lf) + + p_count = 0; + p_firstname = NULL; + p_fields = NULL; + p_lf = NULL; + + if (!buf || !(*buf)) + return ok; + + ptr = p_firstname = strdup(buf); + p_lf = strchr(p_firstname, '\n'); + if (mode == ONECOLON) { + colon = strchr(ptr, ':'); + if (colon) { + ptr = colon; + *(ptr++) = '\0'; + } else + return ok; + } + + while (ptr && *ptr) { + if (mode == ALLCOLON) { + colon = strchr(ptr, ':'); + if (colon) + ptr = colon + 1; + else + return ok; + } + comma = strchr(ptr, ','); + if (comma) + *(comma++) = '\0'; + p_fields = realloc(p_fields, ++p_count * sizeof(*p_fields)); + if (unlikely(!p_fields)) + quit(1, "Failed to realloc p_fields in breakdown"); + p_fields[p_count-1] = ptr; + ptr = comma; + } + + ok = true; + return ok; +} + +static void freebreakdown(int *count, char **firstname, char ***fields) +{ + if (*firstname) + free(*firstname); + if (*count > 0) + free(*fields); + *count = 0; + *firstname = NULL; + *fields = NULL; +} + +static bool isokerr(int err, char *buf, int amount) +{ + if (err < 0 || amount < (int)BFLSC_OK_LEN) + return false; + else { + if (strstr(buf, BFLSC_ANERR)) { + applog(LOG_INFO, "BFLSC not ok err: %s", buf); + return false; + } else + return true; + } +} + +// send+receive dual stage - always single line replies +static int send_recv_ds(struct cgpu_info *bflsc, int dev, int *stage, bool *sent, int *amount, char *send1, int send1_len, enum usb_cmds send1_cmd, enum usb_cmds recv1_cmd, char *send2, int send2_len, enum usb_cmds send2_cmd, enum usb_cmds recv2_cmd, char *recv, int recv_siz) +{ + struct DataForwardToChain data; + int len, err, tried; + + if (dev == 0) { + usb_buffer_clear(bflsc); + + *stage = 1; + *sent = false; + err = usb_write(bflsc, send1, send1_len, amount, send1_cmd); + if (err < 0 || *amount < send1_len) + return err; + + *sent = true; + err = usb_read_nl(bflsc, recv, recv_siz, amount, recv1_cmd); + if (!isokerr(err, recv, *amount)) + return err; + + usb_buffer_clear(bflsc); + + *stage = 2; + *sent = false; + err = usb_write(bflsc, send2, send2_len, amount, send2_cmd); + if (err < 0 || *amount < send2_len) + return err; + + *sent = true; + err = usb_read_nl(bflsc, recv, recv_siz, amount, recv2_cmd); + + return err; + } + + data.header = BFLSC_XLINKHDR; + data.deviceAddress = (uint8_t)dev; + tried = 0; + while (tried++ < 3) { + data.payloadSize = send1_len; + memcpy(data.payloadData, send1, send1_len); + len = DATAFORWARDSIZE(data); + + usb_buffer_clear(bflsc); + + *stage = 1; + *sent = false; + err = usb_write(bflsc, (char *)&data, len, amount, send1_cmd); + if (err < 0 || *amount < send1_len) + return err; + + *sent = true; + err = usb_read_nl(bflsc, recv, recv_siz, amount, recv1_cmd); + + if (err != LIBUSB_SUCCESS) + return err; + + // x-link timeout? - try again? + if (strstr(recv, BFLSC_XTIMEOUT)) + continue; + + if (!isokerr(err, recv, *amount)) + return err; + + data.payloadSize = send2_len; + memcpy(data.payloadData, send2, send2_len); + len = DATAFORWARDSIZE(data); + + usb_buffer_clear(bflsc); + + *stage = 2; + *sent = false; + err = usb_write(bflsc, (char *)&data, len, amount, send2_cmd); + if (err < 0 || *amount < send2_len) + return err; + + *sent = true; + err = usb_read_nl(bflsc, recv, recv_siz, amount, recv2_cmd); + + if (err != LIBUSB_SUCCESS) + return err; + + // x-link timeout? - try again? + if (strstr(recv, BFLSC_XTIMEOUT)) + continue; + + // SUCCESS - return it + break; + } + return err; +} + +#define READ_OK true +#define READ_NL false + +// send+receive single stage +static int send_recv_ss(struct cgpu_info *bflsc, int dev, bool *sent, int *amount, char *send, int send_len, enum usb_cmds send_cmd, char *recv, int recv_siz, enum usb_cmds recv_cmd, bool read_ok) +{ + struct DataForwardToChain data; + int len, err, tried; + + if (dev == 0) { + usb_buffer_clear(bflsc); + + *sent = false; + err = usb_write(bflsc, send, send_len, amount, send_cmd); + if (err < 0 || *amount < send_len) { + // N.B. thus !(*sent) directly implies err < 0 or *amount < send_len + return err; + } + + *sent = true; + if (read_ok == READ_OK) + err = usb_read_ok(bflsc, recv, recv_siz, amount, recv_cmd); + else + err = usb_read_nl(bflsc, recv, recv_siz, amount, recv_cmd); + + return err; + } + + data.header = BFLSC_XLINKHDR; + data.deviceAddress = (uint8_t)dev; + data.payloadSize = send_len; + memcpy(data.payloadData, send, send_len); + len = DATAFORWARDSIZE(data); + + tried = 0; + while (tried++ < 3) { + usb_buffer_clear(bflsc); + + *sent = false; + err = usb_write(bflsc, (char *)&data, len, amount, recv_cmd); + if (err < 0 || *amount < send_len) + return err; + + *sent = true; + if (read_ok == READ_OK) + err = usb_read_ok(bflsc, recv, recv_siz, amount, recv_cmd); + else + err = usb_read_nl(bflsc, recv, recv_siz, amount, recv_cmd); + + if (err != LIBUSB_SUCCESS && err != LIBUSB_ERROR_TIMEOUT) + return err; + + // read_ok can err timeout if it's looking for OK + // TODO: add a usb_read() option to spot the ERR: and convert end=OK to just + // x-link timeout? - try again? + if ((err == LIBUSB_SUCCESS || (read_ok == READ_OK && err == LIBUSB_ERROR_TIMEOUT)) && + strstr(recv, BFLSC_XTIMEOUT)) + continue; + + // SUCCESS or TIMEOUT - return it + break; + } + return err; +} + +static int write_to_dev(struct cgpu_info *bflsc, int dev, char *buf, int buflen, int *amount, enum usb_cmds cmd) +{ + struct DataForwardToChain data; + int len; + + /* + * The protocol is syncronous so any previous excess can be + * discarded and assumed corrupt data or failed USB transfers + */ + usb_buffer_clear(bflsc); + + if (dev == 0) + return usb_write(bflsc, buf, buflen, amount, cmd); + + data.header = BFLSC_XLINKHDR; + data.deviceAddress = (uint8_t)dev; + data.payloadSize = buflen; + memcpy(data.payloadData, buf, buflen); + len = DATAFORWARDSIZE(data); + + return usb_write(bflsc, (char *)&data, len, amount, cmd); +} + +static void bflsc_send_flush_work(struct cgpu_info *bflsc, int dev) +{ + char buf[BFLSC_BUFSIZ+1]; + int err, amount; + bool sent; + + // Device is gone + if (bflsc->usbinfo.nodev) + return; + + mutex_lock(&bflsc->device_mutex); + err = send_recv_ss(bflsc, dev, &sent, &amount, + BFLSC_QFLUSH, BFLSC_QFLUSH_LEN, C_QUEFLUSH, + buf, sizeof(buf)-1, C_QUEFLUSHREPLY, READ_NL); + mutex_unlock(&bflsc->device_mutex); + + if (!sent) + bflsc_applog(bflsc, dev, C_QUEFLUSH, amount, err); + else { + // TODO: do we care if we don't get 'OK'? (always will in normal processing) + } +} + +/* return True = attempted usb_read_ok() + * set ignore to true means no applog/ignore errors */ +static bool bflsc_qres(struct cgpu_info *bflsc, char *buf, size_t bufsiz, int dev, int *err, int *amount, bool ignore) +{ + bool readok = false; + + mutex_lock(&(bflsc->device_mutex)); + *err = send_recv_ss(bflsc, dev, &readok, amount, + BFLSC_QRES, BFLSC_QRES_LEN, C_REQUESTRESULTS, + buf, bufsiz-1, C_GETRESULTS, READ_OK); + mutex_unlock(&(bflsc->device_mutex)); + + if (!readok) { + if (!ignore) + bflsc_applog(bflsc, dev, C_REQUESTRESULTS, *amount, *err); + + // TODO: do what? flag as dead device? + // count how many times it has happened and reset/fail it + // or even make sure it is all x-link and that means device + // has failed after some limit of this? + // of course all other I/O must also be failing ... + } else { + if (*err < 0 || *amount < 1) { + if (!ignore) + bflsc_applog(bflsc, dev, C_GETRESULTS, *amount, *err); + + // TODO: do what? ... see above + } + } + + return readok; +} + +static void __bflsc_initialise(struct cgpu_info *bflsc) +{ + int err, interface; + +// TODO: does x-link bypass the other device FTDI? (I think it does) +// So no initialisation required except for the master device? + + if (bflsc->usbinfo.nodev) + return; + + interface = usb_interface(bflsc); + // Reset + err = usb_transfer(bflsc, FTDI_TYPE_OUT, FTDI_REQUEST_RESET, + FTDI_VALUE_RESET, interface, C_RESET); + + applog(LOG_DEBUG, "%s%i: reset got err %d", + bflsc->drv->name, bflsc->device_id, err); + + if (bflsc->usbinfo.nodev) + return; + + usb_ftdi_set_latency(bflsc); + + if (bflsc->usbinfo.nodev) + return; + + // Set data control + err = usb_transfer(bflsc, FTDI_TYPE_OUT, FTDI_REQUEST_DATA, + FTDI_VALUE_DATA_BAS, interface, C_SETDATA); + + applog(LOG_DEBUG, "%s%i: setdata got err %d", + bflsc->drv->name, bflsc->device_id, err); + + if (bflsc->usbinfo.nodev) + return; + + // Set the baud + err = usb_transfer(bflsc, FTDI_TYPE_OUT, FTDI_REQUEST_BAUD, FTDI_VALUE_BAUD_BAS, + (FTDI_INDEX_BAUD_BAS & 0xff00) | interface, + C_SETBAUD); + + applog(LOG_DEBUG, "%s%i: setbaud got err %d", + bflsc->drv->name, bflsc->device_id, err); + + if (bflsc->usbinfo.nodev) + return; + + // Set Flow Control + err = usb_transfer(bflsc, FTDI_TYPE_OUT, FTDI_REQUEST_FLOW, + FTDI_VALUE_FLOW, interface, C_SETFLOW); + + applog(LOG_DEBUG, "%s%i: setflowctrl got err %d", + bflsc->drv->name, bflsc->device_id, err); + + if (bflsc->usbinfo.nodev) + return; + + // Set Modem Control + err = usb_transfer(bflsc, FTDI_TYPE_OUT, FTDI_REQUEST_MODEM, + FTDI_VALUE_MODEM, interface, C_SETMODEM); + + applog(LOG_DEBUG, "%s%i: setmodemctrl got err %d", + bflsc->drv->name, bflsc->device_id, err); + + if (bflsc->usbinfo.nodev) + return; + + // Clear any sent data + err = usb_transfer(bflsc, FTDI_TYPE_OUT, FTDI_REQUEST_RESET, + FTDI_VALUE_PURGE_TX, interface, C_PURGETX); + + applog(LOG_DEBUG, "%s%i: purgetx got err %d", + bflsc->drv->name, bflsc->device_id, err); + + if (bflsc->usbinfo.nodev) + return; + + // Clear any received data + err = usb_transfer(bflsc, FTDI_TYPE_OUT, FTDI_REQUEST_RESET, + FTDI_VALUE_PURGE_RX, interface, C_PURGERX); + + applog(LOG_DEBUG, "%s%i: purgerx got err %d", + bflsc->drv->name, bflsc->device_id, err); + + if (!bflsc->cutofftemp) + bflsc->cutofftemp = opt_bflsc_overheat; +} + +static void bflsc_initialise(struct cgpu_info *bflsc) +{ + struct bflsc_info *sc_info = (struct bflsc_info *)(bflsc->device_data); + char buf[BFLSC_BUFSIZ+1]; + int err, amount; + int dev; + + mutex_lock(&(bflsc->device_mutex)); + __bflsc_initialise(bflsc); + mutex_unlock(&(bflsc->device_mutex)); + + for (dev = 0; dev < sc_info->sc_count; dev++) { + bflsc_send_flush_work(bflsc, dev); + bflsc_qres(bflsc, buf, sizeof(buf), dev, &err, &amount, true); + } +} + +static bool getinfo(struct cgpu_info *bflsc, int dev) +{ + struct bflsc_info *sc_info = (struct bflsc_info *)(bflsc->device_data); + struct bflsc_dev sc_dev; + char buf[BFLSC_BUFSIZ+1]; + int err, amount; + char **items, *firstname, **fields, *lf; + bool res, ok = false; + int i, lines, count; + char *tmp; + + /* + * Kano's first dev Jalapeno output: + * DEVICE: BitFORCE SC + * FIRMWARE: 1.0.0 + * ENGINES: 30 + * FREQUENCY: [UNKNOWN] + * XLINK MODE: MASTER + * XLINK PRESENT: YES + * --DEVICES IN CHAIN: 0 + * --CHAIN PRESENCE MASK: 00000000 + * OK + */ + + /* + * Don't use send_recv_ss() since we have a different receive timeout + * Also getinfo() is called multiple times if it fails anyway + */ + err = write_to_dev(bflsc, dev, BFLSC_DETAILS, BFLSC_DETAILS_LEN, &amount, C_REQUESTDETAILS); + if (err < 0 || amount != BFLSC_DETAILS_LEN) { + applog(LOG_ERR, "%s detect (%s) send details request failed (%d:%d)", + bflsc->drv->dname, bflsc->device_path, amount, err); + return ok; + } + + err = usb_read_ok_timeout(bflsc, buf, sizeof(buf)-1, &amount, + BFLSC_INFO_TIMEOUT, C_GETDETAILS); + if (err < 0 || amount < 1) { + if (err < 0) { + applog(LOG_ERR, "%s detect (%s) get details return invalid/timed out (%d:%d)", + bflsc->drv->dname, bflsc->device_path, amount, err); + } else { + applog(LOG_ERR, "%s detect (%s) get details returned nothing (%d:%d)", + bflsc->drv->dname, bflsc->device_path, amount, err); + } + return ok; + } + + memset(&sc_dev, 0, sizeof(struct bflsc_dev)); + sc_info->sc_count = 1; + res = tolines(bflsc, dev, &(buf[0]), &lines, &items, C_GETDETAILS); + if (!res) + return ok; + + tmp = str_text(buf); + strncpy(sc_dev.getinfo, tmp, sizeof(sc_dev.getinfo)); + sc_dev.getinfo[sizeof(sc_dev.getinfo)-1] = '\0'; + free(tmp); + + for (i = 0; i < lines-2; i++) { + res = breakdown(ONECOLON, items[i], &count, &firstname, &fields, &lf); + if (lf) + *lf = '\0'; + if (!res || count != 1) { + tmp = str_text(items[i]); + applogsiz(LOG_WARNING, BFLSC_APPLOGSIZ, + "%s detect (%s) invalid details line: '%s' %d", + bflsc->drv->dname, bflsc->device_path, tmp, count); + free(tmp); + dev_error(bflsc, REASON_DEV_COMMS_ERROR); + goto mata; + } + if (strstr(firstname, BFLSC_DI_FIRMWARE)) { + sc_dev.firmware = strdup(fields[0]); + sc_info->driver_version = drv_ver(bflsc, sc_dev.firmware); + } + else if (Strcasestr(firstname, BFLSC_DI_ENGINES)) { + sc_dev.engines = atoi(fields[0]); + if (sc_dev.engines < 1) { + tmp = str_text(items[i]); + applogsiz(LOG_WARNING, BFLSC_APPLOGSIZ, + "%s detect (%s) invalid engine count: '%s'", + bflsc->drv->dname, bflsc->device_path, tmp); + free(tmp); + goto mata; + } + } + else if (strstr(firstname, BFLSC_DI_XLINKMODE)) + sc_dev.xlink_mode = strdup(fields[0]); + else if (strstr(firstname, BFLSC_DI_XLINKPRESENT)) + sc_dev.xlink_present = strdup(fields[0]); + else if (strstr(firstname, BFLSC_DI_DEVICESINCHAIN)) { + if (fields[0][0] == '0' || + (fields[0][0] == ' ' && fields[0][1] == '0')) + sc_info->sc_count = 1; + else + sc_info->sc_count = atoi(fields[0]); + if (sc_info->sc_count < 1 || sc_info->sc_count > 30) { + tmp = str_text(items[i]); + applogsiz(LOG_WARNING, BFLSC_APPLOGSIZ, + "%s detect (%s) invalid x-link count: '%s'", + bflsc->drv->dname, bflsc->device_path, tmp); + free(tmp); + goto mata; + } + } + else if (strstr(firstname, BFLSC_DI_CHIPS)) + sc_dev.chips = strdup(fields[0]); + else if (strstr(firstname, BFLSC28_DI_ASICS)) + sc_dev.chips = strdup(fields[0]); + + freebreakdown(&count, &firstname, &fields); + } + + if (sc_info->driver_version == BFLSC_DRVUNDEF) { + applog(LOG_WARNING, "%s detect (%s) missing %s", + bflsc->drv->dname, bflsc->device_path, BFLSC_DI_FIRMWARE); + goto ne; + } + + sc_info->sc_devs = calloc(sc_info->sc_count, sizeof(struct bflsc_dev)); + if (unlikely(!sc_info->sc_devs)) + quit(1, "Failed to calloc in getinfo"); + memcpy(&(sc_info->sc_devs[0]), &sc_dev, sizeof(sc_dev)); + // TODO: do we care about getting this info for the rest if > 0 x-link + + ok = true; + goto ne; + +mata: + freebreakdown(&count, &firstname, &fields); + ok = false; +ne: + freetolines(&lines, &items); + return ok; +} + +static bool bflsc28_queue_full(struct cgpu_info *bflsc); + +static struct cgpu_info *bflsc_detect_one(struct libusb_device *dev, struct usb_find_devices *found) +{ + struct bflsc_info *sc_info = NULL; + char buf[BFLSC_BUFSIZ+1]; + int i, err, amount; + struct timeval init_start, init_now; + int init_sleep, init_count; + bool ident_first, sent; + char *newname; + uint16_t latency; + + struct cgpu_info *bflsc = usb_alloc_cgpu(&bflsc_drv, 1); + + sc_info = calloc(1, sizeof(*sc_info)); + if (unlikely(!sc_info)) + quit(1, "Failed to calloc sc_info in bflsc_detect_one"); + // TODO: fix ... everywhere ... + bflsc->device_data = (FILE *)sc_info; + + if (!usb_init(bflsc, dev, found)) + goto shin; + + // Allow 2 complete attempts if the 1st time returns an unrecognised reply + ident_first = true; +retry: + init_count = 0; + init_sleep = REINIT_TIME_FIRST_MS; + cgtime(&init_start); +reinit: + __bflsc_initialise(bflsc); + + err = send_recv_ss(bflsc, 0, &sent, &amount, + BFLSC_IDENTIFY, BFLSC_IDENTIFY_LEN, C_REQUESTIDENTIFY, + buf, sizeof(buf)-1, C_GETIDENTIFY, READ_NL); + + if (!sent) { + applog(LOG_ERR, "%s detect (%s) send identify request failed (%d:%d)", + bflsc->drv->dname, bflsc->device_path, amount, err); + goto unshin; + } + + if (err < 0 || amount < 1) { + init_count++; + cgtime(&init_now); + if (us_tdiff(&init_now, &init_start) <= REINIT_TIME_MAX) { + if (init_count == 2) { + applog(LOG_WARNING, "%s detect (%s) 2nd init failed (%d:%d) - retrying", + bflsc->drv->dname, bflsc->device_path, amount, err); + } + cgsleep_ms(init_sleep); + if ((init_sleep * 2) <= REINIT_TIME_MAX_MS) + init_sleep *= 2; + goto reinit; + } + + if (init_count > 0) + applog(LOG_WARNING, "%s detect (%s) init failed %d times %.2fs", + bflsc->drv->dname, bflsc->device_path, init_count, tdiff(&init_now, &init_start)); + + if (err < 0) { + applog(LOG_ERR, "%s detect (%s) error identify reply (%d:%d)", + bflsc->drv->dname, bflsc->device_path, amount, err); + } else { + applog(LOG_ERR, "%s detect (%s) empty identify reply (%d)", + bflsc->drv->dname, bflsc->device_path, amount); + } + + goto unshin; + } + buf[amount] = '\0'; + + if (unlikely(!strstr(buf, BFLSC_BFLSC) && !strstr(buf, BFLSC_BFLSC28))) { + applog(LOG_DEBUG, "%s detect (%s) found an FPGA '%s' ignoring", + bflsc->drv->dname, bflsc->device_path, buf); + goto unshin; + } + + if (unlikely(strstr(buf, BFLSC_IDENTITY))) { + if (ident_first) { + applog(LOG_DEBUG, "%s detect (%s) didn't recognise '%s' trying again ...", + bflsc->drv->dname, bflsc->device_path, buf); + ident_first = false; + goto retry; + } + applog(LOG_DEBUG, "%s detect (%s) didn't recognise '%s' on 2nd attempt", + bflsc->drv->dname, bflsc->device_path, buf); + goto unshin; + } + + int tries = 0; + while (7734) { + if (getinfo(bflsc, 0)) + break; + + // N.B. we will get displayed errors each time it fails + if (++tries > 2) + goto unshin; + + cgsleep_ms(40); + } + + switch (sc_info->driver_version) { + case BFLSC_DRV1: + sc_info->que_size = BFLSC_QUE_SIZE_V1; + sc_info->que_full_enough = BFLSC_QUE_FULL_ENOUGH_V1; + sc_info->que_watermark = BFLSC_QUE_WATERMARK_V1; + sc_info->que_low = BFLSC_QUE_LOW_V1; + sc_info->que_noncecount = QUE_NONCECOUNT_V1; + sc_info->que_fld_min = QUE_FLD_MIN_V1; + sc_info->que_fld_max = QUE_FLD_MAX_V1; + // Only Jalapeno uses 1.0.0 + sc_info->flush_size = 1; + break; + case BFLSC_DRV2: + case BFLSC_DRVUNDEF: + default: + sc_info->driver_version = BFLSC_DRV2; + + sc_info->que_size = BFLSC_QUE_SIZE_V2; + sc_info->que_full_enough = BFLSC_QUE_FULL_ENOUGH_V2; + sc_info->que_watermark = BFLSC_QUE_WATERMARK_V2; + sc_info->que_low = BFLSC_QUE_LOW_V2; + sc_info->que_noncecount = QUE_NONCECOUNT_V2; + sc_info->que_fld_min = QUE_FLD_MIN_V2; + sc_info->que_fld_max = QUE_FLD_MAX_V2; + // TODO: this can be reduced to total chip count + sc_info->flush_size = 16 * sc_info->sc_count; + break; + } + + // Set parallelization based on the getinfo() response if it is present + if (sc_info->sc_devs[0].chips && strlen(sc_info->sc_devs[0].chips)) { + if (strstr(sc_info->sc_devs[0].chips, BFLSC_DI_CHIPS_PARALLEL)) { + sc_info->que_noncecount = QUE_NONCECOUNT_V2; + sc_info->que_fld_min = QUE_FLD_MIN_V2; + sc_info->que_fld_max = QUE_FLD_MAX_V2; + } else { + sc_info->que_noncecount = QUE_NONCECOUNT_V1; + sc_info->que_fld_min = QUE_FLD_MIN_V1; + sc_info->que_fld_max = QUE_FLD_MAX_V1; + } + } + + sc_info->scan_sleep_time = BAS_SCAN_TIME; + sc_info->results_sleep_time = BFLSC_RES_TIME; + sc_info->default_ms_work = BAS_WORK_TIME; + latency = BAS_LATENCY; + + /* When getinfo() "FREQUENCY: [UNKNOWN]" is fixed - + * use 'freq * engines' to estimate. + * Otherwise for now: */ + newname = NULL; + if (sc_info->sc_count > 1) { + newname = BFLSC_MINIRIG; + sc_info->scan_sleep_time = BAM_SCAN_TIME; + sc_info->default_ms_work = BAM_WORK_TIME; + bflsc->usbdev->ident = IDENT_BAM; + latency = BAM_LATENCY; + } else { + if (sc_info->sc_devs[0].engines < 34) { // 16 * 2 + 2 + newname = BFLSC_JALAPENO; + sc_info->scan_sleep_time = BAJ_SCAN_TIME; + sc_info->default_ms_work = BAJ_WORK_TIME; + bflsc->usbdev->ident = IDENT_BAJ; + latency = BAJ_LATENCY; + } else if (sc_info->sc_devs[0].engines < 130) { // 16 * 8 + 2 + newname = BFLSC_LITTLESINGLE; + sc_info->scan_sleep_time = BAL_SCAN_TIME; + sc_info->default_ms_work = BAL_WORK_TIME; + bflsc->usbdev->ident = IDENT_BAL; + latency = BAL_LATENCY; + } + } + + sc_info->ident = usb_ident(bflsc); + if (sc_info->ident == IDENT_BMA) { + bflsc->drv->queue_full = &bflsc28_queue_full; + sc_info->scan_sleep_time = BMA_SCAN_TIME; + sc_info->default_ms_work = BMA_WORK_TIME; + sc_info->results_sleep_time = BMA_RES_TIME; + } + + if (latency != bflsc->usbdev->found->latency) { + bflsc->usbdev->found->latency = latency; + usb_ftdi_set_latency(bflsc); + } + + for (i = 0; i < sc_info->sc_count; i++) + sc_info->sc_devs[i].ms_work = sc_info->default_ms_work; + + if (newname) { + if (!bflsc->drv->copy) + bflsc->drv = copy_drv(bflsc->drv); + bflsc->drv->name = newname; + } + + // We have a real BFLSC! + applog(LOG_DEBUG, "%s (%s) identified as: '%s'", + bflsc->drv->dname, bflsc->device_path, bflsc->drv->name); + + if (!add_cgpu(bflsc)) + goto unshin; + + update_usb_stats(bflsc); + + mutex_init(&bflsc->device_mutex); + rwlock_init(&sc_info->stat_lock); + + return bflsc; + +unshin: + + usb_uninit(bflsc); + +shin: + + free(bflsc->device_data); + bflsc->device_data = NULL; + + if (bflsc->name != blank) { + free(bflsc->name); + bflsc->name = NULL; + } + + bflsc = usb_free_cgpu(bflsc); + + return NULL; +} + +static void bflsc_detect(bool __maybe_unused hotplug) +{ + usb_detect(&bflsc_drv, bflsc_detect_one); +} + +static void get_bflsc_statline_before(char *buf, size_t bufsiz, struct cgpu_info *bflsc) +{ + struct bflsc_info *sc_info = (struct bflsc_info *)(bflsc->device_data); + float temp = 0; + float vcc2 = 0; + int i; + + rd_lock(&(sc_info->stat_lock)); + for (i = 0; i < sc_info->sc_count; i++) { + if (sc_info->sc_devs[i].temp1 > temp) + temp = sc_info->sc_devs[i].temp1; + if (sc_info->sc_devs[i].temp2 > temp) + temp = sc_info->sc_devs[i].temp2; + if (sc_info->sc_devs[i].vcc2 > vcc2) + vcc2 = sc_info->sc_devs[i].vcc2; + } + rd_unlock(&(sc_info->stat_lock)); + + tailsprintf(buf, bufsiz, "max%3.0fC %4.2fV", temp, vcc2); +} + +static void flush_one_dev(struct cgpu_info *bflsc, int dev) +{ + struct bflsc_info *sc_info = (struct bflsc_info *)(bflsc->device_data); + struct work *work, *tmp; + bool did = false; + + bflsc_send_flush_work(bflsc, dev); + + rd_lock(&bflsc->qlock); + + HASH_ITER(hh, bflsc->queued_work, work, tmp) { + if (work->subid == dev) { + // devflag is used to flag stale work + work->devflag = true; + did = true; + } + } + + rd_unlock(&bflsc->qlock); + + if (did) { + wr_lock(&(sc_info->stat_lock)); + sc_info->sc_devs[dev].flushed = true; + sc_info->sc_devs[dev].flush_id = sc_info->sc_devs[dev].result_id; + sc_info->sc_devs[dev].work_queued = 0; + wr_unlock(&(sc_info->stat_lock)); + } +} + +static void bflsc_flush_work(struct cgpu_info *bflsc) +{ + struct bflsc_info *sc_info = (struct bflsc_info *)(bflsc->device_data); + int dev; + + for (dev = 0; dev < sc_info->sc_count; dev++) + flush_one_dev(bflsc, dev); +} + +static void bflsc_set_volt(struct cgpu_info *bflsc, int dev) +{ + struct bflsc_info *sc_info = (struct bflsc_info *)(bflsc->device_data); + char buf[BFLSC_BUFSIZ+1]; + char msg[16]; + int err, amount; + bool sent; + + // Device is gone + if (bflsc->usbinfo.nodev) + return; + + snprintf(msg, sizeof(msg), "V%dX", sc_info->volt_next); + + mutex_lock(&bflsc->device_mutex); + + err = send_recv_ss(bflsc, dev, &sent, &amount, + msg, strlen(msg), C_SETVOLT, + buf, sizeof(buf)-1, C_REPLYSETVOLT, READ_NL); + mutex_unlock(&(bflsc->device_mutex)); + + if (!sent) + bflsc_applog(bflsc, dev, C_SETVOLT, amount, err); + else { + // Don't care + } + + sc_info->volt_next_stat = false; + + return; +} + +static void bflsc_set_clock(struct cgpu_info *bflsc, int dev) +{ + struct bflsc_info *sc_info = (struct bflsc_info *)(bflsc->device_data); + char buf[BFLSC_BUFSIZ+1]; + char msg[16]; + int err, amount; + bool sent; + + // Device is gone + if (bflsc->usbinfo.nodev) + return; + + snprintf(msg, sizeof(msg), "F%XX", sc_info->clock_next); + + mutex_lock(&bflsc->device_mutex); + + err = send_recv_ss(bflsc, dev, &sent, &amount, + msg, strlen(msg), C_SETCLOCK, + buf, sizeof(buf)-1, C_REPLYSETCLOCK, READ_NL); + mutex_unlock(&(bflsc->device_mutex)); + + if (!sent) + bflsc_applog(bflsc, dev, C_SETCLOCK, amount, err); + else { + // Don't care + } + + sc_info->clock_next_stat = false; + + return; +} + +static void bflsc_flash_led(struct cgpu_info *bflsc, int dev) +{ + struct bflsc_info *sc_info = (struct bflsc_info *)(bflsc->device_data); + char buf[BFLSC_BUFSIZ+1]; + int err, amount; + bool sent; + + // Device is gone + if (bflsc->usbinfo.nodev) + return; + + // It is not critical flashing the led so don't get stuck if we + // can't grab the mutex now + if (mutex_trylock(&bflsc->device_mutex)) + return; + + err = send_recv_ss(bflsc, dev, &sent, &amount, + BFLSC_FLASH, BFLSC_FLASH_LEN, C_REQUESTFLASH, + buf, sizeof(buf)-1, C_FLASHREPLY, READ_NL); + mutex_unlock(&(bflsc->device_mutex)); + + if (!sent) + bflsc_applog(bflsc, dev, C_REQUESTFLASH, amount, err); + else { + // Don't care + } + + // Once we've tried - don't do it until told to again + // - even if it failed + sc_info->flash_led = false; + + return; +} + +/* Flush and stop all work if the device reaches the thermal cutoff temp, or + * temporarily stop queueing work if it's in the throttling range. */ +static void bflsc_manage_temp(struct cgpu_info *bflsc, struct bflsc_dev *sc_dev, + int dev, float temp) +{ + bflsc->temp = temp; + if (bflsc->cutofftemp > 0) { + int cutoff = bflsc->cutofftemp; + int throttle = cutoff - BFLSC_TEMP_THROTTLE; + int recover = cutoff - BFLSC_TEMP_RECOVER; + + if (sc_dev->overheat) { + if (temp < recover) + sc_dev->overheat = false; + } else if (temp > throttle) { + sc_dev->overheat = true; + if (temp > cutoff) { + applog(LOG_WARNING, "%s%i: temp (%.1f) hit thermal cutoff limit %d, stopping work!", + bflsc->drv->name, bflsc->device_id, temp, cutoff); + dev_error(bflsc, REASON_DEV_THERMAL_CUTOFF); + flush_one_dev(bflsc, dev); + + } else { + applog(LOG_NOTICE, "%s%i: temp (%.1f) hit thermal throttle limit %d, throttling", + bflsc->drv->name, bflsc->device_id, temp, throttle); + } + } + } +} + +static bool bflsc_get_temp(struct cgpu_info *bflsc, int dev) +{ + struct bflsc_info *sc_info = (struct bflsc_info *)(bflsc->device_data); + struct bflsc_dev *sc_dev; + char temp_buf[BFLSC_BUFSIZ+1]; + char volt_buf[BFLSC_BUFSIZ+1]; + char *tmp; + int err, amount; + char *firstname, **fields, *lf; + char xlink[17]; + int count; + bool res, sent; + float temp, temp1, temp2; + float vcc1, vcc2, vmain; + + // Device is gone + if (bflsc->usbinfo.nodev) + return false; + + if (dev >= sc_info->sc_count) { + applog(LOG_ERR, "%s%i: temp invalid xlink device %d - limit %d", + bflsc->drv->name, bflsc->device_id, dev, sc_info->sc_count - 1); + return false; + } + + if (sc_info->volt_next_stat || sc_info->clock_next_stat) { + if (sc_info->volt_next_stat) + bflsc_set_volt(bflsc, dev); + if (sc_info->clock_next_stat) + bflsc_set_clock(bflsc, dev); + return true; + } + + // Flash instead of Temp + if (sc_info->flash_led) { + bflsc_flash_led(bflsc, dev); + return true; + } + + xlinkstr(xlink, sizeof(xlink), dev, sc_info); + + /* It is not very critical getting temp so don't get stuck if we + * can't grab the mutex here */ + if (mutex_trylock(&bflsc->device_mutex)) + return false; + + err = send_recv_ss(bflsc, dev, &sent, &amount, + BFLSC_TEMPERATURE, BFLSC_TEMPERATURE_LEN, C_REQUESTTEMPERATURE, + temp_buf, sizeof(temp_buf)-1, C_GETTEMPERATURE, READ_NL); + mutex_unlock(&(bflsc->device_mutex)); + + if (!sent) { + applog(LOG_ERR, "%s%i: Error: Request%s temp invalid/timed out (%d:%d)", + bflsc->drv->name, bflsc->device_id, xlink, amount, err); + return false; + } else { + if (err < 0 || amount < 1) { + if (err < 0) { + applog(LOG_ERR, "%s%i: Error: Get%s temp return invalid/timed out (%d:%d)", + bflsc->drv->name, bflsc->device_id, xlink, amount, err); + } else { + applog(LOG_ERR, "%s%i: Error: Get%s temp returned nothing (%d:%d)", + bflsc->drv->name, bflsc->device_id, xlink, amount, err); + } + return false; + } + } + + // Ignore it if we can't get the V + if (mutex_trylock(&bflsc->device_mutex)) + return false; + + err = send_recv_ss(bflsc, dev, &sent, &amount, + BFLSC_VOLTAGE, BFLSC_VOLTAGE_LEN, C_REQUESTVOLTS, + volt_buf, sizeof(volt_buf)-1, C_GETVOLTS, READ_NL); + mutex_unlock(&(bflsc->device_mutex)); + + if (!sent) { + applog(LOG_ERR, "%s%i: Error: Request%s volts invalid/timed out (%d:%d)", + bflsc->drv->name, bflsc->device_id, xlink, amount, err); + return false; + } else { + if (err < 0 || amount < 1) { + if (err < 0) { + applog(LOG_ERR, "%s%i: Error: Get%s volt return invalid/timed out (%d:%d)", + bflsc->drv->name, bflsc->device_id, xlink, amount, err); + } else { + applog(LOG_ERR, "%s%i: Error: Get%s volt returned nothing (%d:%d)", + bflsc->drv->name, bflsc->device_id, xlink, amount, err); + } + return false; + } + } + + res = breakdown(ALLCOLON, temp_buf, &count, &firstname, &fields, &lf); + if (lf) + *lf = '\0'; + if (!res || count < 2 || !lf) { + tmp = str_text(temp_buf); + applog(LOG_WARNING, "%s%i: Invalid%s temp reply: '%s'", + bflsc->drv->name, bflsc->device_id, xlink, tmp); + free(tmp); + freebreakdown(&count, &firstname, &fields); + dev_error(bflsc, REASON_DEV_COMMS_ERROR); + return false; + } + + temp = temp1 = (float)atoi(fields[0]); + temp2 = (float)atoi(fields[1]); + + freebreakdown(&count, &firstname, &fields); + + res = breakdown(NOCOLON, volt_buf, &count, &firstname, &fields, &lf); + if (lf) + *lf = '\0'; + if (!res || count != 3 || !lf) { + tmp = str_text(volt_buf); + applog(LOG_WARNING, "%s%i: Invalid%s volt reply: '%s'", + bflsc->drv->name, bflsc->device_id, xlink, tmp); + free(tmp); + freebreakdown(&count, &firstname, &fields); + dev_error(bflsc, REASON_DEV_COMMS_ERROR); + return false; + } + + sc_dev = &sc_info->sc_devs[dev]; + vcc1 = (float)atoi(fields[0]) / 1000.0; + vcc2 = (float)atoi(fields[1]) / 1000.0; + vmain = (float)atoi(fields[2]) / 1000.0; + + freebreakdown(&count, &firstname, &fields); + + if (vcc1 > 0 || vcc2 > 0 || vmain > 0) { + wr_lock(&(sc_info->stat_lock)); + if (vcc1 > 0) { + if (unlikely(sc_dev->vcc1 == 0)) + sc_dev->vcc1 = vcc1; + else { + sc_dev->vcc1 += vcc1 * 0.63; + sc_dev->vcc1 /= 1.63; + } + } + if (vcc2 > 0) { + if (unlikely(sc_dev->vcc2 == 0)) + sc_dev->vcc2 = vcc2; + else { + sc_dev->vcc2 += vcc2 * 0.63; + sc_dev->vcc2 /= 1.63; + } + } + if (vmain > 0) { + if (unlikely(sc_dev->vmain == 0)) + sc_dev->vmain = vmain; + else { + sc_dev->vmain += vmain * 0.63; + sc_dev->vmain /= 1.63; + } + } + wr_unlock(&(sc_info->stat_lock)); + } + + if (temp1 > 0 || temp2 > 0) { + wr_lock(&(sc_info->stat_lock)); + if (unlikely(!sc_dev->temp1)) + sc_dev->temp1 = temp1; + else { + sc_dev->temp1 += temp1 * 0.63; + sc_dev->temp1 /= 1.63; + } + if (unlikely(!sc_dev->temp2)) + sc_dev->temp2 = temp2; + else { + sc_dev->temp2 += temp2 * 0.63; + sc_dev->temp2 /= 1.63; + } + if (temp1 > sc_dev->temp1_max) { + sc_dev->temp1_max = temp1; + sc_dev->temp1_max_time = time(NULL); + } + if (temp2 > sc_dev->temp2_max) { + sc_dev->temp2_max = temp2; + sc_dev->temp2_max_time = time(NULL); + } + + if (unlikely(sc_dev->temp1_5min_av == 0)) + sc_dev->temp1_5min_av = temp1; + else { + sc_dev->temp1_5min_av += temp1 * .0042; + sc_dev->temp1_5min_av /= 1.0042; + } + if (unlikely(sc_dev->temp2_5min_av == 0)) + sc_dev->temp2_5min_av = temp2; + else { + sc_dev->temp2_5min_av += temp2 * .0042; + sc_dev->temp2_5min_av /= 1.0042; + } + wr_unlock(&(sc_info->stat_lock)); + + if (temp < temp2) + temp = temp2; + + bflsc_manage_temp(bflsc, sc_dev, dev, temp); + } + + return true; +} + +static void inc_core_errors(struct bflsc_info *info, int8_t core) +{ + if (info->ident == IDENT_BMA) { + if (core >= 0) + info->cortex_hw[core]++; + } else { + if (core >= 0 && core < 16) + info->core_hw[core]++; + } +} + +static void inc_bflsc_errors(struct thr_info *thr, struct bflsc_info *info, int8_t core) +{ + inc_hw_errors(thr); + inc_core_errors(info, core); +} + +static void inc_bflsc_nonces(struct bflsc_info *info, int8_t core) +{ + if (info->ident == IDENT_BMA) { + if (core >= 0) + info->cortex_nonces[core]++; + } else { + if (core >= 0 && core < 16) + info->core_nonces[core]++; + } +} + +struct work *bflsc_work_by_uid(struct cgpu_info *bflsc, struct bflsc_info *sc_info, int id) +{ + struct bflsc_work *bwork; + struct work *work = NULL; + + wr_lock(&bflsc->qlock); + HASH_FIND_INT(sc_info->bworks, &id, bwork); + if (likely(bwork)) { + HASH_DEL(sc_info->bworks, bwork); + work = bwork->work; + free(bwork); + } + wr_unlock(&bflsc->qlock); + + return work; +} + +static void process_nonces(struct cgpu_info *bflsc, int dev, char *xlink, char *data, int count, char **fields, int *nonces) +{ + struct bflsc_info *sc_info = (struct bflsc_info *)(bflsc->device_data); + struct thr_info *thr = bflsc->thr[0]; + struct work *work = NULL; + int8_t core = -1; + uint32_t nonce; + int i, num, x; + char *tmp; + bool res; + + if (count < sc_info->que_fld_min) { + tmp = str_text(data); + applogsiz(LOG_INFO, BFLSC_APPLOGSIZ, + "%s%i:%s work returned too small (%d,%s)", + bflsc->drv->name, bflsc->device_id, xlink, count, tmp); + free(tmp); + inc_bflsc_errors(thr, sc_info, core); + return; + } + + if (sc_info->ident == IDENT_BMA) { + unsigned int ucore; + + if (sscanf(fields[QUE_CC], "%x", &ucore) == 1) + core = ucore; + } else if (sc_info->que_noncecount != QUE_NONCECOUNT_V1) { + unsigned int ucore; + + if (sscanf(fields[QUE_CHIP_V2], "%x", &ucore) == 1) + core = ucore; + } + + if (count > sc_info->que_fld_max) { + applog(LOG_INFO, "%s%i:%s work returned too large (%d) processing %d anyway", + bflsc->drv->name, bflsc->device_id, xlink, count, sc_info->que_fld_max); + count = sc_info->que_fld_max; + inc_bflsc_errors(thr, sc_info, core); + } + + num = atoi(fields[sc_info->que_noncecount]); + if (num != count - sc_info->que_fld_min) { + tmp = str_text(data); + applogsiz(LOG_INFO, BFLSC_APPLOGSIZ, + "%s%i:%s incorrect data count (%d) will use %d instead from (%s)", + bflsc->drv->name, bflsc->device_id, xlink, num, + count - sc_info->que_fld_max, tmp); + free(tmp); + inc_bflsc_errors(thr, sc_info, core); + } + + if (sc_info->ident == IDENT_BMA) { + int uid; + + if (sscanf(fields[QUE_UID], "%04x", &uid) == 1) + work = bflsc_work_by_uid(bflsc, sc_info, uid); + } else { + char midstate[MIDSTATE_BYTES] = {}, blockdata[MERKLE_BYTES] = {}; + + if (!hex2bin((unsigned char *)midstate, fields[QUE_MIDSTATE], MIDSTATE_BYTES) || + !hex2bin((unsigned char *)blockdata, fields[QUE_BLOCKDATA], MERKLE_BYTES)) { + applog(LOG_INFO, "%s%i:%s Failed to convert binary data to hex result - ignored", + bflsc->drv->name, bflsc->device_id, xlink); + inc_bflsc_errors(thr, sc_info, core); + return; + } + + work = take_queued_work_bymidstate(bflsc, midstate, MIDSTATE_BYTES, + blockdata, MERKLE_OFFSET, MERKLE_BYTES); + } + if (!work) { + if (sc_info->not_first_work) { + applog(LOG_INFO, "%s%i:%s failed to find nonce work - can't be processed - ignored", + bflsc->drv->name, bflsc->device_id, xlink); + inc_bflsc_errors(thr, sc_info, core); + } + return; + } + + res = false; + x = 0; + for (i = sc_info->que_fld_min; i < count; i++) { + if (strlen(fields[i]) != 8) { + tmp = str_text(data); + applogsiz(LOG_INFO, BFLSC_APPLOGSIZ, + "%s%i:%s invalid nonce (%s) will try to process anyway", + bflsc->drv->name, bflsc->device_id, xlink, tmp); + free(tmp); + } + + hex2bin((void*)&nonce, fields[i], 4); + nonce = htobe32(nonce); + res = submit_nonce(thr, work, nonce); + if (res) { + wr_lock(&(sc_info->stat_lock)); + sc_info->sc_devs[dev].nonces_found++; + wr_unlock(&(sc_info->stat_lock)); + + (*nonces)++; + x++; + inc_bflsc_nonces(sc_info, core); + } else + inc_core_errors(sc_info, core); + } + + wr_lock(&(sc_info->stat_lock)); + if (res) + sc_info->sc_devs[dev].result_id++; + if (x > QUE_MAX_RESULTS) + x = QUE_MAX_RESULTS + 1; + (sc_info->result_size[x])++; + sc_info->sc_devs[dev].work_complete++; + sc_info->sc_devs[dev].hashes_unsent += FULLNONCE; + // If not flushed (stale) + if (!(work->devflag)) + sc_info->sc_devs[dev].work_queued -= 1; + wr_unlock(&(sc_info->stat_lock)); + + free_work(work); +} + +static int process_results(struct cgpu_info *bflsc, int dev, char *pbuf, int *nonces, int *in_process) +{ + struct bflsc_info *sc_info = (struct bflsc_info *)(bflsc->device_data); + char **items, *firstname, **fields, *lf; + int que = 0, i, lines, count; + char *tmp, *tmp2, *buf; + char xlink[17]; + bool res; + + *nonces = 0; + *in_process = 0; + + xlinkstr(xlink, sizeof(xlink), dev, sc_info); + + buf = strdup(pbuf); + if (!strncmp(buf, "INPROCESS", 9)) + sscanf(buf, "INPROCESS:%d\n%s", in_process, pbuf); + res = tolines(bflsc, dev, buf, &lines, &items, C_GETRESULTS); + if (!res || lines < 1) { + tmp = str_text(pbuf); + applogsiz(LOG_ERR, BFLSC_APPLOGSIZ, + "%s%i:%s empty result (%s) ignored", + bflsc->drv->name, bflsc->device_id, xlink, tmp); + free(tmp); + goto arigatou; + } + + if (lines < QUE_RES_LINES_MIN) { + tmp = str_text(pbuf); + applogsiz(LOG_ERR, BFLSC_APPLOGSIZ, + "%s%i:%s result of %d too small (%s) ignored", + bflsc->drv->name, bflsc->device_id, xlink, lines, tmp); + free(tmp); + goto arigatou; + } + + breakdown(ONECOLON, items[1], &count, &firstname, &fields, &lf); + if (count < 1) { + tmp = str_text(pbuf); + tmp2 = str_text(items[1]); + applogsiz(LOG_ERR, BFLSC_APPLOGSIZ, + "%s%i:%s empty result count (%s) in (%s) ignoring", + bflsc->drv->name, bflsc->device_id, xlink, tmp2, tmp); + free(tmp2); + free(tmp); + goto arigatou; + } else if (count != 1) { + tmp = str_text(pbuf); + tmp2 = str_text(items[1]); + applogsiz(LOG_ERR, BFLSC_APPLOGSIZ, + "%s%i:%s incorrect result count %d (%s) in (%s) will try anyway", + bflsc->drv->name, bflsc->device_id, xlink, count, tmp2, tmp); + free(tmp2); + free(tmp); + } + + que = atoi(fields[0]); + if (que != (lines - QUE_RES_LINES_MIN)) { + i = que; + // 1+ In case the last line isn't 'OK' - try to process it + que = 1 + lines - QUE_RES_LINES_MIN; + + tmp = str_text(pbuf); + tmp2 = str_text(items[0]); + applogsiz(LOG_ERR, BFLSC_APPLOGSIZ, + "%s%i:%s incorrect result count %d (%s) will try %d (%s)", + bflsc->drv->name, bflsc->device_id, xlink, i, tmp2, que, tmp); + free(tmp2); + free(tmp); + + } + + freebreakdown(&count, &firstname, &fields); + + for (i = 0; i < que; i++) { + res = breakdown(NOCOLON, items[i + QUE_RES_LINES_MIN - 1], &count, &firstname, &fields, &lf); + if (likely(res)) + process_nonces(bflsc, dev, &(xlink[0]), items[i], count, fields, nonces); + else + applogsiz(LOG_ERR, BFLSC_APPLOGSIZ, + "%s%i:%s failed to process nonce %s", + bflsc->drv->name, bflsc->device_id, xlink, items[i]); + freebreakdown(&count, &firstname, &fields); + sc_info->not_first_work = true; + } + +arigatou: + freetolines(&lines, &items); + free(buf); + + return que; +} + +#define TVF(tv) ((float)((tv)->tv_sec) + ((float)((tv)->tv_usec) / 1000000.0)) +#define TVFMS(tv) (TVF(tv) * 1000.0) + +// Thread to simply keep looking for results +static void *bflsc_get_results(void *userdata) +{ + struct cgpu_info *bflsc = (struct cgpu_info *)userdata; + struct bflsc_info *sc_info = (struct bflsc_info *)(bflsc->device_data); + struct timeval elapsed, now; + float oldest, f; + char buf[BFLSC_BUFSIZ+1]; + int err, amount; + int i, que, dev, nonces; + bool readok; + + cgtime(&now); + for (i = 0; i < sc_info->sc_count; i++) { + copy_time(&(sc_info->sc_devs[i].last_check_result), &now); + copy_time(&(sc_info->sc_devs[i].last_dev_result), &now); + copy_time(&(sc_info->sc_devs[i].last_nonce_result), &now); + } + + while (sc_info->shutdown == false) { + cgtimer_t ts_start; + int in_process; + + if (bflsc->usbinfo.nodev) + return NULL; + + dev = -1; + oldest = FLT_MAX; + cgtime(&now); + + // Find the first oldest ... that also needs checking + for (i = 0; i < sc_info->sc_count; i++) { + timersub(&now, &(sc_info->sc_devs[i].last_check_result), &elapsed); + f = TVFMS(&elapsed); + if (f < oldest && f >= sc_info->sc_devs[i].ms_work) { + f = oldest; + dev = i; + } + } + + if (bflsc->usbinfo.nodev) + return NULL; + + cgsleep_prepare_r(&ts_start); + if (dev == -1) + goto utsura; + + cgtime(&(sc_info->sc_devs[dev].last_check_result)); + + readok = bflsc_qres(bflsc, buf, sizeof(buf), dev, &err, &amount, false); + if (err < 0 || (!readok && amount != BFLSC_QRES_LEN) || (readok && amount < 1)) { + // TODO: do what else? + } else { + que = process_results(bflsc, dev, buf, &nonces, &in_process); + sc_info->not_first_work = true; // in case it failed processing it + if (que > 0) + cgtime(&(sc_info->sc_devs[dev].last_dev_result)); + if (nonces > 0) + cgtime(&(sc_info->sc_devs[dev].last_nonce_result)); + + /* There are more results queued so do not sleep */ + if (in_process) + continue; + // TODO: if not getting results ... reinit? + } + +utsura: + cgsleep_ms_r(&ts_start, sc_info->results_sleep_time); + } + + return NULL; +} + +static bool bflsc_thread_prepare(struct thr_info *thr) +{ + struct cgpu_info *bflsc = thr->cgpu; + struct bflsc_info *sc_info = (struct bflsc_info *)(bflsc->device_data); + + if (thr_info_create(&(sc_info->results_thr), NULL, bflsc_get_results, (void *)bflsc)) { + applog(LOG_ERR, "%s%i: thread create failed", bflsc->drv->name, bflsc->device_id); + return false; + } + pthread_detach(sc_info->results_thr.pth); + + return true; +} + +static void bflsc_shutdown(struct thr_info *thr) +{ + struct cgpu_info *bflsc = thr->cgpu; + struct bflsc_info *sc_info = (struct bflsc_info *)(bflsc->device_data); + + bflsc_flush_work(bflsc); + sc_info->shutdown = true; +} + +static void bflsc_thread_enable(struct thr_info *thr) +{ + struct cgpu_info *bflsc = thr->cgpu; + + if (bflsc->usbinfo.nodev) + return; + + bflsc_initialise(bflsc); +} + +static bool bflsc_send_work(struct cgpu_info *bflsc, int dev, bool mandatory) +{ + struct bflsc_info *sc_info = (struct bflsc_info *)(bflsc->device_data); + struct FullNonceRangeJob data; + char buf[BFLSC_BUFSIZ+1]; + bool sent, ret = false; + struct work *work; + int err, amount; + int len, try; + int stage; + + // Device is gone + if (bflsc->usbinfo.nodev) + return false; + + // TODO: handle this everywhere + if (sc_info->sc_devs[dev].overheat == true) + return false; + + // Initially code only deals with sending one work item + data.payloadSize = BFLSC_JOBSIZ; + data.endOfBlock = BFLSC_EOB; + + len = sizeof(struct FullNonceRangeJob); + + /* On faster devices we have a lot of lock contention so only + * mandatorily grab the lock and send work if the queue is empty since + * we have a submit queue. */ + if (mandatory) + mutex_lock(&(bflsc->device_mutex)); + else { + if (mutex_trylock(&bflsc->device_mutex)) + return ret; + } + + work = get_queued(bflsc); + if (unlikely(!work)) { + mutex_unlock(&bflsc->device_mutex); + return ret; + } + memcpy(data.midState, work->midstate, MIDSTATE_BYTES); + memcpy(data.blockData, work->data + MERKLE_OFFSET, MERKLE_BYTES); + try = 0; +re_send: + err = send_recv_ds(bflsc, dev, &stage, &sent, &amount, + BFLSC_QJOB, BFLSC_QJOB_LEN, C_REQUESTQUEJOB, C_REQUESTQUEJOBSTATUS, + (char *)&data, len, C_QUEJOB, C_QUEJOBSTATUS, + buf, sizeof(buf)-1); + mutex_unlock(&(bflsc->device_mutex)); + + switch (stage) { + case 1: + if (!sent) { + bflsc_applog(bflsc, dev, C_REQUESTQUEJOB, amount, err); + goto out; + } else { + // TODO: handle other errors ... + + // Try twice + if (try++ < 1 && amount > 1 && + strstr(buf, BFLSC_TIMEOUT)) + goto re_send; + + bflsc_applog(bflsc, dev, C_REQUESTQUEJOBSTATUS, amount, err); + goto out; + } + break; + case 2: + if (!sent) { + bflsc_applog(bflsc, dev, C_QUEJOB, amount, err); + goto out; + } else { + if (!isokerr(err, buf, amount)) { + // TODO: check for QUEUE FULL and set work_queued to sc_info->que_size + // and report a code bug LOG_ERR - coz it should never happen + // TODO: handle other errors ... + + // Try twice + if (try++ < 1 && amount > 1 && + strstr(buf, BFLSC_TIMEOUT)) + goto re_send; + + bflsc_applog(bflsc, dev, C_QUEJOBSTATUS, amount, err); + goto out; + } + } + break; + } + + wr_lock(&(sc_info->stat_lock)); + sc_info->sc_devs[dev].work_queued++; + wr_unlock(&(sc_info->stat_lock)); + + work->subid = dev; + ret = true; +out: + if (unlikely(!ret)) + work_completed(bflsc, work); + return ret; +} + +#define JP_COMMAND 0 +#define JP_STREAMLENGTH 2 +#define JP_SIGNATURE 4 +#define JP_JOBSINARRY 5 +#define JP_JOBSARRY 6 +#define JP_ARRAYSIZE 45 + +static bool bflsc28_queue_full(struct cgpu_info *bflsc) +{ + struct bflsc_info *sc_info = bflsc->device_data; + int created, queued = 0, create, i, offset; + struct work *base_work, *work, *works[10]; + char *buf, *field, *ptr; + bool sent, ret = false; + uint16_t *streamlen; + uint8_t *job_pack; + int err, amount; + + job_pack = alloca(2 + // Command + 2 + // StreamLength + 1 + // Signature + 1 + // JobsInArray + JP_ARRAYSIZE * 10 +// Array of up to 10 Job Structs + 1 // EndOfWrapper + ); + + if (bflsc->usbinfo.nodev) + return true; + + /* Don't send any work if this device is overheating */ + if (sc_info->sc_devs[0].overheat == true) + return true; + + wr_lock(&bflsc->qlock); + base_work = __get_queued(bflsc); + if (likely(base_work)) + __work_completed(bflsc, base_work); + wr_unlock(&bflsc->qlock); + + if (unlikely(!base_work)) + return ret; + created = 1; + + create = 9; + if (base_work->drv_rolllimit < create) + create = base_work->drv_rolllimit; + + works[0] = base_work; + for (i = 1; i <= create ; i++) { + created++; + work = make_clone(base_work); + roll_work(base_work); + works[i] = work; + } + + memcpy(job_pack, "WX", 2); + streamlen = (uint16_t *)&job_pack[JP_STREAMLENGTH]; + *streamlen = created * JP_ARRAYSIZE + 7; + job_pack[JP_SIGNATURE] = 0xc1; + job_pack[JP_JOBSINARRY] = created; + offset = JP_JOBSARRY; + + /* Create the maximum number of work items we can queue by nrolling one */ + for (i = 0; i < created; i++) { + work = works[i]; + memcpy(job_pack + offset, work->midstate, MIDSTATE_BYTES); + offset += MIDSTATE_BYTES; + memcpy(job_pack + offset, work->data + MERKLE_OFFSET, MERKLE_BYTES); + offset += MERKLE_BYTES; + job_pack[offset] = 0xaa; // EndOfBlock signature + offset++; + } + job_pack[offset++] = 0xfe; // EndOfWrapper + + buf = alloca(BFLSC_BUFSIZ + 1); + mutex_lock(&bflsc->device_mutex); + err = send_recv_ss(bflsc, 0, &sent, &amount, (char *)job_pack, offset, + C_REQUESTQUEJOB, buf, BFLSC_BUFSIZ, C_REQUESTQUEJOBSTATUS, READ_NL); + mutex_unlock(&bflsc->device_mutex); + + if (!isokerr(err, buf, amount)) { + if (!strncasecmp(buf, "ERR:QUEUE FULL", 14)) { + applog(LOG_DEBUG, "%s%d: Queue full", + bflsc->drv->name, bflsc->device_id); + ret = true; + } else { + applog(LOG_WARNING, "%s%d: Queue response not ok %s", + bflsc->drv->name, bflsc->device_id, buf); + } + goto out; + } + + ptr = alloca(strlen(buf)); + if (sscanf(buf, "OK:QUEUED %d:%s", &queued, ptr) != 2) { + applog(LOG_WARNING, "%s%d: Failed to parse queue response %s", + bflsc->drv->name, bflsc->device_id, buf); + goto out; + } + if (queued < 1 || queued > 10) { + applog(LOG_WARNING, "%s%d: Invalid queued count %d", + bflsc->drv->name, bflsc->device_id, queued); + queued = 0; + goto out; + } + for (i = 0; i < queued; i++) { + struct bflsc_work *bwork, *oldbwork; + unsigned int uid; + + work = works[i]; + field = Strsep(&ptr, ","); + if (!field) { + applog(LOG_WARNING, "%s%d: Ran out of queued IDs after %d of %d", + bflsc->drv->name, bflsc->device_id, i, queued); + queued = i - 1; + goto out; + } + sscanf(field, "%04x", &uid); + bwork = calloc(sizeof(struct bflsc_work), 1); + bwork->id = uid; + bwork->work = work; + + wr_lock(&bflsc->qlock); + HASH_REPLACE_INT(sc_info->bworks, id, bwork, oldbwork); + if (oldbwork) { + free_work(oldbwork->work); + free(oldbwork); + } + wr_unlock(&bflsc->qlock); + sc_info->sc_devs[0].work_queued++; + } + if (queued < created) + ret = true; +out: + for (i = queued; i < created; i++) { + work = works[i]; + discard_work(work); + } + return ret; +} + +static bool bflsc_queue_full(struct cgpu_info *bflsc) +{ + struct bflsc_info *sc_info = (struct bflsc_info *)(bflsc->device_data); + int i, dev, tried, que; + bool ret = false; + int tries = 0; + + tried = -1; + // if something is wrong with a device try the next one available + // TODO: try them all? Add an unavailable flag to sc_devs[i] init to 0 here first + while (++tries < 3) { + bool mandatory = false; + + // Device is gone - shouldn't normally get here + if (bflsc->usbinfo.nodev) { + ret = true; + break; + } + + dev = -1; + rd_lock(&(sc_info->stat_lock)); + // Anything waiting - gets the work first + for (i = 0; i < sc_info->sc_count; i++) { + // TODO: and ignore x-link dead - once I work out how to decide it is dead + if (i != tried && sc_info->sc_devs[i].work_queued == 0 && + !sc_info->sc_devs[i].overheat) { + dev = i; + break; + } + } + + if (dev == -1) { + que = sc_info->que_size * 10; // 10x is certainly above the MAX it could be + // The first device with the smallest amount queued + for (i = 0; i < sc_info->sc_count; i++) { + if (i != tried && sc_info->sc_devs[i].work_queued < que && + !sc_info->sc_devs[i].overheat) { + dev = i; + que = sc_info->sc_devs[i].work_queued; + } + } + if (que > sc_info->que_full_enough) + dev = -1; + else if (que < sc_info->que_low) + mandatory = true; + } + rd_unlock(&(sc_info->stat_lock)); + + // nothing needs work yet + if (dev == -1) { + ret = true; + break; + } + + if (bflsc_send_work(bflsc, dev, mandatory)) + break; + else + tried = dev; + } + + return ret; +} + +static int64_t bflsc_scanwork(struct thr_info *thr) +{ + struct cgpu_info *bflsc = thr->cgpu; + struct bflsc_info *sc_info = (struct bflsc_info *)(bflsc->device_data); + int64_t ret, unsent; + bool flushed, cleanup; + struct work *work, *tmp; + int dev, waited, i; + + // Device is gone + if (bflsc->usbinfo.nodev) + return -1; + + flushed = false; + // Single lock check if any are flagged as flushed + rd_lock(&(sc_info->stat_lock)); + for (dev = 0; dev < sc_info->sc_count; dev++) + flushed |= sc_info->sc_devs[dev].flushed; + rd_unlock(&(sc_info->stat_lock)); + + // > 0 flagged as flushed + if (flushed) { +// TODO: something like this ...... + for (dev = 0; dev < sc_info->sc_count; dev++) { + cleanup = false; + + // Is there any flushed work that can be removed? + rd_lock(&(sc_info->stat_lock)); + if (sc_info->sc_devs[dev].flushed) { + if (sc_info->sc_devs[dev].result_id > (sc_info->sc_devs[dev].flush_id + sc_info->flush_size)) + cleanup = true; + } + rd_unlock(&(sc_info->stat_lock)); + + // yes remove the flushed work that can be removed + if (cleanup) { + wr_lock(&bflsc->qlock); + HASH_ITER(hh, bflsc->queued_work, work, tmp) { + if (work->devflag && work->subid == dev) { + bflsc->queued_count--; + HASH_DEL(bflsc->queued_work, work); + discard_work(work); + } + } + wr_unlock(&bflsc->qlock); + + wr_lock(&(sc_info->stat_lock)); + sc_info->sc_devs[dev].flushed = false; + wr_unlock(&(sc_info->stat_lock)); + } + } + } + + waited = restart_wait(thr, sc_info->scan_sleep_time); + if (waited == ETIMEDOUT && sc_info->ident != IDENT_BMA) { + unsigned int old_sleep_time, new_sleep_time = 0; + int min_queued = sc_info->que_size; + /* Only adjust the scan_sleep_time if we did not receive a + * restart message while waiting. Try to adjust sleep time + * so we drop to sc_info->que_watermark before getting more work. + */ + + rd_lock(&sc_info->stat_lock); + old_sleep_time = sc_info->scan_sleep_time; + for (i = 0; i < sc_info->sc_count; i++) { + if (sc_info->sc_devs[i].work_queued < min_queued) + min_queued = sc_info->sc_devs[i].work_queued; + } + rd_unlock(&sc_info->stat_lock); + new_sleep_time = old_sleep_time; + + /* Increase slowly but decrease quickly */ + if (min_queued > sc_info->que_full_enough && old_sleep_time < BFLSC_MAX_SLEEP) + new_sleep_time = old_sleep_time * 21 / 20; + else if (min_queued < sc_info->que_low) + new_sleep_time = old_sleep_time * 2 / 3; + + /* Do not sleep more than BFLSC_MAX_SLEEP so we can always + * report in at least 2 results per 5s log interval. */ + if (new_sleep_time != old_sleep_time) { + if (new_sleep_time > BFLSC_MAX_SLEEP) + new_sleep_time = BFLSC_MAX_SLEEP; + else if (new_sleep_time == 0) + new_sleep_time = 1; + applog(LOG_DEBUG, "%s%i: Changed scan sleep time to %d", + bflsc->drv->name, bflsc->device_id, new_sleep_time); + + wr_lock(&sc_info->stat_lock); + sc_info->scan_sleep_time = new_sleep_time; + wr_unlock(&sc_info->stat_lock); + } + } + + // Count up the work done since we last were here + ret = 0; + wr_lock(&(sc_info->stat_lock)); + for (dev = 0; dev < sc_info->sc_count; dev++) { + unsent = sc_info->sc_devs[dev].hashes_unsent; + sc_info->sc_devs[dev].hashes_unsent = 0; + sc_info->sc_devs[dev].hashes_sent += unsent; + sc_info->hashes_sent += unsent; + ret += unsent; + } + wr_unlock(&(sc_info->stat_lock)); + + return ret; +} + +#define BFLSC_OVER_TEMP 75 + +/* Set the fanspeed to auto for any valid value <= BFLSC_OVER_TEMP, + * or max for any value > BFLSC_OVER_TEMP or if we don't know the temperature. */ +static void bflsc_set_fanspeed(struct cgpu_info *bflsc) +{ + struct bflsc_info *sc_info = (struct bflsc_info *)bflsc->device_data; + char buf[BFLSC_BUFSIZ+1]; + char data[16+1]; + int amount; + bool sent; + + if ((bflsc->temp <= BFLSC_OVER_TEMP && bflsc->temp > 0 && sc_info->fanauto) || + ((bflsc->temp > BFLSC_OVER_TEMP || !bflsc->temp) && !sc_info->fanauto)) + return; + + if (bflsc->temp > BFLSC_OVER_TEMP || !bflsc->temp) { + strcpy(data, BFLSC_FAN4); + sc_info->fanauto = false; + } else { + strcpy(data, BFLSC_FANAUTO); + sc_info->fanauto = true; + } + + applog(LOG_DEBUG, "%s%i: temp=%.0f over=%d set fan to %s", + bflsc->drv->name, bflsc->device_id, bflsc->temp, + BFLSC_OVER_TEMP, data); + + mutex_lock(&bflsc->device_mutex); + send_recv_ss(bflsc, 0, &sent, &amount, + data, strlen(data), C_SETFAN, + buf, sizeof(buf)-1, C_FANREPLY, READ_NL); + mutex_unlock(&bflsc->device_mutex); +} + +static bool bflsc_get_stats(struct cgpu_info *bflsc) +{ + struct bflsc_info *sc_info = (struct bflsc_info *)(bflsc->device_data); + bool allok = true; + int i; + + // Device is gone + if (bflsc->usbinfo.nodev) + return false; + + for (i = 0; i < sc_info->sc_count; i++) { + if (!bflsc_get_temp(bflsc, i)) + allok = false; + + // Device is gone + if (bflsc->usbinfo.nodev) + return false; + + if (i < (sc_info->sc_count - 1)) + cgsleep_ms(BFLSC_TEMP_SLEEPMS); + } + + bflsc_set_fanspeed(bflsc); + + return allok; +} + +static char *bflsc_set(struct cgpu_info *bflsc, char *option, char *setting, char *replybuf) +{ + struct bflsc_info *sc_info = (struct bflsc_info *)(bflsc->device_data); + int val; + + if (sc_info->ident != IDENT_BMA) { + strcpy(replybuf, "no set options available"); + return replybuf; + } + + if (strcasecmp(option, "help") == 0) { + sprintf(replybuf, "volt: range 0-9 clock: range 0-15"); + return replybuf; + } + + if (strcasecmp(option, "volt") == 0) { + if (!setting || !*setting) { + sprintf(replybuf, "missing volt setting"); + return replybuf; + } + + val = atoi(setting); + if (val < 0 || val > 9) { + sprintf(replybuf, "invalid volt: '%s' valid range 0-9", + setting); + } + + sc_info->volt_next = val; + sc_info->volt_next_stat = true; + + return NULL; + } + + if (strcasecmp(option, "clock") == 0) { + if (!setting || !*setting) { + sprintf(replybuf, "missing clock setting"); + return replybuf; + } + + val = atoi(setting); + if (val < 0 || val > 15) { + sprintf(replybuf, "invalid clock: '%s' valid range 0-15", + setting); + } + + sc_info->clock_next = val; + sc_info->clock_next_stat = true; + + return NULL; + } + + sprintf(replybuf, "Unknown option: %s", option); + return replybuf; +} + +static void bflsc_identify(struct cgpu_info *bflsc) +{ + struct bflsc_info *sc_info = (struct bflsc_info *)(bflsc->device_data); + + // TODO: handle x-link + sc_info->flash_led = true; +} + +static bool bflsc_thread_init(struct thr_info *thr) +{ + struct cgpu_info *bflsc = thr->cgpu; + + if (bflsc->usbinfo.nodev) + return false; + + bflsc_initialise(bflsc); + + return true; +} + +// there should be a new API function to return device info that isn't the standard stuff +// instead of bflsc_api_stats - since the stats should really just be internal code info +// and the new one should be UNusual device stats/extra details - like the stuff below + +static struct api_data *bflsc_api_stats(struct cgpu_info *bflsc) +{ + struct bflsc_info *sc_info = (struct bflsc_info *)(bflsc->device_data); + struct api_data *root = NULL; + char data[4096]; + char buf[256]; + int i, j, off; + size_t len; + +//if no x-link ... etc + rd_lock(&(sc_info->stat_lock)); + root = api_add_temp(root, "Temp1", &(sc_info->sc_devs[0].temp1), true); + root = api_add_temp(root, "Temp2", &(sc_info->sc_devs[0].temp2), true); + root = api_add_volts(root, "Vcc1", &(sc_info->sc_devs[0].vcc1), true); + root = api_add_volts(root, "Vcc2", &(sc_info->sc_devs[0].vcc2), true); + root = api_add_volts(root, "Vmain", &(sc_info->sc_devs[0].vmain), true); + root = api_add_temp(root, "Temp1 Max", &(sc_info->sc_devs[0].temp1_max), true); + root = api_add_temp(root, "Temp2 Max", &(sc_info->sc_devs[0].temp2_max), true); + root = api_add_time(root, "Temp1 Max Time", &(sc_info->sc_devs[0].temp1_max_time), true); + root = api_add_time(root, "Temp2 Max Time", &(sc_info->sc_devs[0].temp2_max_time), true); + root = api_add_int(root, "Work Queued", &(sc_info->sc_devs[0].work_queued), true); + root = api_add_int(root, "Work Complete", &(sc_info->sc_devs[0].work_complete), true); + root = api_add_bool(root, "Overheat", &(sc_info->sc_devs[0].overheat), true); + root = api_add_uint64(root, "Flush ID", &(sc_info->sc_devs[0].flush_id), true); + root = api_add_uint64(root, "Result ID", &(sc_info->sc_devs[0].result_id), true); + root = api_add_bool(root, "Flushed", &(sc_info->sc_devs[0].flushed), true); + root = api_add_uint(root, "Scan Sleep", &(sc_info->scan_sleep_time), true); + root = api_add_uint(root, "Results Sleep", &(sc_info->results_sleep_time), true); + root = api_add_uint(root, "Work ms", &(sc_info->default_ms_work), true); + + buf[0] = '\0'; + for (i = 0; i <= QUE_MAX_RESULTS + 1; i++) + tailsprintf(buf, sizeof(buf), "%s%"PRIu64, (i > 0) ? "/" : "", sc_info->result_size[i]); + root = api_add_string(root, "Result Size", buf, true); + + rd_unlock(&(sc_info->stat_lock)); + + i = (int)(sc_info->driver_version); + root = api_add_int(root, "Driver", &i, true); + root = api_add_string(root, "Firmware", sc_info->sc_devs[0].firmware, false); + root = api_add_string(root, "Chips", sc_info->sc_devs[0].chips, false); + root = api_add_int(root, "Que Size", &(sc_info->que_size), false); + root = api_add_int(root, "Que Full", &(sc_info->que_full_enough), false); + root = api_add_int(root, "Que Watermark", &(sc_info->que_watermark), false); + root = api_add_int(root, "Que Low", &(sc_info->que_low), false); + root = api_add_escape(root, "GetInfo", sc_info->sc_devs[0].getinfo, false); + +/* +else a whole lot of something like these ... etc + root = api_add_temp(root, "X-%d-Temp1", &(sc_info->temp1), false); + root = api_add_temp(root, "X-%d-Temp2", &(sc_info->temp2), false); + root = api_add_volts(root, "X-%d-Vcc1", &(sc_info->vcc1), false); + root = api_add_volts(root, "X-%d-Vcc2", &(sc_info->vcc2), false); + root = api_add_volts(root, "X-%d-Vmain", &(sc_info->vmain), false); +*/ + if (sc_info->ident == IDENT_BMA) { + for (i = 0; i < 128; i += 16) { + data[0] = '\0'; + off = 0; + for (j = 0; j < 16; j++) { + len = snprintf(data+off, sizeof(data)-off, + "%s%"PRIu64, + j > 0 ? " " : "", + sc_info->cortex_nonces[i+j]); + if (len >= (sizeof(data)-off)) + off = sizeof(data)-1; + else { + if (len > 0) + off += len; + } + } + sprintf(buf, "Cortex %02x-%02x Nonces", i, i+15); + root = api_add_string(root, buf, data, true); + } + for (i = 0; i < 128; i += 16) { + data[0] = '\0'; + off = 0; + for (j = 0; j < 16; j++) { + len = snprintf(data+off, sizeof(data)-off, + "%s%"PRIu64, + j > 0 ? " " : "", + sc_info->cortex_hw[i+j]); + if (len >= (sizeof(data)-off)) + off = sizeof(data)-1; + else { + if (len > 0) + off += len; + } + } + sprintf(buf, "Cortex %02x-%02x HW Errors", i, i+15); + root = api_add_string(root, buf, data, true); + } + } else if (sc_info->que_noncecount != QUE_NONCECOUNT_V1) { + for (i = 0; i < 16; i++) { + sprintf(buf, "Core%d Nonces", i); + root = api_add_uint64(root, buf, &sc_info->core_nonces[i], false); + } + for (i = 0; i < 16; i++) { + sprintf(buf, "Core%d HW Errors", i); + root = api_add_uint64(root, buf, &sc_info->core_hw[i], false); + } + } + + return root; +} + +struct device_drv bflsc_drv = { + .drv_id = DRIVER_bflsc, + .dname = "BitForceSC", + .name = BFLSC_SINGLE, + .drv_detect = bflsc_detect, + .get_api_stats = bflsc_api_stats, + .get_statline_before = get_bflsc_statline_before, + .get_stats = bflsc_get_stats, + .set_device = bflsc_set, + .identify_device = bflsc_identify, + .thread_prepare = bflsc_thread_prepare, + .thread_init = bflsc_thread_init, + .hash_work = hash_queued_work, + .scanwork = bflsc_scanwork, + .queue_full = bflsc_queue_full, + .flush_work = bflsc_flush_work, + .thread_shutdown = bflsc_shutdown, + .thread_enable = bflsc_thread_enable +}; diff --git a/driver-bflsc.h b/driver-bflsc.h new file mode 100644 index 0000000..4778993 --- /dev/null +++ b/driver-bflsc.h @@ -0,0 +1,392 @@ +/* + * Copyright 2013-2014 Con Kolivas + * Copyright 2013 Andrew Smith + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#ifndef BFLSC_H +#define BFLSC_H +#define BLANK "" +#define LFSTR "" + +/* + * Firmware + * DRV_V2 expects (beyond V1) the GetInfo to return the chip count + * The queues are 40 instead of 20 and are *usually* consumed and filled + * in bursts due to e.g. a 16 chip device doing 16 items at a time and + * returning 16 results at a time + * If the device has varying chip speeds, it will gradually break up the + * burst of results as we progress + */ +enum driver_version { + BFLSC_DRVUNDEF = 0, + BFLSC_DRV1, + BFLSC_DRV2 +}; + +/* + * With Firmware 1.0.0 and a result queue of 20 the Max is: + * inprocess = 12 + * max count = 9 + * 64+1+24+1+1+(1+8)*8+1 per line = 164 * 20 + * OK = 3 + * Total: 3304 + * + * With Firmware 1.2.* and a result queue of 40 but a limit of 15 replies: + * inprocess = 12 + * max count = 9 + * 64+1+24+1+1+1+1+(1+8)*8+1 per line = 166 * 15 + * OK = 3 + * Total: 2514 + * + */ +#define BFLSC_BUFSIZ (0x1000) + +// Should be big enough +#define BFLSC_APPLOGSIZ 8192 + +#define BFLSC_INFO_TIMEOUT 999 + +#define BFLSC_DI_FIRMWARE "FIRMWARE" +#define BFLSC_DI_ENGINES "ENGINES" +#define BFLSC_DI_JOBSINQUE "JOBS IN QUEUE" +#define BFLSC_DI_XLINKMODE "XLINK MODE" +#define BFLSC_DI_XLINKPRESENT "XLINK PRESENT" +#define BFLSC_DI_DEVICESINCHAIN "DEVICES IN CHAIN" +#define BFLSC_DI_CHAINPRESENCE "CHAIN PRESENCE MASK" +#define BFLSC_DI_CHIPS "CHIP PARALLELIZATION" +#define BFLSC_DI_CHIPS_PARALLEL "YES" +#define BFLSC28_DI_ASICS "ASIC Installed" + +#define FULLNONCE 0x100000000ULL + +struct bflsc_dev { + // Work + unsigned int ms_work; + int work_queued; + int work_complete; + int nonces_hw; // TODO: this - need to add a paramter to submit_nonce() + // so can pass 'dev' to hw_error + uint64_t hashes_unsent; + uint64_t hashes_sent; + uint64_t nonces_found; + + struct timeval last_check_result; + struct timeval last_dev_result; // array > 0 + struct timeval last_nonce_result; // > 0 nonce + + // Info + char getinfo[(BFLSC_BUFSIZ+4)*4]; + char *firmware; + int engines; // each engine represents a 'thread' in a chip + char *xlink_mode; + char *xlink_present; + char *chips; + + // Status + bool dead; // TODO: handle seperate x-link devices failing? + bool overheat; + + // Stats + float temp1; + float temp2; + float vcc1; + float vcc2; + float vmain; + float temp1_max; + float temp2_max; + time_t temp1_max_time; + time_t temp2_max_time; + float temp1_5min_av; // TODO: + float temp2_5min_av; // TODO: + + // To handle the fact that flushing the queue may not remove all work + // (normally one item is still being processed) + // and also that once the queue is flushed, results may still be in + // the output queue - but we don't want to process them at the time of doing an LP + // when result_id > flush_id+1, flushed work can be discarded since it + // is no longer in the device + uint64_t flush_id; // counter when results were last flushed + uint64_t result_id; // counter when results were last checked + bool flushed; // are any flushed? +}; + +#define QUE_MAX_RESULTS 8 + +struct bflsc_work { + UT_hash_handle hh; + int id; + struct work *work; +}; + +struct bflsc_info { + enum sub_ident ident; + enum driver_version driver_version; + pthread_rwlock_t stat_lock; + struct thr_info results_thr; + uint64_t hashes_sent; + uint32_t update_count; + struct timeval last_update; + int sc_count; + struct bflsc_dev *sc_devs; + unsigned int scan_sleep_time; + unsigned int results_sleep_time; + unsigned int default_ms_work; + bool shutdown; + bool flash_led; + bool not_first_work; // allow ignoring the first nonce error + bool fanauto; + int que_size; + int que_full_enough; + int que_watermark; + int que_low; + int que_noncecount; + int que_fld_min; + int que_fld_max; + uint64_t core_nonces[17]; + uint64_t core_hw[17]; + int flush_size; + // count of given size, [+2] is for any > QUE_MAX_RESULTS + uint64_t result_size[QUE_MAX_RESULTS+2]; + + struct bflsc_work *bworks; + uint64_t cortex_nonces[0x80]; + uint64_t cortex_hw[0x80]; + + int volt_next; + bool volt_next_stat; + int clock_next; + bool clock_next_stat; +}; + +#define BFLSC_XLINKHDR '@' +#define BFLSC_MAXPAYLOAD 255 + +struct DataForwardToChain { + uint8_t header; + uint8_t payloadSize; + uint8_t deviceAddress; + uint8_t payloadData[BFLSC_MAXPAYLOAD]; +}; + +#define DATAFORWARDSIZE(data) (1 + 1 + 1 + data.payloadSize) + +#define MIDSTATE_BYTES 32 +#define MERKLE_OFFSET 64 +#define MERKLE_BYTES 12 +#define BFLSC_QJOBSIZ (MIDSTATE_BYTES+MERKLE_BYTES+1) +#define BFLSC_EOB 0xaa + +struct QueueJobStructure { + uint8_t payloadSize; + uint8_t midState[MIDSTATE_BYTES]; + uint8_t blockData[MERKLE_BYTES]; + uint8_t endOfBlock; +}; + +#define QUE_RES_LINES_MIN 3 +#define QUE_MIDSTATE 0 +#define QUE_BLOCKDATA 1 + +#define QUE_UID 0 +#define QUE_CC 1 + +#define QUE_NONCECOUNT_V1 2 +#define QUE_FLD_MIN_V1 3 +#define QUE_FLD_MAX_V1 (QUE_MAX_RESULTS+QUE_FLD_MIN_V1) + +#define QUE_CHIP_V2 2 +#define QUE_NONCECOUNT_V2 3 +#define QUE_FLD_MIN_V2 4 +#define QUE_FLD_MAX_V2 (QUE_MAX_RESULTS+QUE_FLD_MIN_V2) + +#define BFLSC_SIGNATURE 0xc1 +#define BFLSC_EOW 0xfe + +// N.B. this will only work with 5 jobs +// requires a different jobs[N] for each job count +// but really only need to handle 5 anyway +struct QueueJobPackStructure { + uint8_t payloadSize; + uint8_t signature; + uint8_t jobsInArray; + struct QueueJobStructure jobs[5]; + uint8_t endOfWrapper; +}; + +// TODO: Implement in API and also in usb device selection +struct SaveString { + uint8_t payloadSize; + uint8_t payloadData[BFLSC_MAXPAYLOAD]; +}; + +// Commands (Single Stage) +#define BFLSC_IDENTIFY "ZGX" +#define BFLSC_IDENTIFY_LEN (sizeof(BFLSC_IDENTIFY)-1) +#define BFLSC_DETAILS "ZCX" +#define BFLSC_DETAILS_LEN (sizeof(BFLSC_DETAILS)-1) +#define BFLSC_FIRMWARE "ZJX" +#define BFLSC_FIRMWARE_LEN (sizeof(BFLSC_FIRMWARE)-1) +#define BFLSC_FLASH "ZMX" +#define BFLSC_FLASH_LEN (sizeof(BFLSC_FLASH)-1) +#define BFLSC_VOLTAGE "ZTX" +#define BFLSC_VOLTAGE_LEN (sizeof(BFLSC_VOLTAGE)-1) +#define BFLSC_TEMPERATURE "ZLX" +#define BFLSC_TEMPERATURE_LEN (sizeof(BFLSC_TEMPERATURE)-1) +#define BFLSC_QRES "ZOX" +#define BFLSC_QRES_LEN (sizeof(BFLSC_QRES)-1) +#define BFLSC_QFLUSH "ZQX" +#define BFLSC_QFLUSH_LEN (sizeof(BFLSC_QFLUSH)-1) +#define BFLSC_FANAUTO "Z9X" +#define BFLSC_FANOUT_LEN (sizeof(BFLSC_FANAUTO)-1) +#define BFLSC_FAN0 "Z0X" +#define BFLSC_FAN0_LEN (sizeof(BFLSC_FAN0)-1) +#define BFLSC_FAN1 "Z1X" +#define BFLSC_FAN1_LEN (sizeof(BFLSC_FAN1)-1) +#define BFLSC_FAN2 "Z2X" +#define BFLSC_FAN2_LEN (sizeof(BFLSC_FAN2)-1) +#define BFLSC_FAN3 "Z3X" +#define BFLSC_FAN3_LEN (sizeof(BFLSC_FAN3)-1) +#define BFLSC_FAN4 "Z4X" +#define BFLSC_FAN4_LEN (sizeof(BFLSC_FAN4)-1) +#define BFLSC_LOADSTR "ZUX" +#define BFLSC_LOADSTR_LEN (sizeof(BFLSC_LOADSTR)-1) + +// Commands (Dual Stage) +#define BFLSC_QJOB "ZNX" +#define BFLSC_QJOB_LEN (sizeof(BFLSC_QJOB)-1) +#define BFLSC_QJOBS "ZWX" +#define BFLSC_QJOBS_LEN (sizeof(BFLSC_QJOBS)-1) +#define BFLSC_SAVESTR "ZSX" +#define BFLSC_SAVESTR_LEN (sizeof(BFLSC_SAVESTR)-1) + +// Replies +#define BFLSC_IDENTITY "BitFORCE SC" +#define BFLSC_BFLSC "SHA256 SC" +#define BFLSC_BFLSC28 "SC-28nm" + +#define BFLSC_OK "OK\n" +#define BFLSC_OK_LEN (sizeof(BFLSC_OK)-1) +#define BFLSC_SUCCESS "SUCCESS\n" +#define BFLSC_SUCCESS_LEN (sizeof(BFLSC_SUCCESS)-1) + +#define BFLSC_RESULT "COUNT:" +#define BFLSC_RESULT_LEN (sizeof(BFLSC_RESULT)-1) + +#define BFLSC_ANERR "ERR:" +#define BFLSC_ANERR_LEN (sizeof(BFLSC_ANERR)-1) +#define BFLSC_TIMEOUT BFLSC_ANERR "TIMEOUT" +#define BFLSC_TIMEOUT_LEN (sizeof(BFLSC_TIMEOUT)-1) +// x-link timeout has a space (a number follows) +#define BFLSC_XTIMEOUT BFLSC_ANERR "TIMEOUT " +#define BFLSC_XTIMEOUT_LEN (sizeof(BFLSC_XTIMEOUT)-1) +#define BFLSC_INVALID BFLSC_ANERR "INVALID DATA" +#define BFLSC_INVALID_LEN (sizeof(BFLSC_INVALID)-1) +#define BFLSC_ERRSIG BFLSC_ANERR "SIGNATURE" +#define BFLSC_ERRSIG_LEN (sizeof(BFLSC_ERRSIG)-1) +#define BFLSC_OKQ "OK:QUEUED" +#define BFLSC_OKQ_LEN (sizeof(BFLSC_OKQ)-1) +#define BFLSC_INPROCESS "INPROCESS" +#define BFLSC_INPROCESS_LEN (sizeof(BFLSC_INPROCESS)-1) +// Followed by N=1..5 +#define BFLSC_OKQN "OK:QUEUED " +#define BFLSC_OKQN_LEN (sizeof(BFLSC_OKQN)-1) +#define BFLSC_QFULL "QUEUE FULL" +#define BFLSC_QFULL_LEN (sizeof(BFLSC_QFULL)-1) +#define BFLSC_HITEMP "HIGH TEMPERATURE RECOVERY" +#define BFLSC_HITEMP_LEN (sizeof(BFLSC_HITEMP)-1) +#define BFLSC_EMPTYSTR "MEMORY EMPTY" +#define BFLSC_EMPTYSTR_LEN (sizeof(BFLSC_EMPTYSTR)-1) + +// Queued and non-queued are the same +#define FullNonceRangeJob QueueJobStructure +#define BFLSC_JOBSIZ BFLSC_QJOBSIZ + +// Non queued commands (not used) +#define BFLSC_SENDWORK "ZDX" +#define BFLSC_SENDWORK_LEN (sizeof(BFLSC_SENDWORK)-1) +#define BFLSC_WORKSTATUS "ZFX" +#define BFLSC_WORKSTATUS_LEN (sizeof(BFLSC_WORKSTATUS)-1) +#define BFLSC_SENDRANGE "ZPX" +#define BFLSC_SENDRANGE_LEN (sizeof(BFLSC_SENDRANGE)-1) + +// Non queued work replies (not used) +#define BFLSC_NONCE "NONCE-FOUND:" +#define BFLSC_NONCE_LEN (sizeof(BFLSC_NONCE)-1) +#define BFLSC_NO_NONCE "NO-NONCE" +#define BFLSC_NO_NONCE_LEN (sizeof(BFLSC_NO_NONCE)-1) +#define BFLSC_IDLE "IDLE" +#define BFLSC_IDLE_LEN (sizeof(BFLSC_IDLE)-1) +#define BFLSC_BUSY "BUSY" +#define BFLSC_BUSY_LEN (sizeof(BFLSC_BUSY)-1) + +#define BFLSC_MINIRIG "BAM" +#define BFLSC_SINGLE "BAS" +#define BFLSC_LITTLESINGLE "BAL" +#define BFLSC_JALAPENO "BAJ" +#define BFLSC_MONARCH "BMA" + +// Default expected time for a nonce range +// - thus no need to check until this + last time work was found +// 60GH/s MiniRig (1 board) or Single +#define BAM_WORK_TIME 71.58 +#define BAS_WORK_TIME 71.58 +// 30GH/s Little Single +#define BAL_WORK_TIME 143.17 +// 4.5GH/s Jalapeno +#define BAJ_WORK_TIME 954.44 +#define BMA_WORK_TIME 35 // ??? + +// Defaults (slightly over half the work time) but ensure none are above 100 +// SCAN_TIME - delay after sending work +// RES_TIME - delay between checking for results +#define BAM_SCAN_TIME 20 +#define BMA_SCAN_TIME 50 +#define BAS_SCAN_TIME 360 +#define BAL_SCAN_TIME 720 +#define BAJ_SCAN_TIME 1000 +#define BFLSC_RES_TIME 100 +#define BMA_RES_TIME 50 +#define BFLSC_MAX_SLEEP 2000 + +#define BAJ_LATENCY LATENCY_STD +#define BAL_LATENCY 12 +#define BAS_LATENCY 12 +// For now a BAM doesn't really exist - it's currently 8 independent BASs +#define BAM_LATENCY 2 + +#define BFLSC_TEMP_SLEEPMS 5 + +#define BFLSC_QUE_SIZE_V1 20 +#define BFLSC_QUE_FULL_ENOUGH_V1 13 +#define BFLSC_QUE_WATERMARK_V1 6 +#define BFLSC_QUE_LOW_V1 3 + +// TODO: use 5 batch jobs +// TODO: base these numbers on the chip count? +#define BFLSC_QUE_SIZE_V2 40 +#define BFLSC_QUE_FULL_ENOUGH_V2 36 +#define BFLSC_QUE_WATERMARK_V2 32 +#define BFLSC_QUE_LOW_V2 16 + +#define BFLSC_TEMP_OVERHEAT 85 +// Will start throttling this much below overheat +#define BFLSC_TEMP_THROTTLE 3 +// Must drop this far below overheat before resuming work +#define BFLSC_TEMP_RECOVER 5 + +// If initialisation fails the first time, +// sleep this amount (ms) and try again +#define REINIT_TIME_FIRST_MS 100 +// Max ms per sleep +#define REINIT_TIME_MAX_MS 800 +// Keep trying up to this many us +#define REINIT_TIME_MAX 3000000 + +int opt_bflsc_overheat; + +#endif /* BFLSC_H */ diff --git a/driver-bitforce.c b/driver-bitforce.c new file mode 100644 index 0000000..34f1ef9 --- /dev/null +++ b/driver-bitforce.c @@ -0,0 +1,752 @@ +/* + * Copyright 2012-2013 Andrew Smith + * Copyright 2012 Luke Dashjr + * Copyright 2012 Con Kolivas + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include "miner.h" +#include "usbutils.h" +#include "util.h" + +#ifdef WIN32 +#include +#endif /* WIN32 */ + +#define BITFORCE_IDENTIFY "ZGX" +#define BITFORCE_IDENTIFY_LEN (sizeof(BITFORCE_IDENTIFY)-1) +#define BITFORCE_FLASH "ZMX" +#define BITFORCE_FLASH_LEN (sizeof(BITFORCE_FLASH)-1) +#define BITFORCE_TEMPERATURE "ZLX" +#define BITFORCE_TEMPERATURE_LEN (sizeof(BITFORCE_TEMPERATURE)-1) +#define BITFORCE_SENDRANGE "ZPX" +#define BITFORCE_SENDRANGE_LEN (sizeof(BITFORCE_SENDRANGE)-1) +#define BITFORCE_SENDWORK "ZDX" +#define BITFORCE_SENDWORK_LEN (sizeof(BITFORCE_SENDWORK)-1) +#define BITFORCE_WORKSTATUS "ZFX" +#define BITFORCE_WORKSTATUS_LEN (sizeof(BITFORCE_WORKSTATUS)-1) + +// Either of Nonce or No-nonce start with: +#define BITFORCE_EITHER "N" +#define BITFORCE_EITHER_LEN 1 +#define BITFORCE_NONCE "NONCE-FOUND" +#define BITFORCE_NONCE_LEN (sizeof(BITFORCE_NONCE)-1) +#define BITFORCE_NO_NONCE "NO-NONCE" +#define BITFORCE_NO_NONCE_MATCH 3 +#define BITFORCE_IDLE "IDLE" +#define BITFORCE_IDLE_MATCH 1 + +#define BITFORCE_SLEEP_MS 500 +#define BITFORCE_TIMEOUT_S 7 +#define BITFORCE_TIMEOUT_MS (BITFORCE_TIMEOUT_S * 1000) +#define BITFORCE_LONG_TIMEOUT_S 30 +#define BITFORCE_LONG_TIMEOUT_MS (BITFORCE_LONG_TIMEOUT_S * 1000) +#define BITFORCE_CHECK_INTERVAL_MS 10 +#define WORK_CHECK_INTERVAL_MS 50 +#define MAX_START_DELAY_MS 100 +#define tv_to_ms(tval) (tval.tv_sec * 1000 + tval.tv_usec / 1000) +#define TIME_AVG_CONSTANT 8 + +#define KNAME_WORK "full work" +#define KNAME_RANGE "nonce range" + +#define BITFORCE_BUFSIZ (0x200) + +// If initialisation fails the first time, +// sleep this amount (ms) and try again +#define REINIT_TIME_FIRST_MS 100 +// Max ms per sleep +#define REINIT_TIME_MAX_MS 800 +// Keep trying up to this many us +#define REINIT_TIME_MAX 3000000 + +static const char *blank = ""; + +static void bitforce_initialise(struct cgpu_info *bitforce, bool lock) +{ + int err, interface; + + if (lock) + mutex_lock(&bitforce->device_mutex); + + if (bitforce->usbinfo.nodev) + goto failed; + + interface = usb_interface(bitforce); + // Reset + err = usb_transfer(bitforce, FTDI_TYPE_OUT, FTDI_REQUEST_RESET, + FTDI_VALUE_RESET, interface, C_RESET); + if (opt_debug) + applog(LOG_DEBUG, "%s%i: reset got err %d", + bitforce->drv->name, bitforce->device_id, err); + + if (bitforce->usbinfo.nodev) + goto failed; + + // Set data control + err = usb_transfer(bitforce, FTDI_TYPE_OUT, FTDI_REQUEST_DATA, + FTDI_VALUE_DATA_BFL, interface, C_SETDATA); + if (opt_debug) + applog(LOG_DEBUG, "%s%i: setdata got err %d", + bitforce->drv->name, bitforce->device_id, err); + + if (bitforce->usbinfo.nodev) + goto failed; + + // Set the baud + err = usb_transfer(bitforce, FTDI_TYPE_OUT, FTDI_REQUEST_BAUD, FTDI_VALUE_BAUD_BFL, + (FTDI_INDEX_BAUD_BFL & 0xff00) | interface, + C_SETBAUD); + if (opt_debug) + applog(LOG_DEBUG, "%s%i: setbaud got err %d", + bitforce->drv->name, bitforce->device_id, err); + + if (bitforce->usbinfo.nodev) + goto failed; + + // Set Flow Control + err = usb_transfer(bitforce, FTDI_TYPE_OUT, FTDI_REQUEST_FLOW, + FTDI_VALUE_FLOW, interface, C_SETFLOW); + if (opt_debug) + applog(LOG_DEBUG, "%s%i: setflowctrl got err %d", + bitforce->drv->name, bitforce->device_id, err); + + if (bitforce->usbinfo.nodev) + goto failed; + + // Set Modem Control + err = usb_transfer(bitforce, FTDI_TYPE_OUT, FTDI_REQUEST_MODEM, + FTDI_VALUE_MODEM, interface, C_SETMODEM); + if (opt_debug) + applog(LOG_DEBUG, "%s%i: setmodemctrl got err %d", + bitforce->drv->name, bitforce->device_id, err); + + if (bitforce->usbinfo.nodev) + goto failed; + + // Clear any sent data + err = usb_transfer(bitforce, FTDI_TYPE_OUT, FTDI_REQUEST_RESET, + FTDI_VALUE_PURGE_TX, interface, C_PURGETX); + if (opt_debug) + applog(LOG_DEBUG, "%s%i: purgetx got err %d", + bitforce->drv->name, bitforce->device_id, err); + + if (bitforce->usbinfo.nodev) + goto failed; + + // Clear any received data + err = usb_transfer(bitforce, FTDI_TYPE_OUT, FTDI_REQUEST_RESET, + FTDI_VALUE_PURGE_RX, interface, C_PURGERX); + if (opt_debug) + applog(LOG_DEBUG, "%s%i: purgerx got err %d", + bitforce->drv->name, bitforce->device_id, err); + +failed: + + if (lock) + mutex_unlock(&bitforce->device_mutex); +} + +static struct cgpu_info *bitforce_detect_one(struct libusb_device *dev, struct usb_find_devices *found) +{ + char buf[BITFORCE_BUFSIZ+1]; + int err, amount; + char *s; + struct timeval init_start, init_now; + int init_sleep, init_count; + bool ident_first; + + struct cgpu_info *bitforce = usb_alloc_cgpu(&bitforce_drv, 1); + + if (!usb_init(bitforce, dev, found)) + goto shin; + + // Allow 2 complete attempts if the 1st time returns an unrecognised reply + ident_first = true; +retry: + init_count = 0; + init_sleep = REINIT_TIME_FIRST_MS; + cgtime(&init_start); +reinit: + bitforce_initialise(bitforce, false); + if ((err = usb_write(bitforce, BITFORCE_IDENTIFY, BITFORCE_IDENTIFY_LEN, &amount, C_REQUESTIDENTIFY)) < 0 || amount != BITFORCE_IDENTIFY_LEN) { + applog(LOG_ERR, "%s detect (%s) send identify request failed (%d:%d)", + bitforce->drv->dname, bitforce->device_path, amount, err); + goto unshin; + } + + if ((err = usb_read_nl(bitforce, buf, sizeof(buf)-1, &amount, C_GETIDENTIFY)) < 0 || amount < 1) { + init_count++; + cgtime(&init_now); + if (us_tdiff(&init_now, &init_start) <= REINIT_TIME_MAX) { + if (init_count == 2) { + applog(LOG_WARNING, "%s detect (%s) 2nd init failed (%d:%d) - retrying", + bitforce->drv->dname, bitforce->device_path, amount, err); + } + cgsleep_ms(init_sleep); + if ((init_sleep * 2) <= REINIT_TIME_MAX_MS) + init_sleep *= 2; + goto reinit; + } + + if (init_count > 0) + applog(LOG_WARNING, "%s detect (%s) init failed %d times %.2fs", + bitforce->drv->dname, bitforce->device_path, init_count, tdiff(&init_now, &init_start)); + + if (err < 0) { + applog(LOG_ERR, "%s detect (%s) error identify reply (%d:%d)", + bitforce->drv->dname, bitforce->device_path, amount, err); + } else { + applog(LOG_ERR, "%s detect (%s) empty identify reply (%d)", + bitforce->drv->dname, bitforce->device_path, amount); + } + + goto unshin; + } + buf[amount] = '\0'; + + if (unlikely(!strstr(buf, "SHA256"))) { + if (ident_first) { + applog(LOG_WARNING, "%s detect (%s) didn't recognise '%s' trying again ...", + bitforce->drv->dname, bitforce->device_path, buf); + ident_first = false; + goto retry; + } + applog(LOG_ERR, "%s detect (%s) didn't recognise '%s' on 2nd attempt", + bitforce->drv->dname, bitforce->device_path, buf); + goto unshin; + } + + if (strstr(buf, "SHA256 SC")) { +#ifdef USE_BFLSC + applog(LOG_DEBUG, "SC device detected, will defer to BFLSC driver"); +#else + applog(LOG_WARNING, "SC device detected but no BFLSC support compiled in!"); +#endif + goto unshin; + } + + if (likely((!memcmp(buf, ">>>ID: ", 7)) && (s = strstr(buf + 3, ">>>")))) { + s[0] = '\0'; + bitforce->name = strdup(buf + 7); + } else { + bitforce->name = (char *)blank; + } + + // We have a real BitForce! + applog(LOG_DEBUG, "%s (%s) identified as: '%s'", + bitforce->drv->dname, bitforce->device_path, bitforce->name); + + /* Initially enable support for nonce range and disable it later if it + * fails */ + if (opt_bfl_noncerange) { + bitforce->nonce_range = true; + bitforce->sleep_ms = BITFORCE_SLEEP_MS; + bitforce->kname = KNAME_RANGE; + } else { + bitforce->sleep_ms = BITFORCE_SLEEP_MS * 5; + bitforce->kname = KNAME_WORK; + } + + if (!add_cgpu(bitforce)) + goto unshin; + + update_usb_stats(bitforce); + + mutex_init(&bitforce->device_mutex); + + return bitforce; + +unshin: + + usb_uninit(bitforce); + +shin: + + if (bitforce->name != blank) { + free(bitforce->name); + bitforce->name = NULL; + } + + bitforce = usb_free_cgpu(bitforce); + + return NULL; +} + +static void bitforce_detect(bool __maybe_unused hotplug) +{ + usb_detect(&bitforce_drv, bitforce_detect_one); +} + +static void get_bitforce_statline_before(char *buf, size_t bufsiz, struct cgpu_info *bitforce) +{ + float gt = bitforce->temp; + + if (gt > 0) + tailsprintf(buf, bufsiz, "%5.1fC", gt); +} + +static bool bitforce_thread_prepare(__maybe_unused struct thr_info *thr) +{ +// struct cgpu_info *bitforce = thr->cgpu; + + return true; +} + +static void bitforce_flash_led(struct cgpu_info *bitforce) +{ + int err, amount; + + /* Do not try to flash the led if we're polling for a result to + * minimise the chance of interleaved results */ + if (bitforce->polling) + return; + + /* It is not critical flashing the led so don't get stuck if we + * can't grab the mutex now */ + if (mutex_trylock(&bitforce->device_mutex)) + return; + + if ((err = usb_write(bitforce, BITFORCE_FLASH, BITFORCE_FLASH_LEN, &amount, C_REQUESTFLASH)) < 0 || amount != BITFORCE_FLASH_LEN) { + applog(LOG_ERR, "%s%i: flash request failed (%d:%d)", + bitforce->drv->name, bitforce->device_id, amount, err); + } else { + /* However, this stops anything else getting a reply + * So best to delay any other access to the BFL */ + cgsleep_ms(4000); + } + + /* Once we've tried - don't do it until told to again */ + bitforce->flash_led = false; + + mutex_unlock(&bitforce->device_mutex); + + return; // nothing is returned by the BFL +} + +static bool bitforce_get_temp(struct cgpu_info *bitforce) +{ + char buf[BITFORCE_BUFSIZ+1]; + int err, amount; + char *s; + + // Device is gone + if (bitforce->usbinfo.nodev) + return false; + + /* Do not try to get the temperature if we're polling for a result to + * minimise the chance of interleaved results */ + if (bitforce->polling) + return true; + + // Flash instead of Temp - doing both can be too slow + if (bitforce->flash_led) { + bitforce_flash_led(bitforce); + return true; + } + + /* It is not critical getting temperature so don't get stuck if we + * can't grab the mutex here */ + if (mutex_trylock(&bitforce->device_mutex)) + return false; + + if ((err = usb_write(bitforce, BITFORCE_TEMPERATURE, BITFORCE_TEMPERATURE_LEN, &amount, C_REQUESTTEMPERATURE)) < 0 || amount != BITFORCE_TEMPERATURE_LEN) { + mutex_unlock(&bitforce->device_mutex); + applog(LOG_ERR, "%s%i: Error: Request temp invalid/timed out (%d:%d)", + bitforce->drv->name, bitforce->device_id, amount, err); + bitforce->hw_errors++; + return false; + } + + if ((err = usb_read_nl(bitforce, buf, sizeof(buf)-1, &amount, C_GETTEMPERATURE)) < 0 || amount < 1) { + mutex_unlock(&bitforce->device_mutex); + if (err < 0) { + applog(LOG_ERR, "%s%i: Error: Get temp return invalid/timed out (%d:%d)", + bitforce->drv->name, bitforce->device_id, amount, err); + } else { + applog(LOG_ERR, "%s%i: Error: Get temp returned nothing (%d:%d)", + bitforce->drv->name, bitforce->device_id, amount, err); + } + bitforce->hw_errors++; + return false; + } + + mutex_unlock(&bitforce->device_mutex); + + if ((!strncasecmp(buf, "TEMP", 4)) && (s = strchr(buf + 4, ':'))) { + float temp = strtof(s + 1, NULL); + + /* Cope with older software that breaks and reads nonsense + * values */ + if (temp > 100) + temp = strtod(s + 1, NULL); + + if (temp > 0) { + bitforce->temp = temp; + if (unlikely(bitforce->cutofftemp > 0 && temp > bitforce->cutofftemp)) { + applog(LOG_WARNING, "%s%i: Hit thermal cutoff limit, disabling!", + bitforce->drv->name, bitforce->device_id); + bitforce->deven = DEV_RECOVER; + dev_error(bitforce, REASON_DEV_THERMAL_CUTOFF); + } + } + } else { + /* Use the temperature monitor as a kind of watchdog for when + * our responses are out of sync and flush the buffer to + * hopefully recover */ + applog(LOG_WARNING, "%s%i: Garbled response probably throttling, clearing buffer", + bitforce->drv->name, bitforce->device_id); + dev_error(bitforce, REASON_DEV_THROTTLE); + /* Count throttling episodes as hardware errors */ + bitforce->hw_errors++; + bitforce_initialise(bitforce, true); + return false; + } + + return true; +} + +static bool bitforce_send_work(struct thr_info *thr, struct work *work) +{ + struct cgpu_info *bitforce = thr->cgpu; + unsigned char ob[70]; + char buf[BITFORCE_BUFSIZ+1]; + int err, amount; + char *s; + char *cmd; + int len; + +re_send: + if (bitforce->nonce_range) { + cmd = BITFORCE_SENDRANGE; + len = BITFORCE_SENDRANGE_LEN; + } else { + cmd = BITFORCE_SENDWORK; + len = BITFORCE_SENDWORK_LEN; + } + + mutex_lock(&bitforce->device_mutex); + if ((err = usb_write(bitforce, cmd, len, &amount, C_REQUESTSENDWORK)) < 0 || amount != len) { + mutex_unlock(&bitforce->device_mutex); + applog(LOG_ERR, "%s%i: request send work failed (%d:%d)", + bitforce->drv->name, bitforce->device_id, amount, err); + return false; + } + + if ((err = usb_read_nl(bitforce, buf, sizeof(buf)-1, &amount, C_REQUESTSENDWORKSTATUS)) < 0) { + mutex_unlock(&bitforce->device_mutex); + applog(LOG_ERR, "%s%d: read request send work status failed (%d:%d)", + bitforce->drv->name, bitforce->device_id, amount, err); + return false; + } + + if (amount == 0 || !buf[0] || !strncasecmp(buf, "B", 1)) { + mutex_unlock(&bitforce->device_mutex); + cgsleep_ms(WORK_CHECK_INTERVAL_MS); + goto re_send; + } else if (unlikely(strncasecmp(buf, "OK", 2))) { + mutex_unlock(&bitforce->device_mutex); + if (bitforce->nonce_range) { + applog(LOG_WARNING, "%s%i: Does not support nonce range, disabling", + bitforce->drv->name, bitforce->device_id); + bitforce->nonce_range = false; + bitforce->sleep_ms *= 5; + bitforce->kname = KNAME_WORK; + goto re_send; + } + applog(LOG_ERR, "%s%i: Error: Send work reports: %s", + bitforce->drv->name, bitforce->device_id, buf); + return false; + } + + sprintf((char *)ob, ">>>>>>>>"); + memcpy(ob + 8, work->midstate, 32); + memcpy(ob + 8 + 32, work->data + 64, 12); + if (!bitforce->nonce_range) { + sprintf((char *)ob + 8 + 32 + 12, ">>>>>>>>"); + work->nonce = bitforce->nonces = 0xffffffff; + len = 60; + } else { + uint32_t *nonce; + + nonce = (uint32_t *)(ob + 8 + 32 + 12); + *nonce = htobe32(work->nonce); + nonce = (uint32_t *)(ob + 8 + 32 + 12 + 4); + /* Split work up into 1/5th nonce ranges */ + bitforce->nonces = 0x33333332; + *nonce = htobe32(work->nonce + bitforce->nonces); + work->nonce += bitforce->nonces + 1; + sprintf((char *)ob + 8 + 32 + 12 + 8, ">>>>>>>>"); + len = 68; + } + + if ((err = usb_write(bitforce, (char *)ob, len, &amount, C_SENDWORK)) < 0 || amount != len) { + mutex_unlock(&bitforce->device_mutex); + applog(LOG_ERR, "%s%i: send work failed (%d:%d)", + bitforce->drv->name, bitforce->device_id, amount, err); + return false; + } + + if ((err = usb_read_nl(bitforce, buf, sizeof(buf)-1, &amount, C_SENDWORKSTATUS)) < 0) { + mutex_unlock(&bitforce->device_mutex); + applog(LOG_ERR, "%s%d: read send work status failed (%d:%d)", + bitforce->drv->name, bitforce->device_id, amount, err); + return false; + } + + mutex_unlock(&bitforce->device_mutex); + + if (opt_debug) { + s = bin2hex(ob + 8, 44); + applog(LOG_DEBUG, "%s%i: block data: %s", + bitforce->drv->name, bitforce->device_id, s); + free(s); + } + + if (amount == 0 || !buf[0]) { + applog(LOG_ERR, "%s%i: Error: Send block data returned empty string/timed out", + bitforce->drv->name, bitforce->device_id); + return false; + } + + if (unlikely(strncasecmp(buf, "OK", 2))) { + applog(LOG_ERR, "%s%i: Error: Send block data reports: %s", + bitforce->drv->name, bitforce->device_id, buf); + return false; + } + + cgtime(&bitforce->work_start_tv); + return true; +} + +static int64_t bitforce_get_result(struct thr_info *thr, struct work *work) +{ + struct cgpu_info *bitforce = thr->cgpu; + unsigned int delay_time_ms; + struct timeval elapsed; + struct timeval now; + char buf[BITFORCE_BUFSIZ+1]; + int amount; + char *pnoncebuf; + uint32_t nonce; + + while (1) { + if (unlikely(thr->work_restart)) + return 0; + + mutex_lock(&bitforce->device_mutex); + usb_write(bitforce, BITFORCE_WORKSTATUS, BITFORCE_WORKSTATUS_LEN, &amount, C_REQUESTWORKSTATUS); + usb_read_nl(bitforce, buf, sizeof(buf)-1, &amount, C_GETWORKSTATUS); + mutex_unlock(&bitforce->device_mutex); + + cgtime(&now); + timersub(&now, &bitforce->work_start_tv, &elapsed); + + if (elapsed.tv_sec >= BITFORCE_LONG_TIMEOUT_S) { + applog(LOG_ERR, "%s%i: took %ldms - longer than %dms", + bitforce->drv->name, bitforce->device_id, + tv_to_ms(elapsed), BITFORCE_LONG_TIMEOUT_MS); + return 0; + } + + if (amount > 0 && buf[0] && strncasecmp(buf, "B", 1)) /* BFL does not respond during throttling */ + break; + + /* if BFL is throttling, no point checking so quickly */ + delay_time_ms = (buf[0] ? BITFORCE_CHECK_INTERVAL_MS : 2 * WORK_CHECK_INTERVAL_MS); + cgsleep_ms(delay_time_ms); + bitforce->wait_ms += delay_time_ms; + } + + if (elapsed.tv_sec > BITFORCE_TIMEOUT_S) { + applog(LOG_ERR, "%s%i: took %ldms - longer than %dms", + bitforce->drv->name, bitforce->device_id, + tv_to_ms(elapsed), BITFORCE_TIMEOUT_MS); + dev_error(bitforce, REASON_DEV_OVER_HEAT); + + /* Only return if we got nothing after timeout - there still may be results */ + if (amount == 0) + return 0; + } else if (!strncasecmp(buf, BITFORCE_EITHER, BITFORCE_EITHER_LEN)) { + /* Simple timing adjustment. Allow a few polls to cope with + * OS timer delays being variably reliable. wait_ms will + * always equal sleep_ms when we've waited greater than or + * equal to the result return time.*/ + delay_time_ms = bitforce->sleep_ms; + + if (bitforce->wait_ms > bitforce->sleep_ms + (WORK_CHECK_INTERVAL_MS * 2)) + bitforce->sleep_ms += (bitforce->wait_ms - bitforce->sleep_ms) / 2; + else if (bitforce->wait_ms == bitforce->sleep_ms) { + if (bitforce->sleep_ms > WORK_CHECK_INTERVAL_MS) + bitforce->sleep_ms -= WORK_CHECK_INTERVAL_MS; + else if (bitforce->sleep_ms > BITFORCE_CHECK_INTERVAL_MS) + bitforce->sleep_ms -= BITFORCE_CHECK_INTERVAL_MS; + } + + if (delay_time_ms != bitforce->sleep_ms) + applog(LOG_DEBUG, "%s%i: Wait time changed to: %d, waited %u", + bitforce->drv->name, bitforce->device_id, + bitforce->sleep_ms, bitforce->wait_ms); + + /* Work out the average time taken. Float for calculation, uint for display */ + bitforce->avg_wait_f += (tv_to_ms(elapsed) - bitforce->avg_wait_f) / TIME_AVG_CONSTANT; + bitforce->avg_wait_d = (unsigned int) (bitforce->avg_wait_f + 0.5); + } + + applog(LOG_DEBUG, "%s%i: waited %dms until %s", + bitforce->drv->name, bitforce->device_id, + bitforce->wait_ms, buf); + if (!strncasecmp(buf, BITFORCE_NO_NONCE, BITFORCE_NO_NONCE_MATCH)) + return bitforce->nonces; /* No valid nonce found */ + else if (!strncasecmp(buf, BITFORCE_IDLE, BITFORCE_IDLE_MATCH)) + return 0; /* Device idle */ + else if (strncasecmp(buf, BITFORCE_NONCE, BITFORCE_NONCE_LEN)) { + bitforce->hw_errors++; + applog(LOG_WARNING, "%s%i: Error: Get result reports: %s", + bitforce->drv->name, bitforce->device_id, buf); + bitforce_initialise(bitforce, true); + return 0; + } + + pnoncebuf = &buf[12]; + + while (1) { + hex2bin((void*)&nonce, pnoncebuf, 4); +#ifndef __BIG_ENDIAN__ + nonce = swab32(nonce); +#endif + if (unlikely(bitforce->nonce_range && (nonce >= work->nonce || + (work->nonce > 0 && nonce < work->nonce - bitforce->nonces - 1)))) { + applog(LOG_WARNING, "%s%i: Disabling broken nonce range support", + bitforce->drv->name, bitforce->device_id); + bitforce->nonce_range = false; + work->nonce = 0xffffffff; + bitforce->sleep_ms *= 5; + bitforce->kname = KNAME_WORK; + } + + submit_nonce(thr, work, nonce); + if (strncmp(&pnoncebuf[8], ",", 1)) + break; + pnoncebuf += 9; + } + + return bitforce->nonces; +} + +static void bitforce_shutdown(__maybe_unused struct thr_info *thr) +{ +// struct cgpu_info *bitforce = thr->cgpu; +} + +static void biforce_thread_enable(struct thr_info *thr) +{ + struct cgpu_info *bitforce = thr->cgpu; + + bitforce_initialise(bitforce, true); +} + +static int64_t bitforce_scanhash(struct thr_info *thr, struct work *work, int64_t __maybe_unused max_nonce) +{ + struct cgpu_info *bitforce = thr->cgpu; + bool send_ret; + int64_t ret; + + // Device is gone + if (bitforce->usbinfo.nodev) + return -1; + + send_ret = bitforce_send_work(thr, work); + + if (!restart_wait(thr, bitforce->sleep_ms)) + return 0; + + bitforce->wait_ms = bitforce->sleep_ms; + + if (send_ret) { + bitforce->polling = true; + ret = bitforce_get_result(thr, work); + bitforce->polling = false; + } else + ret = -1; + + if (ret == -1) { + ret = 0; + applog(LOG_ERR, "%s%i: Comms error", bitforce->drv->name, bitforce->device_id); + dev_error(bitforce, REASON_DEV_COMMS_ERROR); + bitforce->hw_errors++; + /* empty read buffer */ + bitforce_initialise(bitforce, true); + } + return ret; +} + +static bool bitforce_get_stats(struct cgpu_info *bitforce) +{ + return bitforce_get_temp(bitforce); +} + +static void bitforce_identify(struct cgpu_info *bitforce) +{ + bitforce->flash_led = true; +} + +static bool bitforce_thread_init(struct thr_info *thr) +{ + struct cgpu_info *bitforce = thr->cgpu; + unsigned int wait; + + /* Pause each new thread at least 100ms between initialising + * so the devices aren't making calls all at the same time. */ + wait = thr->id * MAX_START_DELAY_MS; + applog(LOG_DEBUG, "%s%d: Delaying start by %dms", + bitforce->drv->name, bitforce->device_id, wait / 1000); + cgsleep_ms(wait); + + return true; +} + +static struct api_data *bitforce_api_stats(struct cgpu_info *cgpu) +{ + struct api_data *root = NULL; + + // Warning, access to these is not locked - but we don't really + // care since hashing performance is way more important than + // locking access to displaying API debug 'stats' + // If locking becomes an issue for any of them, use copy_data=true also + root = api_add_uint(root, "Sleep Time", &(cgpu->sleep_ms), false); + root = api_add_uint(root, "Avg Wait", &(cgpu->avg_wait_d), false); + + return root; +} + +struct device_drv bitforce_drv = { + .drv_id = DRIVER_bitforce, + .dname = "BitForce", + .name = "BFL", + .drv_detect = bitforce_detect, + .get_api_stats = bitforce_api_stats, + .get_statline_before = get_bitforce_statline_before, + .get_stats = bitforce_get_stats, + .identify_device = bitforce_identify, + .thread_prepare = bitforce_thread_prepare, + .thread_init = bitforce_thread_init, + .scanhash = bitforce_scanhash, + .thread_shutdown = bitforce_shutdown, + .thread_enable = biforce_thread_enable +}; diff --git a/driver-bitfury.c b/driver-bitfury.c new file mode 100644 index 0000000..6951ccf --- /dev/null +++ b/driver-bitfury.c @@ -0,0 +1,1660 @@ +/* + * Copyright 2013-2014 Con Kolivas + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#include "config.h" + +#include "miner.h" +#include "driver-bitfury.h" +#include "sha2.h" +#include "mcp2210.h" +#include "libbitfury.h" + +int opt_bxf_temp_target = BXF_TEMP_TARGET / 10; +int opt_nfu_bits = 50; +int opt_bxm_bits = 54; +int opt_bxf_bits = 54; +int opt_bxf_debug; +int opt_osm_led_mode = 4; + +/* Wait longer 1/3 longer than it would take for a full nonce range */ +#define BF1WAIT 1600 +#define BF1MSGSIZE 7 +#define BF1INFOSIZE 14 + +#define TWELVE_MHZ 12000000 + +//Low port pins +#define SK 1 +#define DO 2 +#define DI 4 +#define CS 8 +#define GPIO0 16 +#define GPIO1 32 +#define GPIO2 64 +#define GPIO3 128 + +//GPIO pins +#define GPIOL0 0 +#define GPIOL1 1 +#define GPIOL2 2 +#define GPIOL3 3 +#define GPIOH 4 +#define GPIOH1 5 +#define GPIOH2 6 +#define GPIOH3 7 +#define GPIOH4 8 +#define GPIOH5 9 +#define GPIOH6 10 +#define GPIOH7 11 + +#define DEFAULT_DIR (SK | DO | CS | GPIO0 | GPIO1 | GPIO2 | GPIO3) /* Setup default input or output state per FTDI for SPI */ +#define DEFAULT_STATE (CS) /* CS idles high, CLK idles LOW for SPI0 */ + +//MPSSE commands from FTDI AN_108 +#define INVALID_COMMAND 0xAB +#define ENABLE_ADAPTIVE_CLOCK 0x96 +#define DISABLE_ADAPTIVE_CLOCK 0x97 +#define ENABLE_3_PHASE_CLOCK 0x8C +#define DISABLE_3_PHASE_CLOCK 0x8D +#define TCK_X5 0x8A +#define TCK_D5 0x8B +#define CLOCK_N_CYCLES 0x8E +#define CLOCK_N8_CYCLES 0x8F +#define PULSE_CLOCK_IO_HIGH 0x94 +#define PULSE_CLOCK_IO_LOW 0x95 +#define CLOCK_N8_CYCLES_IO_HIGH 0x9C +#define CLOCK_N8_CYCLES_IO_LOW 0x9D +#define TRISTATE_IO 0x9E +#define TCK_DIVISOR 0x86 +#define LOOPBACK_END 0x85 +#define SET_OUT_ADBUS 0x80 +#define SET_OUT_ACBUS 0x82 +#define WRITE_BYTES_SPI0 0x11 +#define READ_WRITE_BYTES_SPI0 0x31 + +static void bf1_empty_buffer(struct cgpu_info *bitfury) +{ + char buf[512]; + int amount; + + do { + usb_read_once(bitfury, buf, 512, &amount, C_BF1_FLUSH); + } while (amount); +} + +static bool bf1_open(struct cgpu_info *bitfury) +{ + uint32_t buf[2]; + int err; + + bf1_empty_buffer(bitfury); + /* Magic sequence to reset device only really needed for windows but + * harmless on linux. */ + buf[0] = 0x80250000; + buf[1] = 0x00000800; + err = usb_transfer(bitfury, 0, 9, 1, 0, C_ATMEL_RESET); + if (!err) + err = usb_transfer(bitfury, 0x21, 0x22, 0, 0, C_ATMEL_OPEN); + if (!err) { + err = usb_transfer_data(bitfury, 0x21, 0x20, 0x0000, 0, buf, + BF1MSGSIZE, C_ATMEL_INIT); + } + + if (err < 0) { + applog(LOG_INFO, "%s %d: Failed to open with error %s", bitfury->drv->name, + bitfury->device_id, libusb_error_name(err)); + } + return (err == BF1MSGSIZE); +} + +static void bf1_close(struct cgpu_info *bitfury) +{ + bf1_empty_buffer(bitfury); +} + +static void bf1_identify(struct cgpu_info *bitfury) +{ + int amount; + + usb_write(bitfury, "L", 1, &amount, C_BF1_IDENTIFY); +} + +static void bitfury_identify(struct cgpu_info *bitfury) +{ + struct bitfury_info *info = bitfury->device_data; + + switch(info->ident) { + case IDENT_BF1: + bf1_identify(bitfury); + break; + case IDENT_BXF: + case IDENT_OSM: + default: + break; + } +} + +static bool bf1_getinfo(struct cgpu_info *bitfury, struct bitfury_info *info) +{ + int amount, err; + char buf[16]; + + err = usb_write(bitfury, "I", 1, &amount, C_BF1_REQINFO); + if (err) { + applog(LOG_INFO, "%s %d: Failed to write REQINFO", + bitfury->drv->name, bitfury->device_id); + return false; + } + err = usb_read(bitfury, buf, BF1INFOSIZE, &amount, C_BF1_GETINFO); + if (err) { + applog(LOG_INFO, "%s %d: Failed to read GETINFO", + bitfury->drv->name, bitfury->device_id); + return false; + } + if (amount != BF1INFOSIZE) { + applog(LOG_INFO, "%s %d: Getinfo received %d bytes instead of %d", + bitfury->drv->name, bitfury->device_id, amount, BF1INFOSIZE); + return false; + } + info->version = buf[1]; + memcpy(&info->product, buf + 2, 8); + memcpy(&info->serial, buf + 10, 4); + bitfury->unique_id = bin2hex((unsigned char *)buf + 10, 4); + + applog(LOG_INFO, "%s %d: Getinfo returned version %d, product %s serial %s", bitfury->drv->name, + bitfury->device_id, info->version, info->product, bitfury->unique_id); + bf1_empty_buffer(bitfury); + return true; +} + +static bool bf1_reset(struct cgpu_info *bitfury) +{ + int amount, err; + char buf[16]; + + err = usb_write(bitfury, "R", 1, &amount, C_BF1_REQRESET); + if (err) { + applog(LOG_INFO, "%s %d: Failed to write REQRESET", + bitfury->drv->name, bitfury->device_id); + return false; + } + err = usb_read_timeout(bitfury, buf, BF1MSGSIZE, &amount, BF1WAIT, + C_BF1_GETRESET); + if (err) { + applog(LOG_INFO, "%s %d: Failed to read GETRESET", + bitfury->drv->name, bitfury->device_id); + return false; + } + if (amount != BF1MSGSIZE) { + applog(LOG_INFO, "%s %d: Getreset received %d bytes instead of %d", + bitfury->drv->name, bitfury->device_id, amount, BF1MSGSIZE); + return false; + } + applog(LOG_DEBUG, "%s %d: Getreset returned %s", bitfury->drv->name, + bitfury->device_id, buf); + bf1_empty_buffer(bitfury); + return true; +} + +static bool bxf_send_msg(struct cgpu_info *bitfury, char *buf, enum usb_cmds cmd) +{ + int err, amount, len; + + if (unlikely(bitfury->usbinfo.nodev)) + return false; + + if (opt_bxf_debug) { + char *strbuf = str_text(buf); + + applog(LOG_ERR, "%s %d: >BXF [%s]", bitfury->drv->name, bitfury->device_id, strbuf); + free(strbuf); + } + + len = strlen(buf); + applog(LOG_DEBUG, "%s %d: Sending %s", bitfury->drv->name, bitfury->device_id, buf); + err = usb_write(bitfury, buf, len, &amount, cmd); + if (err || amount != len) { + applog(LOG_WARNING, "%s %d: Error %d sending %s sent %d of %d", bitfury->drv->name, + bitfury->device_id, err, usb_cmdname(cmd), amount, len); + return false; + } + return true; +} + +static bool bxf_send_debugmode(struct cgpu_info *bitfury) +{ + char buf[16]; + + sprintf(buf, "debug-mode %d\n", opt_bxf_debug); + return bxf_send_msg(bitfury, buf, C_BXF_DEBUGMODE); +} + +static bool bxf_send_ledmode(struct cgpu_info *bitfury) +{ + char buf[16]; + + sprintf(buf, "led-mode %d\n", opt_osm_led_mode); + return bxf_send_msg(bitfury, buf, C_BXF_LEDMODE); +} + +/* Returns the amount received only if we receive a full message, otherwise + * it returns the err value. */ +static int bxf_recv_msg(struct cgpu_info *bitfury, char *buf) +{ + int err, amount; + + err = usb_read_nl(bitfury, buf, 512, &amount, C_BXF_READ); + if (amount) + applog(LOG_DEBUG, "%s %d: Received %s", bitfury->drv->name, bitfury->device_id, buf); + if (!err) + return amount; + return err; +} + +/* Keep reading till the first timeout or error */ +static void bxf_clear_buffer(struct cgpu_info *bitfury) +{ + int err, retries = 0; + char buf[512]; + + do { + err = bxf_recv_msg(bitfury, buf); + usb_buffer_clear(bitfury); + if (err < 0) + break; + } while (retries++ < 10); +} + +static bool bxf_send_flush(struct cgpu_info *bitfury) +{ + char buf[8]; + + sprintf(buf, "flush\n"); + return bxf_send_msg(bitfury, buf, C_BXF_FLUSH); +} + +static bool bxf_detect_one(struct cgpu_info *bitfury, struct bitfury_info *info) +{ + int err, retries = 0; + char buf[512]; + + if (!bxf_send_flush(bitfury)) + return false; + + bxf_clear_buffer(bitfury); + + sprintf(buf, "version\n"); + if (!bxf_send_msg(bitfury, buf, C_BXF_VERSION)) + return false; + + do { + err = bxf_recv_msg(bitfury, buf); + if (err < 0 && err != LIBUSB_ERROR_TIMEOUT) + return false; + if (err > 0 && !strncmp(buf, "version", 7)) { + sscanf(&buf[8], "%d.%d rev %d chips %d", &info->ver_major, + &info->ver_minor, &info->hw_rev, &info->chips); + applog(LOG_INFO, "%s %d: Version %d.%d rev %d chips %d", + bitfury->drv->name, bitfury->device_id, info->ver_major, + info->ver_minor, info->hw_rev, info->chips); + break; + } + /* Keep parsing if the buffer is full without counting it as + * a retry. */ + if (usb_buffer_size(bitfury)) + continue; + } while (retries++ < 10); + + if (!add_cgpu(bitfury)) + quit(1, "Failed to add_cgpu in bxf_detect_one"); + + update_usb_stats(bitfury); + applog(LOG_INFO, "%s %d: Successfully initialised %s", + bitfury->drv->name, bitfury->device_id, bitfury->device_path); + + /* Sanity check and recognise variations */ + if (info->chips <= 2 || info->chips > 999) + info->chips = 2; + else if (info->chips <= 6 && info->ident == IDENT_BXF) + bitfury->drv->name = "HXF"; + else if (info->chips > 6 && info->ident == IDENT_BXF) + bitfury->drv->name = "MXF"; + info->filtered_hw = calloc(sizeof(int), info->chips); + info->job = calloc(sizeof(int), info->chips); + info->submits = calloc(sizeof(int), info->chips); + if (!info->filtered_hw || !info->job || !info->submits) + quit(1, "Failed to calloc bxf chip arrays"); + info->total_nonces = 1; + info->temp_target = opt_bxf_temp_target * 10; + /* This unsets it to make sure it gets set on the first pass */ + info->maxroll = -1; + + return true; +} + +static bool bf1_detect_one(struct cgpu_info *bitfury, struct bitfury_info *info) +{ + if (!bf1_open(bitfury)) + goto out_close; + + /* Send getinfo request */ + if (!bf1_getinfo(bitfury, info)) + goto out_close; + + /* Send reset request */ + if (!bf1_reset(bitfury)) + goto out_close; + + bf1_identify(bitfury); + bf1_empty_buffer(bitfury); + + if (!add_cgpu(bitfury)) + quit(1, "Failed to add_cgpu in bf1_detect_one"); + + update_usb_stats(bitfury); + applog(LOG_INFO, "%s %d: Successfully initialised %s", + bitfury->drv->name, bitfury->device_id, bitfury->device_path); + + /* This does not artificially raise hashrate, it simply allows the + * hashrate to adapt quickly on starting. */ + info->total_nonces = 1; + + return true; +out_close: + bf1_close(bitfury); + return false; +} + +static void nfu_close(struct cgpu_info *bitfury) +{ + struct bitfury_info *info = bitfury->device_data; + struct mcp_settings *mcp = &info->mcp; + int i; + + mcp2210_spi_cancel(bitfury); + + /* Set all pins to input mode, ignoring return code */ + for (i = 0; i < 9; i++) { + mcp->direction.pin[i] = MCP2210_GPIO_INPUT; + mcp->value.pin[i] = MCP2210_GPIO_PIN_LOW; + } + mcp2210_set_gpio_settings(bitfury, mcp); +} + +static bool nfu_reinit(struct cgpu_info *bitfury, struct bitfury_info *info) +{ + bool ret = true; + int i; + + for (i = 0; i < info->chips; i++) { + spi_clear_buf(info); + spi_add_break(info); + spi_add_fasync(info, i); + spi_set_freq(info); + spi_send_conf(info); + spi_send_init(info); + spi_reset(bitfury, info); + ret = info->spi_txrx(bitfury, info); + if (!ret) + break; + } + return ret; +} + +static bool nfu_set_spi_settings(struct cgpu_info *bitfury, struct bitfury_info *info) +{ + struct mcp_settings *mcp = &info->mcp; + + return mcp2210_set_spi_transfer_settings(bitfury, mcp->bitrate, mcp->icsv, + mcp->acsv, mcp->cstdd, mcp->ldbtcsd, mcp->sdbd, mcp->bpst, mcp->spimode); +} + +static void nfu_alloc_arrays(struct bitfury_info *info) +{ + info->payload = calloc(sizeof(struct bitfury_payload), info->chips); + info->oldbuf = calloc(sizeof(unsigned int) * 17, info->chips); + info->job_switched = calloc(sizeof(bool), info->chips); + info->second_run = calloc(sizeof(bool), info->chips); + info->work = calloc(sizeof(struct work *), info->chips); + info->owork = calloc(sizeof(struct work *), info->chips); + info->submits = calloc(sizeof(int *), info->chips); +} + +static bool nfu_detect_one(struct cgpu_info *bitfury, struct bitfury_info *info) +{ + struct mcp_settings *mcp = &info->mcp; + char buf[MCP2210_BUFFER_LENGTH]; + unsigned int length; + bool ret = false; + int i, val; + + /* Identify number of chips, and use it in device name if it can fit + * into 3 chars, otherwise use generic NFU name. */ + val = sscanf(bitfury->usbdev->prod_string, "NanoFury NF%u ", &info->chips); + if (val < 1) + info->chips = 1; + else if (info->chips < 10) { + sprintf(info->product, "NF%u", info->chips); + bitfury->drv->name = info->product; + } + nfu_alloc_arrays(info); + + info->spi_txrx = &mcp_spi_txrx; + mcp2210_get_gpio_settings(bitfury, mcp); + + for (i = 0; i < 9; i++) { + /* Set all pins to GPIO mode */ + mcp->designation.pin[i] = MCP2210_PIN_GPIO; + /* Set all pins to input mode */ + mcp->direction.pin[i] = MCP2210_GPIO_INPUT; + mcp->value.pin[i] = MCP2210_GPIO_PIN_LOW; + } + + /* Set LED and PWR pins to output and high */ + mcp->direction.pin[NFU_PIN_LED] = mcp->direction.pin[NFU_PIN_PWR_EN] = MCP2210_GPIO_OUTPUT; + mcp->value.pin[NFU_PIN_LED] = mcp->value.pin[NFU_PIN_PWR_EN] = MCP2210_GPIO_PIN_HIGH; + mcp->direction.pin[NFU_PIN_PWR_EN0] = MCP2210_GPIO_OUTPUT; + mcp->value.pin[NFU_PIN_PWR_EN0] = MCP2210_GPIO_PIN_LOW; + + mcp->direction.pin[4] = MCP2210_GPIO_OUTPUT; + mcp->designation.pin[4] = MCP2210_PIN_CS; + + if (!mcp2210_set_gpio_settings(bitfury, mcp)) + goto out; + + if (opt_debug) { + struct gpio_pin gp; + + mcp2210_get_gpio_pindirs(bitfury, &gp); + for (i = 0; i < 9; i++) { + applog(LOG_DEBUG, "%s %d: Pin dir %d %d", bitfury->drv->name, + bitfury->device_id, i, gp.pin[i]); + } + mcp2210_get_gpio_pinvals(bitfury, &gp); + for (i = 0; i < 9; i++) { + applog(LOG_DEBUG, "%s %d: Pin val %d %d", bitfury->drv->name, + bitfury->device_id, i, gp.pin[i]); + } + mcp2210_get_gpio_pindes(bitfury, &gp); + for (i = 0; i < 9; i++) { + applog(LOG_DEBUG, "%s %d: Pin des %d %d", bitfury->drv->name, + bitfury->device_id, i, gp.pin[i]); + } + } + + /* Cancel any transfers in progress */ + if (!mcp2210_spi_cancel(bitfury)) + goto out; + if (!mcp2210_get_spi_transfer_settings(bitfury, &mcp->bitrate, &mcp->icsv, + &mcp->acsv, &mcp->cstdd, &mcp->ldbtcsd, &mcp->sdbd, &mcp->bpst, &mcp->spimode)) + goto out; + mcp->bitrate = 200000; // default to 200kHz + mcp->icsv = 0xffff; + mcp->acsv = 0xffef; + mcp->cstdd = mcp->ldbtcsd = mcp->sdbd = mcp->spimode = 0; + mcp->bpst = 1; + if (!nfu_set_spi_settings(bitfury, info)) + goto out; + + buf[0] = 0; + length = 1; + if (!mcp2210_spi_transfer(bitfury, mcp, buf, &length)) + goto out; + /* after this command SCK_OVRRIDE should read the same as current SCK + * value (which for mode 0 should be 0) */ + if (!mcp2210_get_gpio_pinval(bitfury, NFU_PIN_SCK_OVR, &val)) + goto out; + if (val != MCP2210_GPIO_PIN_LOW) + goto out; + + /* switch SCK to polarity (default SCK=1 in mode 2) */ + mcp->spimode = 2; + if (!nfu_set_spi_settings(bitfury, info)) + goto out; + buf[0] = 0; + length = 1; + if (!mcp2210_spi_transfer(bitfury, mcp, buf, &length)) + goto out; + /* after this command SCK_OVRRIDE should read the same as current SCK + * value (which for mode 2 should be 1) */ + if (!mcp2210_get_gpio_pinval(bitfury, NFU_PIN_SCK_OVR, &val)) + goto out; + if (val != MCP2210_GPIO_PIN_HIGH) + goto out; + + /* switch SCK to polarity (default SCK=0 in mode 0) */ + mcp->spimode = 0; + if (!nfu_set_spi_settings(bitfury, info)) + goto out; + buf[0] = 0; + length = 1; + if (!mcp2210_spi_transfer(bitfury, mcp, buf, &length)) + goto out; + if (!mcp2210_get_gpio_pinval(bitfury, NFU_PIN_SCK_OVR, &val)) + goto out; + if (val != MCP2210_GPIO_PIN_LOW) + goto out; + + info->osc6_bits = opt_nfu_bits; + if (!nfu_reinit(bitfury, info)) + goto out; + + ret = true; + if (!add_cgpu(bitfury)) + quit(1, "Failed to add_cgpu in nfu_detect_one"); + + update_usb_stats(bitfury); + applog(LOG_INFO, "%s %d: Successfully initialised %s", + bitfury->drv->name, bitfury->device_id, bitfury->device_path); + spi_clear_buf(info); + + info->total_nonces = info->chips; +out: + if (!ret) + nfu_close(bitfury); + + return ret; +} + +static bool bxm_purge_buffers(struct cgpu_info *bitfury) +{ + int err; + + err = usb_transfer(bitfury, FTDI_TYPE_OUT, SIO_RESET_REQUEST, SIO_RESET_PURGE_RX, 1, C_BXM_PURGERX); + if (err) + return false; + err = usb_transfer(bitfury, FTDI_TYPE_OUT, SIO_RESET_REQUEST, SIO_RESET_PURGE_TX, 1, C_BXM_PURGETX); + if (err) + return false; + return true; +} + +/* Calculate required divisor for desired frequency see FTDI AN_108 page 19*/ +static uint16_t calc_divisor(uint32_t system_clock, uint32_t freq) +{ + uint16_t divisor = system_clock / freq; + + divisor /= 2; + divisor -= 1; + return divisor; +} + +static void bxm_shutdown(struct cgpu_info *bitfury, struct bitfury_info *info) +{ + int chip_n; + + for (chip_n = 0; chip_n < 2; chip_n++) { + spi_clear_buf(info); + spi_add_break(info); + spi_add_fasync(info, chip_n); + spi_config_reg(info, 4, 0); + info->spi_txrx(bitfury, info); + } +} + +static void bxm_close(struct cgpu_info *bitfury, struct bitfury_info *info) +{ + unsigned char bitmask = 0; + unsigned char mode = BITMODE_RESET; + unsigned short usb_val = bitmask; + + bxm_shutdown(bitfury, info); + + //Need to do BITMODE_RESET before usb close per FTDI + usb_val |= (mode << 8); + usb_transfer(bitfury, FTDI_TYPE_OUT, SIO_SET_BITMODE_REQUEST, usb_val, 1, C_BXM_SETBITMODE); +} + +static bool bxm_open(struct cgpu_info *bitfury) +{ + unsigned char mode = BITMODE_RESET; + unsigned char bitmask = 0; + unsigned short usb_val = bitmask; + uint32_t system_clock = TWELVE_MHZ; + uint32_t freq = 200000; + uint16_t divisor = calc_divisor(system_clock,freq); + int amount, err; + char buf[4]; + + /* Enable the transaction translator emulator for these devices + * otherwise we may write to them too quickly. */ + bitfury->usbdev->tt = true; + + err = usb_transfer(bitfury, FTDI_TYPE_OUT, SIO_RESET_REQUEST, SIO_RESET_SIO, 1, C_BXM_SRESET); + if (err) + return false; + err = usb_transfer(bitfury, FTDI_TYPE_OUT, SIO_SET_LATENCY_TIMER_REQUEST, BXM_LATENCY_MS, 1, C_BXM_SETLATENCY); + if (err) + return false; + err = usb_transfer(bitfury, FTDI_TYPE_OUT, SIO_SET_EVENT_CHAR_REQUEST, 0x00, 1, C_BXM_SECR); + if (err) + return false; + + //Do a BITMODE_RESET + usb_val |= (mode << 8); + err = usb_transfer(bitfury, FTDI_TYPE_OUT, SIO_SET_BITMODE_REQUEST, usb_val, 1, C_BXM_SETBITMODE); + if (err) + return false; + //Now set to MPSSE mode + bitmask = 0; + mode = BITMODE_MPSSE; + usb_val = bitmask; + usb_val |= (mode << 8); + err = usb_transfer(bitfury, FTDI_TYPE_OUT, SIO_SET_BITMODE_REQUEST, usb_val, 1, C_BXM_SETBITMODE); + if (err) + return false; + + //Now set the clock divisor + //First send just the 0x8B command to set the system clock to 12MHz + memset(buf, 0, 4); + buf[0] = TCK_D5; + err = usb_write(bitfury, buf, 1, &amount, C_BXM_CLOCK); + if (err || amount != 1) + return false; + + buf[0] = TCK_DIVISOR; + buf[1] = (divisor & 0xFF); + buf[2] = ((divisor >> 8) & 0xFF); + err = usb_write(bitfury, buf, 3, &amount, C_BXM_CLOCKDIV); + if (err || amount != 3) + return false; + + //Disable internal loopback + buf[0] = LOOPBACK_END; + err = usb_write(bitfury, buf, 1, &amount, C_BXM_LOOP); + if (err || amount != 1) + return false; + + //Now set direction and idle (initial) states for the pins + buf[0] = SET_OUT_ADBUS; + buf[1] = DEFAULT_STATE; //Bitmask for LOW_PORT + buf[2] = DEFAULT_DIR; + err = usb_write(bitfury, buf, 3, &amount, C_BXM_ADBUS); + if (err || amount != 3) + return false; + + //Set the pin states for the HIGH_BITS port as all outputs, all low + buf[0] = SET_OUT_ACBUS; + buf[1] = 0x00; //Bitmask for HIGH_PORT + buf[2] = 0xFF; + err = usb_write(bitfury, buf, 3, &amount, C_BXM_ACBUS); + if (err || amount != 3) + return false; + + return true; +} + +static bool bxm_set_CS_low(struct cgpu_info *bitfury) +{ + char buf[4] = { 0 }; + int err, amount; + + buf[0] = SET_OUT_ADBUS; + buf[1] &= ~DEFAULT_STATE; //Bitmask for LOW_PORT + buf[2] = DEFAULT_DIR; + err = usb_write(bitfury, buf, 3, &amount, C_BXM_CSLOW); + if (err || amount != 3) + return false; + + return true; +} + +static bool bxm_set_CS_high(struct cgpu_info *bitfury) +{ + char buf[4] = { 0 }; + int err, amount; + + buf[0] = SET_OUT_ADBUS; + buf[1] = DEFAULT_STATE; //Bitmask for LOW_PORT + buf[2] = DEFAULT_DIR; + err = usb_write(bitfury, buf, 3, &amount, C_BXM_CSHIGH); + if (err || amount != 3) + return false; + + return true; +} + +static bool bxm_reset_bitfury(struct cgpu_info *bitfury) +{ + char buf[20] = { 0 }; + char rst_buf[8] = {0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00}; + int err, amount; + + //Set the FTDI CS pin HIGH. This will gate the clock to the Bitfury chips so we can send the reset sequence. + if (!bxm_set_CS_high(bitfury)) + return false; + + buf[0] = WRITE_BYTES_SPI0; + buf[1] = (uint8_t)16 - (uint8_t)1; + buf[2] = 0; + memcpy(&buf[3], rst_buf, 8); + memcpy(&buf[11], rst_buf, 8); + err = usb_write(bitfury, buf, 19, &amount, C_BXM_RESET); + if (err || amount != 19) + return false; + + if (!bxm_set_CS_low(bitfury)) + return false; + + return true; +} + +static bool bxm_reinit(struct cgpu_info *bitfury, struct bitfury_info *info) +{ + bool ret; + int i; + + for (i = 0; i < 2; i++) { + spi_clear_buf(info); + spi_add_break(info); + spi_add_fasync(info, i); + spi_set_freq(info); + spi_send_conf(info); + spi_send_init(info); + ret = info->spi_txrx(bitfury, info); + if (!ret) + break; + } + return ret; +} + +static bool bxm_detect_one(struct cgpu_info *bitfury, struct bitfury_info *info) +{ + bool ret; + + info->spi_txrx = &ftdi_spi_txrx; + ret = bxm_open(bitfury); + if (!ret) + goto out; + ret = bxm_purge_buffers(bitfury); + if (!ret) + goto out; + ret = bxm_reset_bitfury(bitfury); + if (!ret) + goto out; + ret = bxm_purge_buffers(bitfury); + if (!ret) + goto out; + + /* Do a dummy read */ + memset(info->spibuf, 0, 80); + info->spibufsz = 80; + ret = info->spi_txrx(bitfury, info); + if (!ret) + goto out; + info->osc6_bits = opt_bxm_bits; + /* Only have 2 chip devices for now */ + info->chips = 2; + nfu_alloc_arrays(info); + + ret = bxm_reinit(bitfury, info); + if (!ret) + goto out; + + if (!add_cgpu(bitfury)) + quit(1, "Failed to add_cgpu in bxm_detect_one"); + + update_usb_stats(bitfury); + applog(LOG_INFO, "%s %d: Successfully initialised %s", + bitfury->drv->name, bitfury->device_id, bitfury->device_path); + spi_clear_buf(info); + + info->total_nonces = 1; +out: + if (!ret) + bxm_close(bitfury, info); + return ret; +} + +static struct cgpu_info *bitfury_detect_one(struct libusb_device *dev, struct usb_find_devices *found) +{ + struct cgpu_info *bitfury; + struct bitfury_info *info; + enum sub_ident ident; + bool ret = false; + + bitfury = usb_alloc_cgpu(&bitfury_drv, 1); + + if (!usb_init(bitfury, dev, found)) + goto out; + applog(LOG_INFO, "%s %d: Found at %s", bitfury->drv->name, + bitfury->device_id, bitfury->device_path); + + info = calloc(sizeof(struct bitfury_info), 1); + if (!info) + quit(1, "Failed to calloc info in bitfury_detect_one"); + bitfury->device_data = info; + info->ident = ident = usb_ident(bitfury); + switch (ident) { + case IDENT_BF1: + ret = bf1_detect_one(bitfury, info); + break; + case IDENT_BXF: + case IDENT_OSM: + ret = bxf_detect_one(bitfury, info); + break; + case IDENT_NFU: + ret = nfu_detect_one(bitfury, info); + break; + case IDENT_BXM: + ret = bxm_detect_one(bitfury, info); + break; + default: + applog(LOG_INFO, "%s %d: Unrecognised bitfury device", + bitfury->drv->name, bitfury->device_id); + break; + } + + if (!ret) { + free(info); + usb_uninit(bitfury); +out: + bitfury = usb_free_cgpu(bitfury); + } + return bitfury; +} + +static void bitfury_detect(bool __maybe_unused hotplug) +{ + usb_detect(&bitfury_drv, bitfury_detect_one); +} + +static void adjust_bxf_chips(struct cgpu_info *bitfury, struct bitfury_info *info, int chip) +{ + int chips = chip + 1; + size_t old, new; + + if (likely(chips <= info->chips)) + return; + if (chips > 999) + return; + old = sizeof(int) * info->chips; + new = sizeof(int) * chips; + applog(LOG_INFO, "%s %d: Adjust chip size to %d", bitfury->drv->name, bitfury->device_id, + chips); + + recalloc(info->filtered_hw, old, new); + recalloc(info->job, old, new); + recalloc(info->submits, old, new); + if (info->chips == 2 && chips <= 6 && info->ident == IDENT_BXF) + bitfury->drv->name = "HXF"; + else if (info->chips <= 6 && chips > 6 && info->ident == IDENT_BXF) + bitfury->drv->name = "MXF"; + info->chips = chips; +} + +static void parse_bxf_submit(struct cgpu_info *bitfury, struct bitfury_info *info, char *buf) +{ + struct work *match_work, *tmp, *work = NULL; + struct thr_info *thr = info->thr; + uint32_t nonce, timestamp; + int workid, chip = -1; + + if (!sscanf(&buf[7], "%x %x %x %d", &nonce, &workid, ×tamp, &chip)) { + applog(LOG_WARNING, "%s %d: Failed to parse submit response", + bitfury->drv->name, bitfury->device_id); + return; + } + adjust_bxf_chips(bitfury, info, chip); + if (unlikely(chip >= info->chips || chip < 0)) { + applog(LOG_INFO, "%s %d: Invalid submit chip number %d", + bitfury->drv->name, bitfury->device_id, chip); + } else + info->submits[chip]++; + + applog(LOG_DEBUG, "%s %d: Parsed nonce %u workid %d timestamp %u", + bitfury->drv->name, bitfury->device_id, nonce, workid, timestamp); + + rd_lock(&bitfury->qlock); + HASH_ITER(hh, bitfury->queued_work, match_work, tmp) { + if (match_work->subid == workid) { + work = copy_work(match_work); + break; + } + } + rd_unlock(&bitfury->qlock); + + if (!work) { + /* Discard first results from any previous run */ + if (unlikely(!info->valid)) + return; + + applog(LOG_INFO, "%s %d: No matching work", bitfury->drv->name, bitfury->device_id); + + mutex_lock(&info->lock); + info->no_matching_work++; + mutex_unlock(&info->lock); + + inc_hw_errors(thr); + return; + } + /* Set the device start time from when we first get valid results */ + if (unlikely(!info->valid)) { + info->valid = true; + cgtime(&bitfury->dev_start_tv); + } + set_work_ntime(work, timestamp); + if (submit_nonce(thr, work, nonce)) { + mutex_lock(&info->lock); + info->nonces++; + mutex_unlock(&info->lock); + } + free_work(work); +} + +static bool bxf_send_clock(struct cgpu_info *bitfury, struct bitfury_info *info, + uint8_t clockspeed) +{ + char buf[64]; + + info->clocks = clockspeed; + sprintf(buf, "clock %d %d\n", clockspeed, clockspeed); + return bxf_send_msg(bitfury, buf, C_BXF_CLOCK); +} + +static void parse_bxf_temp(struct cgpu_info *bitfury, struct bitfury_info *info, char *buf) +{ + uint8_t clockspeed = info->clocks; + int decitemp; + + if (!sscanf(&buf[5], "%d", &decitemp)) { + applog(LOG_INFO, "%s %d: Failed to parse temperature", + bitfury->drv->name, bitfury->device_id); + return; + } + + mutex_lock(&info->lock); + bitfury->temp = (double)decitemp / 10; + if (decitemp > info->max_decitemp) { + info->max_decitemp = decitemp; + applog(LOG_DEBUG, "%s %d: New max decitemp %d", bitfury->drv->name, + bitfury->device_id, decitemp); + } + mutex_unlock(&info->lock); + + if (decitemp > info->temp_target + BXF_TEMP_HYSTERESIS) { + if (info->clocks <= BXF_CLOCK_MIN) + goto out; + applog(LOG_WARNING, "%s %d: Hit overheat temperature of %d, throttling!", + bitfury->drv->name, bitfury->device_id, decitemp); + clockspeed = BXF_CLOCK_MIN; + goto out; + } + if (decitemp > info->temp_target) { + if (info->clocks <= BXF_CLOCK_MIN) + goto out; + if (decitemp < info->last_decitemp) + goto out; + applog(LOG_INFO, "%s %d: Temp %d over target and not falling, decreasing clock", + bitfury->drv->name, bitfury->device_id, decitemp); + clockspeed = info->clocks - 1; + goto out; + } + if (decitemp <= info->temp_target && decitemp >= info->temp_target - BXF_TEMP_HYSTERESIS) { + if (decitemp == info->last_decitemp) + goto out; + if (decitemp > info->last_decitemp) { + if (info->clocks <= BXF_CLOCK_MIN) + goto out; + applog(LOG_DEBUG, "%s %d: Temp %d in target and rising, decreasing clock", + bitfury->drv->name, bitfury->device_id, decitemp); + clockspeed = info->clocks - 1; + goto out; + } + /* implies: decitemp < info->last_decitemp */ + if (info->clocks >= opt_bxf_bits) + goto out; + applog(LOG_DEBUG, "%s %d: Temp %d in target and falling, increasing clock", + bitfury->drv->name, bitfury->device_id, decitemp); + clockspeed = info->clocks + 1; + goto out; + } + /* implies: decitemp < info->temp_target - BXF_TEMP_HYSTERESIS */ + if (info->clocks >= opt_bxf_bits) + goto out; + applog(LOG_DEBUG, "%s %d: Temp %d below target, increasing clock", + bitfury->drv->name, bitfury->device_id, decitemp); + clockspeed = info->clocks + 1; +out: + bxf_send_clock(bitfury, info, clockspeed); + info->last_decitemp = decitemp; +} + +static void bxf_update_work(struct cgpu_info *bitfury, struct bitfury_info *info); + +static void parse_bxf_needwork(struct cgpu_info *bitfury, struct bitfury_info *info, + char *buf) +{ + int needed; + + if (!sscanf(&buf[9], "%d", &needed)) { + applog(LOG_INFO, "%s %d: Failed to parse needwork", + bitfury->drv->name, bitfury->device_id); + return; + } + while (needed-- > 0) + bxf_update_work(bitfury, info); +} + +static void parse_bxf_job(struct cgpu_info *bitfury, struct bitfury_info *info, char *buf) +{ + int job_id, timestamp, chip; + + if (sscanf(&buf[4], "%x %x %x", &job_id, ×tamp, &chip) != 3) { + applog(LOG_INFO, "%s %d: Failed to parse job", + bitfury->drv->name, bitfury->device_id); + return; + } + adjust_bxf_chips(bitfury, info, chip); + if (chip >= info->chips || chip < 0) { + applog(LOG_INFO, "%s %d: Invalid job chip number %d", + bitfury->drv->name, bitfury->device_id, chip); + return; + } + ++info->job[chip]; +} + +static void parse_bxf_hwerror(struct cgpu_info *bitfury, struct bitfury_info *info, char *buf) +{ + int chip; + + if (!sscanf(&buf[8], "%d", &chip)) { + applog(LOG_INFO, "%s %d: Failed to parse hwerror", + bitfury->drv->name, bitfury->device_id); + return; + } + adjust_bxf_chips(bitfury, info, chip); + if (chip >= info->chips || chip < 0) { + applog(LOG_INFO, "%s %d: Invalid hwerror chip number %d", + bitfury->drv->name, bitfury->device_id, chip); + return; + } + ++info->filtered_hw[chip]; +} + +#define PARSE_BXF_MSG(MSG) \ + msg = strstr(buf, #MSG); \ + if (msg) { \ + parse_bxf_##MSG(bitfury, info, msg); \ + continue; \ + } + +static void *bxf_get_results(void *userdata) +{ + struct cgpu_info *bitfury = userdata; + struct bitfury_info *info = bitfury->device_data; + char threadname[24], buf[512]; + + snprintf(threadname, 24, "bxf_recv/%d", bitfury->device_id); + + /* We operate the device at lowest diff since it's not a lot of results + * to process and gives us a better indicator of the nonce return rate + * and hardware errors. */ + sprintf(buf, "target ffffffff\n"); + if (!bxf_send_msg(bitfury, buf, C_BXF_TARGET)) + goto out; + + /* Read thread sends the first work item to get the device started + * since it will roll ntime and make work itself from there on. */ + bxf_update_work(bitfury, info); + bxf_update_work(bitfury, info); + + while (likely(!bitfury->shutdown)) { + char *msg, *strbuf; + int err; + + if (unlikely(bitfury->usbinfo.nodev)) + break; + + err = bxf_recv_msg(bitfury, buf); + if (err < 0) { + if (err != LIBUSB_ERROR_TIMEOUT) + break; + continue; + } + if (!err) + continue; + + if (opt_bxf_debug) { + strbuf = str_text(buf); + applog(LOG_ERR, "%s %d: < [%s]", + bitfury->drv->name, bitfury->device_id, strbuf); + free(strbuf); + } + + PARSE_BXF_MSG(submit); + PARSE_BXF_MSG(temp); + PARSE_BXF_MSG(needwork); + PARSE_BXF_MSG(job); + PARSE_BXF_MSG(hwerror); + + if (buf[0] != '#') { + strbuf = str_text(buf); + applog(LOG_DEBUG, "%s %d: Unrecognised string %s", + bitfury->drv->name, bitfury->device_id, strbuf); + free(strbuf); + } + } +out: + return NULL; +} + +static bool bxf_prepare(struct cgpu_info *bitfury, struct bitfury_info *info) +{ + bxf_send_ledmode(bitfury); + bxf_send_debugmode(bitfury); + + mutex_init(&info->lock); + if (pthread_create(&info->read_thr, NULL, bxf_get_results, (void *)bitfury)) + quit(1, "Failed to create bxf read_thr"); + + return bxf_send_clock(bitfury, info, opt_bxf_bits); +} + +static bool bitfury_prepare(struct thr_info *thr) +{ + struct cgpu_info *bitfury = thr->cgpu; + struct bitfury_info *info = bitfury->device_data; + + info->thr = thr; + + switch(info->ident) { + case IDENT_BXF: + case IDENT_OSM: + return bxf_prepare(bitfury, info); + break; + case IDENT_BF1: + default: + return true; + } +} + +static int64_t bitfury_rate(struct bitfury_info *info) +{ + double nonce_rate; + int64_t ret = 0; + + info->cycles++; + info->total_nonces += info->nonces; + info->saved_nonces += info->nonces; + info->nonces = 0; + nonce_rate = (double)info->total_nonces / (double)info->cycles; + if (info->saved_nonces >= nonce_rate) { + info->saved_nonces -= nonce_rate; + ret = (double)0xffffffff * nonce_rate; + } + return ret; +} + +static int64_t bf1_scan(struct thr_info *thr, struct cgpu_info *bitfury, + struct bitfury_info *info) +{ + int amount, i, aged, total = 0, ms_diff; + char readbuf[512], buf[45]; + struct work *work, *tmp; + struct timeval tv_now; + int64_t ret = 0; + + work = get_queue_work(thr, bitfury, thr->id); + if (unlikely(thr->work_restart)) { + work_completed(bitfury, work); + goto out; + } + + buf[0] = 'W'; + memcpy(buf + 1, work->midstate, 32); + memcpy(buf + 33, work->data + 64, 12); + + /* New results may spill out from the latest work, making us drop out + * too early so read whatever we get for the first half nonce and then + * look for the results to prev work. */ + cgtime(&tv_now); + ms_diff = 600 - ms_tdiff(&tv_now, &info->tv_start); + if (ms_diff > 0) { + usb_read_timeout_cancellable(bitfury, readbuf, 512, &amount, ms_diff, + C_BF1_GETRES); + total += amount; + } + + /* Now look for the bulk of the previous work results, they will come + * in a batch following the first data. */ + cgtime(&tv_now); + ms_diff = BF1WAIT - ms_tdiff(&tv_now, &info->tv_start); + /* If a work restart was sent, just empty the buffer. */ + if (unlikely(ms_diff < 10 || thr->work_restart)) + ms_diff = 10; + usb_read_once_timeout_cancellable(bitfury, readbuf + total, BF1MSGSIZE, + &amount, ms_diff, C_BF1_GETRES); + total += amount; + while (amount) { + usb_read_once_timeout(bitfury, readbuf + total, 512 - total, &amount, 10, + C_BF1_GETRES); + total += amount; + }; + + /* Don't send whatever work we've stored if we got a restart */ + if (unlikely(thr->work_restart)) + goto out; + + /* Send work */ + cgtime(&work->tv_work_start); + usb_write(bitfury, buf, 45, &amount, C_BF1_REQWORK); + cgtime(&info->tv_start); + + /* Get response acknowledging work */ + usb_read(bitfury, buf, BF1MSGSIZE, &amount, C_BF1_GETWORK); + +out: + /* Search for what work the nonce matches in order of likelihood. Last + * entry is end of result marker. */ + for (i = 0; i < total - BF1MSGSIZE; i += BF1MSGSIZE) { + bool found = false; + uint32_t nonce; + + /* Ignore state & switched data in results for now. */ + memcpy(&nonce, readbuf + i + 3, 4); + nonce = decnonce(nonce); + + rd_lock(&bitfury->qlock); + HASH_ITER(hh, bitfury->queued_work, work, tmp) { + if (bitfury_checkresults(thr, work, nonce)) { + info->nonces++; + found = true; + break; + } + } + rd_unlock(&bitfury->qlock); + + if (!found) { + if (likely(info->valid)) + inc_hw_errors(thr); + } else if (unlikely(!info->valid)) { + info->valid = true; + cgtime(&bitfury->dev_start_tv); + } + } + + cgtime(&tv_now); + + /* This iterates over the hashlist finding work started more than 6 + * seconds ago. */ + aged = age_queued_work(bitfury, 6.0); + if (aged) { + applog(LOG_DEBUG, "%s %d: Aged %d work items", bitfury->drv->name, + bitfury->device_id, aged); + } + + ret = bitfury_rate(info); + + if (unlikely(bitfury->usbinfo.nodev)) { + applog(LOG_WARNING, "%s %d: Device disappeared, disabling thread", + bitfury->drv->name, bitfury->device_id); + ret = -1; + } + return ret; +} + +static int64_t bxf_scan(struct cgpu_info *bitfury, struct bitfury_info *info) +{ + int ms, aged; + int64_t ret; + + bxf_update_work(bitfury, info); + ms = 1200 / info->chips; + if (ms < 100) + ms = 100; + cgsleep_ms(ms); + + mutex_lock(&info->lock); + ret = bitfury_rate(info); + mutex_unlock(&info->lock); + + /* Keep no more than the last 90 seconds worth of work items in the + * hashlist */ + aged = age_queued_work(bitfury, 90.0); + if (aged) { + applog(LOG_DEBUG, "%s %d: Aged %d work items", bitfury->drv->name, + bitfury->device_id, aged); + } + + if (unlikely(bitfury->usbinfo.nodev)) { + applog(LOG_WARNING, "%s %d: Device disappeared, disabling thread", + bitfury->drv->name, bitfury->device_id); + ret = -1; + } + return ret; +} + +static void bitfury_check_work(struct thr_info *thr, struct cgpu_info *bitfury, + struct bitfury_info *info, int chip_n) +{ + if (!info->work[chip_n]) { + info->work[chip_n] = get_work(thr, thr->id); + if (unlikely(thr->work_restart)) { + free_work(info->work[chip_n]); + info->work[chip_n] = NULL; + return; + } + bitfury_work_to_payload(&info->payload[chip_n], info->work[chip_n]); + } + + if (unlikely(bitfury->usbinfo.nodev)) + return; + + if (!libbitfury_sendHashData(thr, bitfury, info, chip_n)) + usb_nodev(bitfury); + + if (info->job_switched[chip_n]) { + if (likely(info->owork[chip_n])) + free_work(info->owork[chip_n]); + info->owork[chip_n] = info->work[chip_n]; + info->work[chip_n] = NULL; + } + +} + +static int64_t nfu_scan(struct thr_info *thr, struct cgpu_info *bitfury, + struct bitfury_info *info) +{ + int64_t ret = 0; + int i; + + for (i = 0; i < info->chips; i++) + bitfury_check_work(thr, bitfury, info, i); + + ret = bitfury_rate(info); + + if (unlikely(bitfury->usbinfo.nodev)) { + applog(LOG_WARNING, "%s %d: Device disappeared, disabling thread", + bitfury->drv->name, bitfury->device_id); + ret = -1; + } + + return ret; +} + +static int64_t bitfury_scanwork(struct thr_info *thr) +{ + struct cgpu_info *bitfury = thr->cgpu; + struct bitfury_info *info = bitfury->device_data; + int64_t ret = -1; + + if (unlikely(share_work_tdiff(bitfury) > 60)) { + if (info->failing) { + if (share_work_tdiff(bitfury) > 120) { + applog(LOG_ERR, "%s %d: Device failed to respond to restart", + bitfury->drv->name, bitfury->device_id); + return ret; + } + } else { + applog(LOG_WARNING, "%s %d: No valid hashes for over 1 minute, attempting to reset", + bitfury->drv->name, bitfury->device_id); + usb_reset(bitfury); + info->failing = true; + } + } + + if (unlikely(bitfury->usbinfo.nodev)) + return ret; + + switch(info->ident) { + case IDENT_BF1: + ret = bf1_scan(thr, bitfury, info); + break; + case IDENT_BXF: + case IDENT_OSM: + ret = bxf_scan(bitfury, info); + break; + case IDENT_NFU: + case IDENT_BXM: + ret = nfu_scan(thr, bitfury, info); + break; + default: + ret = 0; + break; + } + if (ret > 0) + info->failing = false; + return ret; +} + +static void bxf_send_maxroll(struct cgpu_info *bitfury, int maxroll) +{ + char buf[20]; + + sprintf(buf, "maxroll %d\n", maxroll); + bxf_send_msg(bitfury, buf, C_BXF_MAXROLL); +} + +static bool bxf_send_work(struct cgpu_info *bitfury, struct work *work) +{ + char buf[512], hexwork[156]; + + __bin2hex(hexwork, work->data, 76); + sprintf(buf, "work %s %x\n", hexwork, work->subid); + return bxf_send_msg(bitfury, buf, C_BXF_WORK); +} + +static void bxf_update_work(struct cgpu_info *bitfury, struct bitfury_info *info) +{ + struct thr_info *thr = info->thr; + struct work *work; + + if (unlikely(bitfury->usbinfo.nodev)) + return; + + work = get_queue_work(thr, bitfury, thr->id); + if (work->drv_rolllimit != info->maxroll) { + info->maxroll = work->drv_rolllimit; + bxf_send_maxroll(bitfury, info->maxroll); + } + + mutex_lock(&info->lock); + work->subid = ++info->work_id; + mutex_unlock(&info->lock); + + cgtime(&work->tv_work_start); + bxf_send_work(bitfury, work); +} + +static void bitfury_flush_work(struct cgpu_info *bitfury) +{ + struct bitfury_info *info = bitfury->device_data; + + switch(info->ident) { + case IDENT_BXF: + case IDENT_OSM: + bxf_send_flush(bitfury); + bxf_update_work(bitfury, info); + bxf_update_work(bitfury, info); + case IDENT_BF1: + default: + break; + } +} + +static void bitfury_update_work(struct cgpu_info *bitfury) +{ + struct bitfury_info *info = bitfury->device_data; + + switch(info->ident) { + case IDENT_BXF: + case IDENT_OSM: + bxf_update_work(bitfury, info); + case IDENT_BF1: + default: + break; + } +} + +static struct api_data *bf1_api_stats(struct bitfury_info *info) +{ + struct api_data *root = NULL; + double nonce_rate; + char serial[16]; + int version; + + version = info->version; + root = api_add_int(root, "Version", &version, true); + root = api_add_string(root, "Product", info->product, false); + sprintf(serial, "%08x", info->serial); + root = api_add_string(root, "Serial", serial, true); + nonce_rate = (double)info->total_nonces / (double)info->cycles; + root = api_add_double(root, "NonceRate", &nonce_rate, true); + + return root; +} + +static struct api_data *bxf_api_stats(struct cgpu_info *bitfury, struct bitfury_info *info) +{ + struct api_data *root = NULL; + double nonce_rate; + char buf[32]; + int i; + + sprintf(buf, "%d.%d", info->ver_major, info->ver_minor); + root = api_add_string(root, "Version", buf, true); + root = api_add_int(root, "Revision", &info->hw_rev, false); + root = api_add_int(root, "Chips", &info->chips, false); + nonce_rate = (double)info->total_nonces / (double)info->cycles; + root = api_add_double(root, "NonceRate", &nonce_rate, true); + root = api_add_int(root, "NoMatchingWork", &info->no_matching_work, false); + root = api_add_double(root, "Temperature", &bitfury->temp, false); + root = api_add_int(root, "Max DeciTemp", &info->max_decitemp, false); + root = api_add_uint8(root, "Clock", &info->clocks, false); + for (i = 0; i < info->chips; i++) { + sprintf(buf, "Core%d hwerror", i); + root = api_add_int(root, buf, &info->filtered_hw[i], false); + sprintf(buf, "Core%d jobs", i); + root = api_add_int(root, buf, &info->job[i], false); + sprintf(buf, "Core%d submits", i); + root = api_add_int(root, buf, &info->submits[i], false); + } + + return root; +} + +static struct api_data *nfu_api_stats(struct bitfury_info *info) +{ + struct api_data *root = NULL; + char buf[32]; + int i; + + root = api_add_int(root, "Chips", &info->chips, false); + for (i = 0; i < info->chips; i++) { + sprintf(buf, "Core%d submits", i); + root = api_add_int(root, buf, &info->submits[i], false); + } + return root; +} + +static struct api_data *bitfury_api_stats(struct cgpu_info *cgpu) +{ + struct bitfury_info *info = cgpu->device_data; + + switch(info->ident) { + case IDENT_BF1: + return bf1_api_stats(info); + break; + case IDENT_BXF: + case IDENT_OSM: + return bxf_api_stats(cgpu, info); + break; + case IDENT_NFU: + case IDENT_BXM: + return nfu_api_stats(info); + break; + default: + break; + } + return NULL; +} + +static void bitfury_get_statline_before(char *buf, size_t bufsiz, struct cgpu_info *cgpu) +{ + struct bitfury_info *info = cgpu->device_data; + + switch(info->ident) { + case IDENT_BXF: + case IDENT_OSM: + tailsprintf(buf, bufsiz, "%5.1fC", cgpu->temp); + break; + default: + break; + } +} + +static void bf1_init(struct cgpu_info *bitfury) +{ + bf1_close(bitfury); + bf1_open(bitfury); + bf1_reset(bitfury); +} + +static void bitfury_init(struct cgpu_info *bitfury) +{ + struct bitfury_info *info = bitfury->device_data; + + switch(info->ident) { + case IDENT_BF1: + bf1_init(bitfury); + break; + default: + break; + } +} + +static void bxf_close(struct bitfury_info *info) +{ + pthread_join(info->read_thr, NULL); + mutex_destroy(&info->lock); +} + +static void bitfury_shutdown(struct thr_info *thr) +{ + struct cgpu_info *bitfury = thr->cgpu; + struct bitfury_info *info = bitfury->device_data; + + switch(info->ident) { + case IDENT_BF1: + bf1_close(bitfury); + break; + case IDENT_BXF: + case IDENT_OSM: + bxf_close(info); + break; + case IDENT_NFU: + nfu_close(bitfury); + break; + case IDENT_BXM: + bxm_close(bitfury, info); + break; + default: + break; + } + usb_nodev(bitfury); +} + +/* Currently hardcoded to BF1 devices */ +struct device_drv bitfury_drv = { + .drv_id = DRIVER_bitfury, + .dname = "bitfury", + .name = "BF1", + .drv_detect = bitfury_detect, + .thread_prepare = bitfury_prepare, + .hash_work = &hash_driver_work, + .scanwork = bitfury_scanwork, + .flush_work = bitfury_flush_work, + .update_work = bitfury_update_work, + .get_api_stats = bitfury_api_stats, + .get_statline_before = bitfury_get_statline_before, + .reinit_device = bitfury_init, + .thread_shutdown = bitfury_shutdown, + .identify_device = bitfury_identify +}; diff --git a/driver-bitfury.h b/driver-bitfury.h new file mode 100644 index 0000000..6cbbc0a --- /dev/null +++ b/driver-bitfury.h @@ -0,0 +1,116 @@ +/* + * Copyright 2013-2014 Con Kolivas + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#ifndef BITFURY_H +#define BITFURY_H + +#include "miner.h" +#include "usbutils.h" +#include "mcp2210.h" + +#define BXF_CLOCK_OFF 0 +#define BXF_CLOCK_MIN 32 +#define BXF_CLOCK_MAX 63 // Not really used since we only get hw errors above default + +/* In tenths of a degree */ +#define BXF_TEMP_TARGET 820 +#define BXF_TEMP_HYSTERESIS 30 + +extern int opt_bxf_temp_target; +extern int opt_nfu_bits; +extern int opt_bxm_bits; +extern int opt_bxf_bits; +extern int opt_bxf_debug; +extern int opt_osm_led_mode; + +#define NFU_PIN_LED 0 +#define NFU_PIN_SCK_OVR 5 +#define NFU_PIN_PWR_EN 6 +#define NFU_PIN_PWR_EN0 7 + +#define SPIBUF_SIZE 16384 +#define BITFURY_REFRESH_DELAY 100 + +#define SIO_RESET_REQUEST 0 +#define SIO_SET_LATENCY_TIMER_REQUEST 0x09 +#define SIO_SET_EVENT_CHAR_REQUEST 0x06 +#define SIO_SET_ERROR_CHAR_REQUEST 0x07 +#define SIO_SET_BITMODE_REQUEST 0x0B +#define SIO_RESET_PURGE_RX 1 +#define SIO_RESET_PURGE_TX 2 + +#define BITMODE_RESET 0x00 +#define BITMODE_MPSSE 0x02 +#define SIO_RESET_SIO 0 + +#define BXM_LATENCY_MS 2 + +struct bitfury_payload { + unsigned char midstate[32]; + unsigned int junk[8]; + unsigned m7; + unsigned ntime; + unsigned nbits; + unsigned nnonce; +}; + +struct bitfury_info { + struct cgpu_info *base_cgpu; + struct thr_info *thr; + enum sub_ident ident; + int nonces; + int total_nonces; + double saved_nonces; + int cycles; + bool valid; /* Set on first valid data being found */ + bool failing; /* Set when an attempted restart has been sent */ + + int chips; + char product[8]; + + /* BF1 specific data */ + uint8_t version; + uint32_t serial; + struct timeval tv_start; + + /* BXF specific data */ + pthread_mutex_t lock; + pthread_t read_thr; + int last_decitemp; + int max_decitemp; + int temp_target; + int work_id; // Current work->subid + int no_matching_work; + int maxroll; // Last maxroll sent to device + int ver_major; + int ver_minor; + int hw_rev; + uint8_t clocks; // There are two but we set them equal + int *filtered_hw; // Hardware errors we're told about but are filtered + int *job; // Completed jobs we're told about + int *submits; // Submitted responses + + /* NFU specific data */ + struct mcp_settings mcp; + char spibuf[SPIBUF_SIZE]; + unsigned int spibufsz; + int osc6_bits; + + /* Chip sized arrays */ + struct bitfury_payload *payload; + unsigned int *oldbuf; // 17 vals per chip + bool *job_switched; + bool *second_run; + struct work **work; + struct work **owork; + + bool (*spi_txrx)(struct cgpu_info *, struct bitfury_info *info); +}; + +#endif /* BITFURY_H */ diff --git a/driver-bitmain.c b/driver-bitmain.c new file mode 100644 index 0000000..0b794dc --- /dev/null +++ b/driver-bitmain.c @@ -0,0 +1,2463 @@ +/* + * Copyright 2012-2013 Lingchao Xu + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#ifndef WIN32 + #include + #include + #include + #include + #ifndef O_CLOEXEC + #define O_CLOEXEC 0 + #endif +#else + #include "compat.h" + #include + #include +#endif + +#include "elist.h" +#include "miner.h" +#include "usbutils.h" +#include "driver-bitmain.h" +#include "hexdump.c" +#include "util.h" + +#define BITMAIN_CALC_DIFF1 1 + +#ifdef WIN32 +#define BITMAIN_TEST +#endif + +#define BITMAIN_TEST_PRINT_WORK 0 +#ifdef BITMAIN_TEST +#define BITMAIN_TEST_NUM 19 +#define BITMAIN_TEST_USENUM 1 +int g_test_index = 0; +const char btm_work_test_data[BITMAIN_TEST_NUM][256] = { + "00000002ddc1ce5579dbec17f17fbb8f31ae218a814b2a0c1900f0d90000000100000000b58aa6ca86546b07a5a46698f736c7ca9c0eedc756d8f28ac33c20cc24d792675276f879190afc85b6888022000000800000000000000000000000000000000000000000000000000000000000000000", + "0000000256ccc4c8aeae2b1e41490bc352893605f284e4be043f7b190000000000000000eb2d45233c5b02de50ddcb9049ba16040e0ba00e9750a474eec75891571d925b52dfda4a190266667145b02f000000800000000000000000000000000000000000000000000000000000000000000000", + "0000000256ccc4c8aeae2b1e41490bc352893605f284e4be043f7b19000000000000000090c7d3743e0b0562e4f56d3dd35cece3c5e8275d0abb21bf7e503cb72bd7ed3b52dfda4a190266667bbb58d7000000800000000000000000000000000000000000000000000000000000000000000000", + "0000000256ccc4c8aeae2b1e41490bc352893605f284e4be043f7b1900000000000000006e0561da06022bfbb42c5ecd74a46bfd91934f201b777e9155cc6c3674724ec652dfda4a19026666a0cd827b000000800000000000000000000000000000000000000000000000000000000000000000", + "0000000256ccc4c8aeae2b1e41490bc352893605f284e4be043f7b1900000000000000000312f42ce4964cc23f2d8c039f106f25ddd58e10a1faed21b3bba4b0e621807b52dfda4a1902666629c9497d000000800000000000000000000000000000000000000000000000000000000000000000", + "0000000256ccc4c8aeae2b1e41490bc352893605f284e4be043f7b19000000000000000033093a6540dbe8f7f3d19e3d2af05585ac58dafad890fa9a942e977334a23d6e52dfda4a190266665ae95079000000800000000000000000000000000000000000000000000000000000000000000000", + "0000000256ccc4c8aeae2b1e41490bc352893605f284e4be043f7b190000000000000000bd7893057d06e69705bddf9a89c7bac6b40c5b32f15e2295fc8c5edf491ea24952dfda4a190266664b89b4d3000000800000000000000000000000000000000000000000000000000000000000000000", + "0000000256ccc4c8aeae2b1e41490bc352893605f284e4be043f7b19000000000000000075e66f533e53837d14236a793ee4e493985642bc39e016b9e63adf14a584a2aa52dfda4a19026666ab5d638d000000800000000000000000000000000000000000000000000000000000000000000000", + "0000000256ccc4c8aeae2b1e41490bc352893605f284e4be043f7b190000000000000000d936f90c5db5f0fe1d017344443854fbf9e40a07a9b7e74fedc8661c23162bff52dfda4a19026666338e79cb000000800000000000000000000000000000000000000000000000000000000000000000", + "0000000256ccc4c8aeae2b1e41490bc352893605f284e4be043f7b190000000000000000d2c1a7d279a4355b017bc0a4b0a9425707786729f21ee18add3fda4252a31a4152dfda4a190266669bc90806000000800000000000000000000000000000000000000000000000000000000000000000", + "0000000256ccc4c8aeae2b1e41490bc352893605f284e4be043f7b190000000000000000ad36d19f33d04ca779942843890bc3b083cec83a4b60b6c45cf7d21fc187746552dfda4a1902666675d81ab7000000800000000000000000000000000000000000000000000000000000000000000000", + "0000000256ccc4c8aeae2b1e41490bc352893605f284e4be043f7b19000000000000000093b809cf82b76082eacb55bc35b79f31882ed0976fd102ef54783cd24341319b52dfda4a1902666642ab4e42000000800000000000000000000000000000000000000000000000000000000000000000", + "0000000256ccc4c8aeae2b1e41490bc352893605f284e4be043f7b1900000000000000007411ff315430a7bbf41de8a685d457e82d5177c05640d6a4436a40f39e99667852dfda4a190266662affa4b5000000800000000000000000000000000000000000000000000000000000000000000000", + "0000000256ccc4c8aeae2b1e41490bc352893605f284e4be043f7b1900000000000000001ad0db5b9e1e2b57c8d3654c160f5a51067521eab7e340a270639d97f00a3fa252dfda4a1902666601a47bb6000000800000000000000000000000000000000000000000000000000000000000000000", + "0000000256ccc4c8aeae2b1e41490bc352893605f284e4be043f7b19000000000000000022e055c442c46bbe16df68603a26891f6e4cf85b90102b39fd7cadb602b4e34552dfda4a1902666695d33cea000000800000000000000000000000000000000000000000000000000000000000000000", + "0000000256ccc4c8aeae2b1e41490bc352893605f284e4be043f7b1900000000000000009c8baf5a8a1e16de2d6ae949d5fec3ed751f10dcd4c99810f2ce08040fb9e31d52dfda4a19026666fe78849d000000800000000000000000000000000000000000000000000000000000000000000000", + "0000000256ccc4c8aeae2b1e41490bc352893605f284e4be043f7b190000000000000000e5655532b414887f35eb4652bc7b11ebac12891f65bc08cbe0ce5b277b9e795152dfda4a19026666fcc0d1d1000000800000000000000000000000000000000000000000000000000000000000000000", + "0000000256ccc4c8aeae2b1e41490bc352893605f284e4be043f7b190000000000000000f272c5508704e2b62dd1c30ea970372c40bf00f9203f9bf69d456b4a7fbfffe352dfda4a19026666c03d4399000000800000000000000000000000000000000000000000000000000000000000000000", + "0000000256ccc4c8aeae2b1e41490bc352893605f284e4be043f7b190000000000000000fca3b4531ba627ad9b0e23cdd84c888952c23810df196e9c6db0bcecba6a830952dfda4a19026666c14009cb000000800000000000000000000000000000000000000000000000000000000000000000" +}; +const char btm_work_test_midstate[BITMAIN_TEST_NUM][256] = { + "2d8738e7f5bcf76dcb8316fec772e20e240cd58c88d47f2d3f5a6a9547ed0a35", + "d31b6ce09c0bfc2af6f3fe3a03475ebefa5aa191fa70a327a354b2c22f9692f1", + "84a8c8224b80d36caeb42eff2a100f634e1ff873e83fd02ef1306a34abef9dbe", + "059882159439b9b32968c79a93c5521e769dbea9d840f56c2a17b9ad87e530b8", + "17fa435d05012574f8f1da26994cc87b6cb9660b5e82072dc6a0881cec150a0d", + "92a28cc5ec4ba6a2688471dfe2032b5fe97c805ca286c503e447d6749796c6af", + "1677a03516d6e9509ac37e273d2482da9af6e077abe8392cdca6a30e916a7ae9", + "50bbe09f1b8ac18c97aeb745d5d2c3b5d669b6ac7803e646f65ac7b763a392d1", + "e46a0022ebdc303a7fb1a0ebfa82b523946c312e745e5b8a116b17ae6b4ce981", + "8f2f61e7f5b4d76d854e6d266acfff4d40347548216838ccc4ef3b9e43d3c9ea", + "0a450588ae99f75d676a08d0326e1ea874a3497f696722c78a80c7b6ee961ea6", + "3c4c0fc2cf040b806c51b46de9ec0dcc678a7cc5cf3eff11c6c03de3bc7818cc", + "f6c7c785ab5daddb8f98e5f854f2cb41879fcaf47289eb2b4196fefc1b28316f", + "005312351ccb0d0794779f5023e4335b5cad221accf0dfa3da7b881266fa9f5a", + "7b26d189c6bba7add54143179aadbba7ccaeff6887bd8d5bec9597d5716126e6", + "a4718f4c801e7ddf913a9474eb71774993525684ffea1915f767ab16e05e6889", + "6b6226a8c18919d0e55684638d33a6892a00d22492cc2f5906ca7a4ac21c74a7", + "383114dccd1cb824b869158aa2984d157fcb02f46234ceca65943e919329e697", + "d4d478df3016852b27cb1ae9e1e98d98617f8d0943bf9dc1217f47f817236222" +}; +#endif + +char opt_bitmain_dev[256] = {0}; +bool opt_bitmain_hwerror = false; +bool opt_bitmain_checkall = false; +bool opt_bitmain_checkn2diff = false; +bool opt_bitmain_dev_usb = true; +bool opt_bitmain_nobeeper = false; +bool opt_bitmain_notempoverctrl = false; +bool opt_bitmain_homemode = false; +int opt_bitmain_temp = BITMAIN_TEMP_TARGET; +int opt_bitmain_overheat = BITMAIN_TEMP_OVERHEAT; +int opt_bitmain_fan_min = BITMAIN_DEFAULT_FAN_MIN_PWM; +int opt_bitmain_fan_max = BITMAIN_DEFAULT_FAN_MAX_PWM; +int opt_bitmain_freq_min = BITMAIN_MIN_FREQUENCY; +int opt_bitmain_freq_max = BITMAIN_MAX_FREQUENCY; +bool opt_bitmain_auto; + +static int option_offset = -1; + +// -------------------------------------------------------------- +// CRC16 check table +// -------------------------------------------------------------- +const uint8_t chCRCHTalbe[] = // CRC high byte table +{ + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, + 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, + 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40 +}; + +const uint8_t chCRCLTalbe[] = // CRC low byte table +{ + 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, + 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, + 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, + 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, + 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, + 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, + 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, + 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, + 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, + 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, + 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, + 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, + 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, + 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, + 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, + 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, + 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, + 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, + 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, + 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C, + 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, + 0x41, 0x81, 0x80, 0x40 +}; + +static uint16_t CRC16(const uint8_t* p_data, uint16_t w_len) +{ + uint8_t chCRCHi = 0xFF; // CRC high byte initialize + uint8_t chCRCLo = 0xFF; // CRC low byte initialize + uint16_t wIndex = 0; // CRC cycling index + + while (w_len--) { + wIndex = chCRCLo ^ *p_data++; + chCRCLo = chCRCHi ^ chCRCHTalbe[wIndex]; + chCRCHi = chCRCLTalbe[wIndex]; + } + return ((chCRCHi << 8) | chCRCLo); +} + +static uint32_t num2bit(int num) { + switch(num) { + case 0: return 0x80000000; + case 1: return 0x40000000; + case 2: return 0x20000000; + case 3: return 0x10000000; + case 4: return 0x08000000; + case 5: return 0x04000000; + case 6: return 0x02000000; + case 7: return 0x01000000; + case 8: return 0x00800000; + case 9: return 0x00400000; + case 10: return 0x00200000; + case 11: return 0x00100000; + case 12: return 0x00080000; + case 13: return 0x00040000; + case 14: return 0x00020000; + case 15: return 0x00010000; + case 16: return 0x00008000; + case 17: return 0x00004000; + case 18: return 0x00002000; + case 19: return 0x00001000; + case 20: return 0x00000800; + case 21: return 0x00000400; + case 22: return 0x00000200; + case 23: return 0x00000100; + case 24: return 0x00000080; + case 25: return 0x00000040; + case 26: return 0x00000020; + case 27: return 0x00000010; + case 28: return 0x00000008; + case 29: return 0x00000004; + case 30: return 0x00000002; + case 31: return 0x00000001; + default: return 0x00000000; + } +} + +static bool get_options(int this_option_offset, int *baud, int *chain_num, + int *asic_num, int *timeout, int *frequency, char * frequency_t, uint8_t * reg_data, uint8_t * voltage, char * voltage_t) +{ + char buf[BUFSIZ+1]; + char *ptr, *comma, *colon, *colon2, *colon3, *colon4, *colon5, *colon6; + size_t max; + int i, tmp; + + if (opt_bitmain_options == NULL) + buf[0] = '\0'; + else { + ptr = opt_bitmain_options; + for (i = 0; i < this_option_offset; i++) { + comma = strchr(ptr, ','); + if (comma == NULL) + break; + ptr = comma + 1; + } + + comma = strchr(ptr, ','); + if (comma == NULL) + max = strlen(ptr); + else + max = comma - ptr; + + if (max > BUFSIZ) + max = BUFSIZ; + strncpy(buf, ptr, max); + buf[max] = '\0'; + } + + if (!(*buf)) + return false; + + colon = strchr(buf, ':'); + if (colon) + *(colon++) = '\0'; + + tmp = atoi(buf); + switch (tmp) { + case 115200: + *baud = 115200; + break; + case 57600: + *baud = 57600; + break; + case 38400: + *baud = 38400; + break; + case 19200: + *baud = 19200; + break; + default: + quit(1, "Invalid bitmain-options for baud (%s) " + "must be 115200, 57600, 38400 or 19200", buf); + } + + if (colon && *colon) { + colon2 = strchr(colon, ':'); + if (colon2) + *(colon2++) = '\0'; + + if (*colon) { + tmp = atoi(colon); + if (tmp > 0) { + *chain_num = tmp; + } else { + quit(1, "Invalid bitmain-options for " + "chain_num (%s) must be 1 ~ %d", + colon, BITMAIN_DEFAULT_CHAIN_NUM); + } + } + + if (colon2 && *colon2) { + colon3 = strchr(colon2, ':'); + if (colon3) + *(colon3++) = '\0'; + + tmp = atoi(colon2); + if (tmp > 0 && tmp <= BITMAIN_DEFAULT_ASIC_NUM) + *asic_num = tmp; + else { + quit(1, "Invalid bitmain-options for " + "asic_num (%s) must be 1 ~ %d", + colon2, BITMAIN_DEFAULT_ASIC_NUM); + } + + if (colon3 && *colon3) { + colon4 = strchr(colon3, ':'); + if (colon4) + *(colon4++) = '\0'; + + tmp = atoi(colon3); + if (tmp > 0 && tmp <= 0xff) + *timeout = tmp; + else { + quit(1, "Invalid bitmain-options for " + "timeout (%s) must be 1 ~ %d", + colon3, 0xff); + } + if (colon4 && *colon4) { + colon5 = strchr(colon4, ':'); + if(colon5) + *(colon5++) = '\0'; + + tmp = atoi(colon4); + if (tmp < BITMAIN_MIN_FREQUENCY || tmp > BITMAIN_MAX_FREQUENCY) { + quit(1, "Invalid bitmain-options for frequency, must be %d <= frequency <= %d", + BITMAIN_MIN_FREQUENCY, BITMAIN_MAX_FREQUENCY); + } else { + *frequency = tmp; + strcpy(frequency_t, colon4); + } + if (colon5 && *colon5) { + colon6 = strchr(colon5, ':'); + if(colon6) + *(colon6++) = '\0'; + + if(strlen(colon5) > 8 || strlen(colon5)%2 != 0 || strlen(colon5)/2 == 0) { + quit(1, "Invalid bitmain-options for reg data, must be hex now: %s", + colon5); + } + memset(reg_data, 0, 4); + if(!hex2bin(reg_data, colon5, strlen(colon5)/2)) { + quit(1, "Invalid bitmain-options for reg data, hex2bin error now: %s", + colon5); + } + + if (colon6 && *colon6) { + if(strlen(colon6) > 4 || strlen(colon6)%2 != 0 || strlen(colon6)/2 == 0) { + quit(1, "Invalid bitmain-options for voltage data, must be hex now: %s", + colon6); + } + memset(voltage, 0, 2); + if(!hex2bin(voltage, colon6, strlen(colon6)/2)) { + quit(1, "Invalid bitmain-options for voltage data, hex2bin error now: %s", + colon5); + } else { + sprintf(voltage_t, "%02x%02x", voltage[0], voltage[1]); + voltage_t[5] = 0; + voltage_t[4] = voltage_t[3]; + voltage_t[3] = voltage_t[2]; + voltage_t[2] = voltage_t[1]; + voltage_t[1] = '.'; + } + } + } + } + } + } + } + return true; +} + +static bool get_option_freq(int *timeout, int *frequency, char * frequency_t, uint8_t * reg_data) +{ + char buf[BUFSIZ+1]; + char *ptr, *comma, *colon, *colon2; + size_t max; + int i, tmp; + + if (opt_bitmain_freq == NULL) + return true; + else { + ptr = opt_bitmain_freq; + + comma = strchr(ptr, ','); + if (comma == NULL) + max = strlen(ptr); + else + max = comma - ptr; + + if (max > BUFSIZ) + max = BUFSIZ; + strncpy(buf, ptr, max); + buf[max] = '\0'; + } + + if (!(*buf)) + return false; + + colon = strchr(buf, ':'); + if (colon) + *(colon++) = '\0'; + + tmp = atoi(buf); + if (tmp > 0 && tmp <= 0xff) + *timeout = tmp; + else { + quit(1, "Invalid bitmain-freq for " + "timeout (%s) must be 1 ~ %d", + buf, 0xff); + } + + if (colon && *colon) { + colon2 = strchr(colon, ':'); + if (colon2) + *(colon2++) = '\0'; + + tmp = atoi(colon); + if (tmp < BITMAIN_MIN_FREQUENCY || tmp > BITMAIN_MAX_FREQUENCY) { + quit(1, "Invalid bitmain-freq for frequency, must be %d <= frequency <= %d", + BITMAIN_MIN_FREQUENCY, BITMAIN_MAX_FREQUENCY); + } else { + *frequency = tmp; + strcpy(frequency_t, colon); + } + + if (colon2 && *colon2) { + if(strlen(colon2) > 8 || strlen(colon2)%2 != 0 || strlen(colon2)/2 == 0) { + quit(1, "Invalid bitmain-freq for reg data, must be hex now: %s", + colon2); + } + memset(reg_data, 0, 4); + if(!hex2bin(reg_data, colon2, strlen(colon2)/2)) { + quit(1, "Invalid bitmain-freq for reg data, hex2bin error now: %s", + colon2); + } + } + } + return true; +} + +static bool get_option_voltage(uint8_t * voltage, char * voltage_t) +{ + if(opt_bitmain_voltage) { + if(strlen(opt_bitmain_voltage) > 4 || strlen(opt_bitmain_voltage)%2 != 0 || strlen(opt_bitmain_voltage)/2 == 0) { + applog(LOG_ERR, "Invalid bitmain-voltage for voltage data, must be hex now: %s,set default_volttage", + opt_bitmain_voltage); + return false; + } + memset(voltage, 0, 2); + if(!hex2bin(voltage, opt_bitmain_voltage, strlen(opt_bitmain_voltage)/2)) { + quit(1, "Invalid bitmain-voltage for voltage data, hex2bin error now: %s", + opt_bitmain_voltage); + } else { + sprintf(voltage_t, "%02x%02x", voltage[0], voltage[1]); + voltage_t[5] = 0; + voltage_t[4] = voltage_t[3]; + voltage_t[3] = voltage_t[2]; + voltage_t[2] = voltage_t[1]; + voltage_t[1] = '.'; + } + } + return true; +} + +static int bitmain_set_txconfig(struct bitmain_txconfig_token *bm, + uint8_t reset, uint8_t fan_eft, uint8_t timeout_eft, uint8_t frequency_eft, + uint8_t voltage_eft, uint8_t chain_check_time_eft, uint8_t chip_config_eft, uint8_t hw_error_eft, + uint8_t beeper_ctrl, uint8_t temp_over_ctrl,uint8_t fan_home_mode, + uint8_t chain_num, uint8_t asic_num, uint8_t fan_pwm_data, uint8_t timeout_data, + uint16_t frequency, uint8_t * voltage, uint8_t chain_check_time, + uint8_t chip_address, uint8_t reg_address, uint8_t * reg_data) +{ + uint16_t crc = 0; + int datalen = 0; + uint8_t version = 0; + uint8_t * sendbuf = (uint8_t *)bm; + if (unlikely(!bm)) { + applog(LOG_WARNING, "bitmain_set_txconfig bitmain_txconfig_token is null"); + return -1; + } + + if (unlikely(timeout_data <= 0 || asic_num <= 0 || chain_num <= 0)) { + applog(LOG_WARNING, "bitmain_set_txconfig parameter invalid timeout_data(%d) asic_num(%d) chain_num(%d)", + timeout_data, asic_num, chain_num); + return -1; + } + + datalen = sizeof(struct bitmain_txconfig_token); + memset(bm, 0, datalen); + + bm->token_type = BITMAIN_TOKEN_TYPE_TXCONFIG; + bm->version = version; + bm->length = datalen-4; + bm->length = htole16(bm->length); + + bm->reset = reset; + bm->fan_eft = fan_eft; + bm->timeout_eft = timeout_eft; + bm->frequency_eft = frequency_eft; + bm->voltage_eft = voltage_eft; + bm->chain_check_time_eft = chain_check_time_eft; + bm->chip_config_eft = chip_config_eft; + bm->hw_error_eft = hw_error_eft; + bm->beeper_ctrl = beeper_ctrl; + bm->temp_over_ctrl = temp_over_ctrl; + bm->fan_home_mode = fan_home_mode; + + sendbuf[4] = htole8(sendbuf[4]); + sendbuf[5] = htole8(sendbuf[5]); + + bm->chain_num = chain_num; + bm->asic_num = asic_num; + bm->fan_pwm_data = fan_pwm_data; + bm->timeout_data = timeout_data; + + bm->frequency = htole16(frequency); + memcpy(bm->voltage, voltage, 2); + bm->chain_check_time = chain_check_time; + + memcpy(bm->reg_data, reg_data, 4); + bm->chip_address = chip_address; + bm->reg_address = reg_address; + + crc = CRC16((uint8_t *)bm, datalen-2); + bm->crc = htole16(crc); + + applog(LOG_ERR, "BTM TxConfigToken:v(%d) reset(%d) fan_e(%d) tout_e(%d) fq_e(%d) vt_e(%d) chainc_e(%d) chipc_e(%d) hw_e(%d) b_c(%d) t_c(%d) f_m(%d) mnum(%d) anum(%d) fanpwmdata(%d) toutdata(%d) freq(%d) volt(%02x%02x) chainctime(%d) regdata(%02x%02x%02x%02x) chipaddr(%02x) regaddr(%02x) crc(%04x)", + version, reset, fan_eft, timeout_eft, frequency_eft, voltage_eft, + chain_check_time_eft, chip_config_eft, hw_error_eft, beeper_ctrl, temp_over_ctrl,fan_home_mode,chain_num, asic_num, + fan_pwm_data, timeout_data, frequency, voltage[0], voltage[1], + chain_check_time, reg_data[0], reg_data[1], reg_data[2], reg_data[3], chip_address, reg_address, crc); + + return datalen; +} + +static int bitmain_set_txtask(uint8_t * sendbuf, + unsigned int * last_work_block, struct work **works, int work_array_size, int work_array, int sendworkcount, int * sendcount) +{ + uint16_t crc = 0; + uint32_t work_id = 0; + uint8_t version = 0; + int datalen = 0; + int i = 0; + int index = work_array; + uint8_t new_block= 0; + char * ob_hex = NULL; + struct bitmain_txtask_token *bm = (struct bitmain_txtask_token *)sendbuf; + *sendcount = 0; + int cursendcount = 0; + int diff = 0; + unsigned int difftmp = 0; + unsigned int pooldiff = 0; + uint64_t netdifftmp = 0; + int netdiff = 0; + if (unlikely(!bm)) { + applog(LOG_WARNING, "bitmain_set_txtask bitmain_txtask_token is null"); + return -1; + } + if (unlikely(!works)) { + applog(LOG_WARNING, "bitmain_set_txtask work is null"); + return -1; + } + memset(bm, 0, sizeof(struct bitmain_txtask_token)); + + bm->token_type = BITMAIN_TOKEN_TYPE_TXTASK; + bm->version = version; + + datalen = 10; + applog(LOG_DEBUG, "BTM send work count %d -----", sendworkcount); + for(i = 0; i < sendworkcount; i++) { + if(index > work_array_size) { + index = 0; + } + if(works[index]) { + if(works[index]->work_block > *last_work_block) { + applog(LOG_ERR, "BTM send task new block %d old(%d)", works[index]->work_block, *last_work_block); + new_block = 1; + *last_work_block = works[index]->work_block; + } +#ifdef BITMAIN_TEST + if(!hex2bin(works[index]->data, btm_work_test_data[g_test_index], 128)) { + applog(LOG_DEBUG, "BTM send task set test data error"); + } + if(!hex2bin(works[index]->midstate, btm_work_test_midstate[g_test_index], 32)) { + applog(LOG_DEBUG, "BTM send task set test midstate error"); + } + g_test_index++; + if(g_test_index >= BITMAIN_TEST_USENUM) { + g_test_index = 0; + } + applog(LOG_DEBUG, "BTM test index = %d", g_test_index); +#endif + work_id = works[index]->id; + bm->works[cursendcount].work_id = htole32(work_id); + applog(LOG_DEBUG, "BTM send task work id:%d %d", bm->works[cursendcount].work_id, work_id); + memcpy(bm->works[cursendcount].midstate, works[index]->midstate, 32); + memcpy(bm->works[cursendcount].data2, works[index]->data + 64, 12); + + if(cursendcount == 0) { + pooldiff = (unsigned int)(works[index]->sdiff); + difftmp = pooldiff; + while(1) { + difftmp = difftmp >> 1; + if(difftmp > 0) { + diff++; + if(diff >= 255) { + break; + } + } else { + break; + } + } + } + + if(BITMAIN_TEST_PRINT_WORK) { + ob_hex = bin2hex(works[index]->data, 76); + applog(LOG_ERR, "work %d data: %s", works[index]->id, ob_hex); + free(ob_hex); + } + + cursendcount++; + } + index++; + } + if(cursendcount <= 0) { + applog(LOG_ERR, "BTM send work count %d", cursendcount); + return 0; + } + + netdifftmp = current_diff; + while(netdifftmp > 0) { + netdifftmp = netdifftmp >> 1; + netdiff++; + } + datalen += 48*cursendcount; + + bm->length = datalen-4; + bm->length = htole16(bm->length); + //len = datalen-3; + //len = htole16(len); + //memcpy(sendbuf+1, &len, 2); + bm->new_block = new_block; + bm->diff = diff; + bm->net_diff = htole16(netdiff); + + sendbuf[4] = htole8(sendbuf[4]); + + applog(LOG_DEBUG, "BitMain TxTask Token: %d %d %02x%02x%02x%02x%02x%02x", + datalen, bm->length, sendbuf[0],sendbuf[1],sendbuf[2],sendbuf[3],sendbuf[4],sendbuf[5]); + + *sendcount = cursendcount; + + crc = CRC16(sendbuf, datalen-2); + crc = htole16(crc); + memcpy(sendbuf+datalen-2, &crc, 2); + + applog(LOG_DEBUG, "BitMain TxTask Token: v(%d) new_block(%d) diff(%d pool:%d net:%d) work_num(%d) crc(%04x)", + version, new_block, diff, pooldiff,netdiff, cursendcount, crc); + applog(LOG_DEBUG, "BitMain TxTask Token: %d %d %02x%02x%02x%02x%02x%02x", + datalen, bm->length, sendbuf[0],sendbuf[1],sendbuf[2],sendbuf[3],sendbuf[4],sendbuf[5]); + + return datalen; +} + +static int bitmain_set_rxstatus(struct bitmain_rxstatus_token *bm, + uint8_t chip_status_eft, uint8_t detect_get, uint8_t chip_address, uint8_t reg_address) +{ + uint16_t crc = 0; + uint8_t version = 0; + int datalen = 0; + uint8_t * sendbuf = (uint8_t *)bm; + + if (unlikely(!bm)) { + applog(LOG_WARNING, "bitmain_set_rxstatus bitmain_rxstatus_token is null"); + return -1; + } + + datalen = sizeof(struct bitmain_rxstatus_token); + memset(bm, 0, datalen); + + bm->token_type = BITMAIN_TOKEN_TYPE_RXSTATUS; + bm->version = version; + bm->length = datalen-4; + bm->length = htole16(bm->length); + + bm->chip_status_eft = chip_status_eft; + bm->detect_get = detect_get; + + sendbuf[4] = htole8(sendbuf[4]); + + bm->chip_address = chip_address; + bm->reg_address = reg_address; + + crc = CRC16((uint8_t *)bm, datalen-2); + bm->crc = htole16(crc); + + applog(LOG_ERR, "BitMain RxStatus Token: v(%d) chip_status_eft(%d) detect_get(%d) chip_address(%02x) reg_address(%02x) crc(%04x)", + version, chip_status_eft, detect_get, chip_address, reg_address, crc); + + return datalen; +} + +static int bitmain_parse_rxstatus(const uint8_t * data, int datalen, struct bitmain_rxstatus_data *bm) +{ + uint16_t crc = 0; + uint8_t version = 0; + int i = 0, j = 0; + int asic_num = 0; + int dataindex = 0; + uint8_t tmp = 0x01; + if (unlikely(!bm)) { + applog(LOG_WARNING, "bitmain_parse_rxstatus bitmain_rxstatus_data is null"); + return -1; + } + if (unlikely(!data || datalen <= 0)) { + applog(LOG_WARNING, "bitmain_parse_rxstatus parameter invalid data is null or datalen(%d) error", datalen); + return -1; + } + memset(bm, 0, sizeof(struct bitmain_rxstatus_data)); + memcpy(bm, data, 28); + if (bm->data_type != BITMAIN_DATA_TYPE_RXSTATUS) { + applog(LOG_ERR, "bitmain_parse_rxstatus datatype(%02x) error", bm->data_type); + return -1; + } + if (bm->version != version) { + applog(LOG_ERR, "bitmain_parse_rxstatus version(%02x) error", bm->version); + return -1; + } + bm->length = htole16(bm->length); + if (bm->length+4 != datalen) { + applog(LOG_ERR, "bitmain_parse_rxstatus length(%d) datalen(%d) error", bm->length, datalen); + return -1; + } + crc = CRC16(data, datalen-2); + memcpy(&(bm->crc), data+datalen-2, 2); + bm->crc = htole16(bm->crc); + if(crc != bm->crc) { + applog(LOG_ERR, "bitmain_parse_rxstatus check crc(%d) != bm crc(%d) datalen(%d)", crc, bm->crc, datalen); + return -1; + } + bm->fifo_space = htole16(bm->fifo_space); + bm->fan_exist = htole16(bm->fan_exist); + bm->temp_exist = htole32(bm->temp_exist); + bm->nonce_error = htole32(bm->nonce_error); + if(bm->chain_num > BITMAIN_MAX_CHAIN_NUM) { + applog(LOG_ERR, "bitmain_parse_rxstatus chain_num=%d error", bm->chain_num); + return -1; + } + dataindex = 28; + if(bm->chain_num > 0) { + memcpy(bm->chain_asic_num, data+datalen-2-bm->chain_num-bm->temp_num-bm->fan_num, bm->chain_num); + } + for(i = 0; i < bm->chain_num; i++) { + asic_num = bm->chain_asic_num[i]; + if(asic_num <= 0) { + asic_num = 1; + } else { + if(asic_num % 32 == 0) { + asic_num = asic_num / 32; + } else { + asic_num = asic_num / 32 + 1; + } + } + memcpy((uint8_t *)bm->chain_asic_exist+i*32, data+dataindex, asic_num*4); + dataindex += asic_num*4; + } + for(i = 0; i < bm->chain_num; i++) { + asic_num = bm->chain_asic_num[i]; + if(asic_num <= 0) { + asic_num = 1; + } else { + if(asic_num % 32 == 0) { + asic_num = asic_num / 32; + } else { + asic_num = asic_num / 32 + 1; + } + } + memcpy((uint8_t *)bm->chain_asic_status+i*32, data+dataindex, asic_num*4); + dataindex += asic_num*4; + } + dataindex += bm->chain_num; + if(dataindex + bm->temp_num + bm->fan_num + 2 != datalen) { + applog(LOG_ERR, "bitmain_parse_rxstatus dataindex(%d) chain_num(%d) temp_num(%d) fan_num(%d) not match datalen(%d)", + dataindex, bm->chain_num, bm->temp_num, bm->fan_num, datalen); + return -1; + } + for(i = 0; i < bm->chain_num; i++) { + //bm->chain_asic_status[i] = swab32(bm->chain_asic_status[i]); + for(j = 0; j < 8; j++) { + bm->chain_asic_exist[i*8+j] = htole32(bm->chain_asic_exist[i*8+j]); + bm->chain_asic_status[i*8+j] = htole32(bm->chain_asic_status[i*8+j]); + } + } + if(bm->temp_num > 0) { + memcpy(bm->temp, data+dataindex, bm->temp_num); + dataindex += bm->temp_num; + } + if(bm->fan_num > 0) { + memcpy(bm->fan, data+dataindex, bm->fan_num); + dataindex += bm->fan_num; + } + if(!opt_bitmain_checkall){ + if(tmp != htole8(tmp)){ + applog(LOG_ERR, "BitMain RxStatus byte4 0x%02x chip_value_eft %d reserved %d get_blk_num %d ",*((uint8_t* )bm +4),bm->chip_value_eft,bm->reserved1,bm->get_blk_num); + memcpy(&tmp,data+4,1); + bm->chip_value_eft = tmp >>7; + bm->get_blk_num = tmp >> 4; + bm->reserved1 = ((tmp << 4) & 0xff) >> 5; + } + found_blocks = bm->get_blk_num; + applog(LOG_ERR, "BitMain RxStatus tmp :0x%02x byte4 0x%02x chip_value_eft %d reserved %d get_blk_num %d ",tmp,*((uint8_t* )bm +4),bm->chip_value_eft,bm->reserved1,bm->get_blk_num); + } + applog(LOG_DEBUG, "BitMain RxStatusData: chipv_e(%d) chainnum(%d) fifos(%d) v1(%d) v2(%d) v3(%d) v4(%d) fann(%d) tempn(%d) fanet(%04x) tempet(%08x) ne(%d) regvalue(%d) crc(%04x)", + bm->chip_value_eft, bm->chain_num, bm->fifo_space, bm->hw_version[0], bm->hw_version[1], bm->hw_version[2], bm->hw_version[3], bm->fan_num, bm->temp_num, bm->fan_exist, bm->temp_exist, bm->nonce_error, bm->reg_value, bm->crc); + applog(LOG_DEBUG, "BitMain RxStatus Data chain info:"); + for(i = 0; i < bm->chain_num; i++) { + applog(LOG_DEBUG, "BitMain RxStatus Data chain(%d) asic num=%d asic_exist=%08x asic_status=%08x", i+1, bm->chain_asic_num[i], bm->chain_asic_exist[i*8], bm->chain_asic_status[i*8]); + } + applog(LOG_DEBUG, "BitMain RxStatus Data temp info:"); + for(i = 0; i < bm->temp_num; i++) { + applog(LOG_DEBUG, "BitMain RxStatus Data temp(%d) temp=%d", i+1, bm->temp[i]); + } + applog(LOG_DEBUG, "BitMain RxStatus Data fan info:"); + for(i = 0; i < bm->fan_num; i++) { + applog(LOG_DEBUG, "BitMain RxStatus Data fan(%d) fan=%d", i+1, bm->fan[i]); + } + return 0; +} + +static int bitmain_parse_rxnonce(const uint8_t * data, int datalen, struct bitmain_rxnonce_data *bm, int * nonce_num) +{ + int i = 0; + uint16_t crc = 0; + uint8_t version = 0; + int curnoncenum = 0; + if (unlikely(!bm)) { + applog(LOG_ERR, "bitmain_parse_rxnonce bitmain_rxstatus_data null"); + return -1; + } + if (unlikely(!data || datalen <= 0)) { + applog(LOG_ERR, "bitmain_parse_rxnonce data null or datalen(%d) error", datalen); + return -1; + } + memcpy(bm, data, sizeof(struct bitmain_rxnonce_data)); + if (bm->data_type != BITMAIN_DATA_TYPE_RXNONCE) { + applog(LOG_ERR, "bitmain_parse_rxnonce datatype(%02x) error", bm->data_type); + return -1; + } + if (bm->version != version) { + applog(LOG_ERR, "bitmain_parse_rxnonce version(%02x) error", bm->version); + return -1; + } + bm->length = htole16(bm->length); + if (bm->length+4 != datalen) { + applog(LOG_ERR, "bitmain_parse_rxnonce length(%d) error", bm->length); + return -1; + } + crc = CRC16(data, datalen-2); + memcpy(&(bm->crc), data+datalen-2, 2); + bm->crc = htole16(bm->crc); + if(crc != bm->crc) { + applog(LOG_ERR, "bitmain_parse_rxnonce check crc(%d) != bm crc(%d) datalen(%d)", crc, bm->crc, datalen); + return -1; + } + bm->fifo_space = htole16(bm->fifo_space); + bm->diff = htole16(bm->diff); + bm->total_nonce_num = htole64(bm->total_nonce_num); + curnoncenum = (datalen-14)/8; + applog(LOG_DEBUG, "BitMain RxNonce Data: nonce_num(%d) fifo_space(%d) diff(%d) tnn(%lld)", curnoncenum, bm->fifo_space, bm->diff, bm->total_nonce_num); + for(i = 0; i < curnoncenum; i++) { + bm->nonces[i].work_id = htole32(bm->nonces[i].work_id); + bm->nonces[i].nonce = htole32(bm->nonces[i].nonce); + + applog(LOG_DEBUG, "BitMain RxNonce Data %d: work_id(%d) nonce(%08x)(%d)", + i, bm->nonces[i].work_id, bm->nonces[i].nonce, bm->nonces[i].nonce); + } + *nonce_num = curnoncenum; + return 0; +} + +static int bitmain_read(struct cgpu_info *bitmain, unsigned char *buf, + size_t bufsize, int timeout, int ep) +{ + int err = 0, readlen = 0; + size_t total = 0; + + if(bitmain == NULL || buf == NULL || bufsize <= 0) { + applog(LOG_WARNING, "bitmain_read parameter error bufsize(%d)", bufsize); + return -1; + } + if(opt_bitmain_dev_usb) { +#ifdef WIN32 + char readbuf[BITMAIN_READBUF_SIZE]; + int ofs = 2, cp = 0; + + err = usb_read_once_timeout(bitmain, readbuf, bufsize, &readlen, timeout, ep); + applog(LOG_DEBUG, "%s%i: Get bitmain read got readlen %d err %d", + bitmain->drv->name, bitmain->device_id, readlen, err); + + if (readlen < 2) + goto out; + + while (readlen > 2) { + cp = readlen - 2; + if (cp > 62) + cp = 62; + memcpy(&buf[total], &readbuf[ofs], cp); + total += cp; + readlen -= cp + 2; + ofs += 64; + } +#else + err = usb_read_once_timeout(bitmain, buf, bufsize, &readlen, timeout, ep); + applog(LOG_DEBUG, "%s%i: Get bitmain read got readlen %d err %d", + bitmain->drv->name, bitmain->device_id, readlen, err); + total = readlen; +#endif + } else { + err = btm_read(bitmain, buf, bufsize); + total = err; + } +out: + return total; +} + +static int bitmain_write(struct cgpu_info *bitmain, char *buf, ssize_t len, int ep) +{ + int err, amount; + if(opt_bitmain_dev_usb) { + err = usb_write(bitmain, buf, len, &amount, ep); + applog(LOG_DEBUG, "%s%i: usb_write got err %d", bitmain->drv->name, + bitmain->device_id, err); + + if (unlikely(err != 0)) { + applog(LOG_ERR, "usb_write error on bitmain_write err=%d", err); + return BTM_SEND_ERROR; + } + if (amount != len) { + applog(LOG_ERR, "usb_write length mismatch on bitmain_write amount=%d len=%d", amount, len); + return BTM_SEND_ERROR; + } + } else { + int havelen = 0; + while(havelen < len) { + err = btm_write(bitmain, buf+havelen, len-havelen); + if(err < 0) { + applog(LOG_DEBUG, "%s%i: btm_write got err %d", bitmain->drv->name, + bitmain->device_id, err); + applog(LOG_WARNING, "usb_write error on bitmain_write"); + return BTM_SEND_ERROR; + } else { + havelen += err; + } + } + } + return BTM_SEND_OK; +} + +static int bitmain_send_data(const uint8_t * data, int datalen, struct cgpu_info *bitmain) +{ + int delay, ret, ep = C_BITMAIN_SEND; + struct bitmain_info *info = NULL; + cgtimer_t ts_start; + + if(datalen <= 0) { + return 0; + } + + if(data[0] == BITMAIN_TOKEN_TYPE_TXCONFIG) { + ep = C_BITMAIN_TOKEN_TXCONFIG; + } else if(data[0] == BITMAIN_TOKEN_TYPE_TXTASK) { + ep = C_BITMAIN_TOKEN_TXTASK; + } else if(data[0] == BITMAIN_TOKEN_TYPE_RXSTATUS) { + ep = C_BITMAIN_TOKEN_RXSTATUS; + } + + info = bitmain->device_data; + //delay = datalen * 10 * 1000000; + //delay = delay / info->baud; + //delay += 4000; + + if(opt_debug) { + applog(LOG_DEBUG, "BitMain: Sent(%d):", datalen); + hexdump(data, datalen); + } + + //cgsleep_prepare_r(&ts_start); + //applog(LOG_DEBUG, "----bitmain_send_data start"); + ret = bitmain_write(bitmain, (char *)data, datalen, ep); + applog(LOG_DEBUG, "----bitmain_send_data stop ret=%d datalen=%d", ret, datalen); + //cgsleep_us_r(&ts_start, delay); + + //applog(LOG_DEBUG, "BitMain: Sent: Buffer delay: %dus", delay); + + return ret; +} + +static bool bitmain_decode_nonce(struct thr_info *thr, struct cgpu_info *bitmain, + struct bitmain_info *info, uint32_t nonce, struct work *work) +{ + info = bitmain->device_data; + //info->matching_work[work->subid]++; + if(opt_bitmain_hwerror) { + applog(LOG_DEBUG, "BitMain: submit direct nonce = %08x", nonce); + if(opt_bitmain_checkall) { + applog(LOG_DEBUG, "BitMain check all"); + return submit_nonce(thr, work, nonce); + } else { + if(opt_bitmain_checkn2diff) { + int diff = 0; + diff = work->sdiff; + if(diff&&(diff&(diff-1))) { + applog(LOG_DEBUG, "BitMain %d not diff 2 submit_nonce", diff); + return submit_nonce(thr, work, nonce); + } else { + applog(LOG_DEBUG, "BitMain %d diff 2 submit_nonce_direct", diff); + return submit_nonce_direct(thr, work, nonce); + } + } else { + return submit_nonce_direct(thr, work, nonce); + } + } + } else { + applog(LOG_DEBUG, "BitMain: submit nonce = %08x", nonce); + return submit_nonce(thr, work, nonce); + } +} + +static void bitmain_inc_nvw(struct bitmain_info *info, struct thr_info *thr) +{ + applog(LOG_INFO, "%s%d: No matching work - HW error", + thr->cgpu->drv->name, thr->cgpu->device_id); + + inc_hw_errors(thr); + info->no_matching_work++; +} + +static inline void record_temp_fan(struct bitmain_info *info, struct bitmain_rxstatus_data *bm, double *temp_avg) +{ + int i = 0; + int maxfan = 0, maxtemp = 0; + *temp_avg = 0; + + info->fan_num = bm->fan_num; + for(i = 0; i < bm->fan_num; i++) { + info->fan[i] = bm->fan[i] * BITMAIN_FAN_FACTOR; + + if(info->fan[i] > maxfan) + maxfan = info->fan[i]; + } + info->temp_num = bm->temp_num; + for(i = 0; i < bm->temp_num; i++) { + info->temp[i] = bm->temp[i]; + /* + if(bm->temp[i] & 0x80) { + bm->temp[i] &= 0x7f; + info->temp[i] = 0 - ((~bm->temp[i] & 0x7f) + 1); + }*/ + *temp_avg += info->temp[i]; + + if(info->temp[i] > info->temp_max) { + info->temp_max = info->temp[i]; + } + if(info->temp[i] > maxtemp) + maxtemp = info->temp[i]; + } + + if(bm->temp_num > 0) { + *temp_avg = *temp_avg / bm->temp_num; + info->temp_avg = *temp_avg; + } + + inc_dev_status(maxfan, maxtemp); +} + +static void bitmain_update_temps(struct cgpu_info *bitmain, struct bitmain_info *info, + struct bitmain_rxstatus_data *bm) +{ + char tmp[64] = {0}; + char msg[10240] = {0}; + int i = 0; + record_temp_fan(info, bm, &(bitmain->temp)); + + strcpy(msg, "BitMain: "); + for(i = 0; i < bm->fan_num; i++) { + if(i != 0) { + strcat(msg, ", "); + } + sprintf(tmp, "Fan%d: %d/m", i+1, info->fan[i]); + strcat(msg, tmp); + } + strcat(msg, "\t"); + for(i = 0; i < bm->temp_num; i++) { + if(i != 0) { + strcat(msg, ", "); + } + sprintf(tmp, "Temp%d: %dC", i+1, info->temp[i]); + strcat(msg, tmp); + } + sprintf(tmp, ", TempMAX: %dC", info->temp_max); + strcat(msg, tmp); + applog(LOG_INFO, msg); + info->temp_history_index++; + info->temp_sum += bitmain->temp; + applog(LOG_DEBUG, "BitMain: temp_index: %d, temp_count: %d, temp_old: %d", + info->temp_history_index, info->temp_history_count, info->temp_old); + if (info->temp_history_index == info->temp_history_count) { + info->temp_history_index = 0; + info->temp_sum = 0; + } + if (unlikely(info->temp_old >= opt_bitmain_overheat)) { + applog(LOG_WARNING, "BTM%d overheat! Idling", bitmain->device_id); + info->overheat = true; + } else if (info->overheat && info->temp_old <= opt_bitmain_temp) { + applog(LOG_WARNING, "BTM%d cooled, restarting", bitmain->device_id); + info->overheat = false; + } +} + +extern void cg_logwork_uint32(struct work *work, uint32_t nonce, bool ok); + +static void bitmain_parse_results(struct cgpu_info *bitmain, struct bitmain_info *info, + struct thr_info *thr, uint8_t *buf, int *offset) +{ + int i, j, n, m, r, errordiff, spare = BITMAIN_READ_SIZE; + uint32_t checkbit = 0x00000000; + bool found = false; + struct work *work = NULL; + char * ob_hex = NULL; + struct bitmain_packet_head packethead; + int asicnum = 0; + int idiff = 0; + int mod = 0,tmp = 0; + + for (i = 0; i <= spare; i++) { + if(buf[i] == 0xa1) { + struct bitmain_rxstatus_data rxstatusdata; + applog(LOG_DEBUG, "bitmain_parse_results RxStatus Data"); + if(*offset < 4) { + return; + } + memcpy(&packethead, buf+i, sizeof(struct bitmain_packet_head)); + packethead.length = htole16(packethead.length); + if(packethead.length > 1130) { + applog(LOG_ERR, "bitmain_parse_results bitmain_parse_rxstatus datalen=%d error", packethead.length+4); + continue; + } + if(*offset < packethead.length + 4) { + return; + } + if(bitmain_parse_rxstatus(buf+i, packethead.length+4, &rxstatusdata) != 0) { + applog(LOG_ERR, "bitmain_parse_results bitmain_parse_rxstatus error len=%d", packethead.length+4); + } else { + mutex_lock(&info->qlock); + info->chain_num = rxstatusdata.chain_num; + info->fifo_space = rxstatusdata.fifo_space; + info->hw_version[0] = rxstatusdata.hw_version[0]; + info->hw_version[1] = rxstatusdata.hw_version[1]; + info->hw_version[2] = rxstatusdata.hw_version[2]; + info->hw_version[3] = rxstatusdata.hw_version[3]; + info->nonce_error = rxstatusdata.nonce_error; + errordiff = info->nonce_error-info->last_nonce_error; + //sprintf(g_miner_version, "%d.%d.%d.%d", info->hw_version[0], info->hw_version[1], info->hw_version[2], info->hw_version[3]); + applog(LOG_ERR, "bitmain_parse_results v=%d chain=%d fifo=%d hwv1=%d hwv2=%d hwv3=%d hwv4=%d nerr=%d-%d freq=%d chain info:", + rxstatusdata.version, info->chain_num, info->fifo_space, info->hw_version[0], info->hw_version[1], info->hw_version[2], info->hw_version[3], + info->last_nonce_error, info->nonce_error, info->frequency); + memcpy(info->chain_asic_exist, rxstatusdata.chain_asic_exist, BITMAIN_MAX_CHAIN_NUM*32); + memcpy(info->chain_asic_status, rxstatusdata.chain_asic_status, BITMAIN_MAX_CHAIN_NUM*32); + for(n = 0; n < rxstatusdata.chain_num; n++) { + info->chain_asic_num[n] = rxstatusdata.chain_asic_num[n]; + memset(info->chain_asic_status_t[n], 0, 320); + j = 0; + + mod = 0; + if(info->chain_asic_num[n] <= 0) { + asicnum = 0; + } else { + mod = info->chain_asic_num[n] % 32; + if(mod == 0) { + asicnum = info->chain_asic_num[n] / 32; + } else { + asicnum = info->chain_asic_num[n] / 32 + 1; + } + } + if(asicnum > 0) { + for(m = asicnum-1; m >= 0; m--) { + tmp = mod ? (32-mod): 0; + for(r = tmp;r < 32;r++){ + if((r-tmp)%8 == 0 && (r-tmp) !=0){ + info->chain_asic_status_t[n][j] = ' '; + j++; + } + checkbit = num2bit(r); + if(rxstatusdata.chain_asic_exist[n*8+m] & checkbit) { + if(rxstatusdata.chain_asic_status[n*8+m] & checkbit) { + info->chain_asic_status_t[n][j] = 'o'; + } else { + info->chain_asic_status_t[n][j] = 'x'; + } + } else { + info->chain_asic_status_t[n][j] = '-'; + } + j++; + } + info->chain_asic_status_t[n][j] = ' '; + j++; + mod = 0; + } + } + applog(LOG_DEBUG, "bitmain_parse_results chain(%d) asic_num=%d asic_exist=%08x%08x%08x%08x%08x%08x%08x%08x asic_status=%08x%08x%08x%08x%08x%08x%08x%08x", + n, info->chain_asic_num[n], + info->chain_asic_exist[n*8+0], info->chain_asic_exist[n*8+1], info->chain_asic_exist[n*8+2], info->chain_asic_exist[n*8+3], info->chain_asic_exist[n*8+4], info->chain_asic_exist[n*8+5], info->chain_asic_exist[n*8+6], info->chain_asic_exist[n*8+7], + info->chain_asic_status[n*8+0], info->chain_asic_status[n*8+1], info->chain_asic_status[n*8+2], info->chain_asic_status[n*8+3], info->chain_asic_status[n*8+4], info->chain_asic_status[n*8+5], info->chain_asic_status[n*8+6], info->chain_asic_status[n*8+7]); + applog(LOG_ERR, "bitmain_parse_results chain(%d) asic_num=%d asic_status=%s", n, info->chain_asic_num[n], info->chain_asic_status_t[n]); + } + mutex_unlock(&info->qlock); + + if(errordiff > 0) { + for(j = 0; j < errordiff; j++) { + bitmain_inc_nvw(info, thr); + } + mutex_lock(&info->qlock); + info->last_nonce_error += errordiff; + mutex_unlock(&info->qlock); + } + bitmain_update_temps(bitmain, info, &rxstatusdata); + } + + found = true; + spare = packethead.length + 4 + i; + if(spare > *offset) { + applog(LOG_ERR, "bitmain_parse_rxresults space(%d) > offset(%d)", spare, *offset); + spare = *offset; + } + break; + } else if(buf[i] == 0xa2) { + struct bitmain_rxnonce_data rxnoncedata; + int nonce_num = 0; + applog(LOG_DEBUG, "bitmain_parse_results RxNonce Data"); + if(*offset < 4) { + return; + } + memcpy(&packethead, buf+i, sizeof(struct bitmain_packet_head)); + packethead.length = htole16(packethead.length); + if(packethead.length > 1030) { + applog(LOG_ERR, "bitmain_parse_results bitmain_parse_rxnonce datalen=%d error", packethead.length+4); + continue; + } + if(*offset < packethead.length + 4) { + return; + } + if(bitmain_parse_rxnonce(buf+i, packethead.length+4, &rxnoncedata, &nonce_num) != 0) { + applog(LOG_ERR, "bitmain_parse_results bitmain_parse_rxnonce error len=%d", packethead.length+4); + } else { + struct pool * pool = NULL; + for(j = 0; j < nonce_num; j++) { + work = clone_queued_work_byid(bitmain, rxnoncedata.nonces[j].work_id); + if(work) { + pool = work->pool; + if(BITMAIN_TEST_PRINT_WORK) { + applog(LOG_ERR, "bitmain_parse_results nonce find work(%d-%d)(%08x)", work->id, rxnoncedata.nonces[j].work_id, rxnoncedata.nonces[j].nonce); + + ob_hex = bin2hex(work->midstate, 32); + applog(LOG_ERR, "work %d midstate: %s", work->id, ob_hex); + free(ob_hex); + + ob_hex = bin2hex(work->data+64, 12); + applog(LOG_ERR, "work %d data2: %s", work->id, ob_hex); + free(ob_hex); + } + + if(work->work_block < info->last_work_block) { + applog(LOG_ERR, "BitMain: bitmain_parse_rxnonce work(%d) nonce stale", rxnoncedata.nonces[j].work_id); + } else { + if (bitmain_decode_nonce(thr, bitmain, info, rxnoncedata.nonces[j].nonce, work)) { + cg_logwork_uint32(work, rxnoncedata.nonces[j].nonce, true); + if(opt_bitmain_hwerror) { +#ifndef BITMAIN_CALC_DIFF1 + mutex_lock(&info->qlock); + idiff = (int)work->sdiff; + info->nonces+=idiff; + info->auto_nonces+=idiff; + mutex_unlock(&info->qlock); + inc_work_status(thr, pool, idiff); +#endif + } else { + mutex_lock(&info->qlock); + info->nonces++; + info->auto_nonces++; + mutex_unlock(&info->qlock); + } + } else { + //bitmain_inc_nvw(info, thr); + applog(LOG_ERR, "BitMain: bitmain_decode_nonce error work(%d)", rxnoncedata.nonces[j].work_id); + } + } + free_work(work); + } else { + //bitmain_inc_nvw(info, thr); + applog(LOG_ERR, "BitMain: Nonce not find work(%d)", rxnoncedata.nonces[j].work_id); + } + } +#ifdef BITMAIN_CALC_DIFF1 + if(opt_bitmain_hwerror) { + int difftmp = 0; + difftmp = rxnoncedata.diff; + idiff = 1; + while(difftmp > 0) { + difftmp--; + idiff = idiff << 1; + } + mutex_lock(&info->qlock); + difftmp = idiff*(rxnoncedata.total_nonce_num-info->total_nonce_num); + if(difftmp < 0) + difftmp = 0; + + info->nonces = info->nonces+difftmp; + info->auto_nonces = info->auto_nonces+difftmp; + info->total_nonce_num = rxnoncedata.total_nonce_num; + info->fifo_space = rxnoncedata.fifo_space; + mutex_unlock(&info->qlock); + inc_work_stats(thr, pool, difftmp); + + applog(LOG_DEBUG, "bitmain_parse_rxnonce fifo space=%d diff=%d rxtnn=%lld tnn=%lld", info->fifo_space, idiff, rxnoncedata.total_nonce_num, info->total_nonce_num); + } else { + mutex_lock(&info->qlock); + info->fifo_space = rxnoncedata.fifo_space; + mutex_unlock(&info->qlock); + applog(LOG_DEBUG, "bitmain_parse_rxnonce fifo space=%d", info->fifo_space); + } +#else + mutex_lock(&info->qlock); + info->fifo_space = rxnoncedata.fifo_space; + mutex_unlock(&info->qlock); + applog(LOG_DEBUG, "bitmain_parse_rxnonce fifo space=%d", info->fifo_space); +#endif + +#ifndef WIN32 + if(nonce_num < BITMAIN_MAX_NONCE_NUM) + cgsleep_ms(5); +#endif + } + + found = true; + spare = packethead.length + 4 + i; + if(spare > *offset) { + applog(LOG_ERR, "bitmain_parse_rxnonce space(%d) > offset(%d)", spare, *offset); + spare = *offset; + } + break; + } else { + applog(LOG_ERR, "bitmain_parse_results data type error=%02x", buf[i]); + } + } + if (!found) { + spare = *offset - BITMAIN_READ_SIZE; + /* We are buffering and haven't accumulated one more corrupt + * work result. */ + if (spare < (int)BITMAIN_READ_SIZE) + return; + bitmain_inc_nvw(info, thr); + } + + *offset -= spare; + memmove(buf, buf + spare, *offset); +} + +static void bitmain_running_reset(struct cgpu_info *bitmain, struct bitmain_info *info) +{ + bitmain->results = 0; + info->reset = false; +} + +static void *bitmain_get_results(void *userdata) +{ + struct cgpu_info *bitmain = (struct cgpu_info *)userdata; + struct bitmain_info *info = bitmain->device_data; + int offset = 0, read_delay = 0, ret = 0; + const int rsize = BITMAIN_FTDI_READSIZE; + char readbuf[BITMAIN_READBUF_SIZE]; + struct thr_info *thr = info->thr; + char threadname[24]; + int errorcount = 0; + + snprintf(threadname, 24, "btm_recv/%d", bitmain->device_id); + RenameThread(threadname); + + while (likely(!bitmain->shutdown)) { + unsigned char buf[rsize]; + + //applog(LOG_DEBUG, "+++++++bitmain_get_results offset=%d", offset); + + if (offset >= (int)BITMAIN_READ_SIZE) { + //applog(LOG_DEBUG, "======start bitmain_get_results "); + bitmain_parse_results(bitmain, info, thr, readbuf, &offset); + //applog(LOG_DEBUG, "======stop bitmain_get_results "); + } + + if (unlikely(offset + rsize >= BITMAIN_READBUF_SIZE)) { + /* This should never happen */ + applog(LOG_DEBUG, "BitMain readbuf overflow, resetting buffer"); + offset = 0; + } + + if (unlikely(info->reset)) { + bitmain_running_reset(bitmain, info); + /* Discard anything in the buffer */ + offset = 0; + } + + /* As the usb read returns after just 1ms, sleep long enough + * to leave the interface idle for writes to occur, but do not + * sleep if we have been receiving data as more may be coming. */ + //if (offset == 0) { + // cgsleep_ms_r(&ts_start, BITMAIN_READ_TIMEOUT); + //} + + //cgsleep_prepare_r(&ts_start); + //applog(LOG_DEBUG, "======start bitmain_get_results bitmain_read"); + ret = bitmain_read(bitmain, buf, rsize, BITMAIN_READ_TIMEOUT, C_BITMAIN_READ); + //applog(LOG_DEBUG, "======stop bitmain_get_results bitmain_read=%d", ret); + + if ((ret < 1) || (ret == 18)) { + errorcount++; +#ifdef WIN32 + if(errorcount > 200) { + //applog(LOG_ERR, "bitmain_read errorcount ret=%d", ret); + cgsleep_ms(20); + errorcount = 0; + } +#else + if(errorcount > 3) { + //applog(LOG_ERR, "bitmain_read errorcount ret=%d", ret); + cgsleep_ms(20); + errorcount = 0; + } +#endif + if(ret < 1) + { + cgsleep_ms(1); // add by clement : we just wait a little time for RX data... + continue; + } + } + + if (opt_debug) { + applog(LOG_DEBUG, "BitMain: get:"); + hexdump((uint8_t *)buf, ret); + } + + memcpy(readbuf+offset, buf, ret); + offset += ret; + } + return NULL; +} + +static void bitmain_set_timeout(struct bitmain_info *info) +{ + info->timeout = BITMAIN_TIMEOUT_FACTOR / info->frequency; +} + +static void *bitmain_send_tasks(void *userdata) +{ + return NULL; +} + +static void bitmain_init(struct cgpu_info *bitmain) +{ + applog(LOG_INFO, "BitMain: Opened on %s", bitmain->device_path); +} + +static bool bitmain_prepare(struct thr_info *thr) +{ + struct cgpu_info *bitmain = thr->cgpu; + struct bitmain_info *info = bitmain->device_data; + + free(bitmain->works); + bitmain->works = calloc(BITMAIN_MAX_WORK_NUM * sizeof(struct work *), + BITMAIN_ARRAY_SIZE); + if (!bitmain->works) + quit(1, "Failed to calloc bitmain works in bitmain_prepare"); + + info->thr = thr; + mutex_init(&info->lock); + mutex_init(&info->qlock); + if (unlikely(pthread_cond_init(&info->qcond, NULL))) + quit(1, "Failed to pthread_cond_init bitmain qcond"); + cgsem_init(&info->write_sem); + + if (pthread_create(&info->read_thr, NULL, bitmain_get_results, (void *)bitmain)) + quit(1, "Failed to create bitmain read_thr"); + + //if (pthread_create(&info->write_thr, NULL, bitmain_send_tasks, (void *)bitmain)) + // quit(1, "Failed to create bitmain write_thr"); + + bitmain_init(bitmain); + + return true; +} + +static int bitmain_initialize(struct cgpu_info *bitmain) +{ + uint8_t data[BITMAIN_READBUF_SIZE]; + struct bitmain_info *info = NULL; + int ret = 0, spare = 0; + uint8_t sendbuf[BITMAIN_SENDBUF_SIZE]; + int readlen = 0; + int sendlen = 0; + int trycount = 3; + struct timespec p; + struct bitmain_rxstatus_data rxstatusdata; + int i = 0, j = 0, m = 0, r = 0, statusok = 0; + uint32_t checkbit = 0x00000000; + int hwerror_eft = 0; + int beeper_ctrl = 1; + int tempover_ctrl = 1; + int home_mode = 0; + struct bitmain_packet_head packethead; + int asicnum = 0; + int mod = 0,tmp = 0; + + /* Send reset, then check for result */ + if(!bitmain) { + applog(LOG_WARNING, "bitmain_initialize cgpu_info is null"); + return -1; + } + info = bitmain->device_data; + + /* clear read buf */ + ret = bitmain_read(bitmain, data, BITMAIN_READBUF_SIZE, + BITMAIN_RESET_TIMEOUT, C_BITMAIN_READ); + if(ret > 0) { + if (opt_debug) { + applog(LOG_DEBUG, "BTM%d Clear Read(%d):", bitmain->device_id, ret); + hexdump(data, ret); + } + } + + sendlen = bitmain_set_rxstatus((struct bitmain_rxstatus_token *)sendbuf, 0, 1, 0, 0); + if(sendlen <= 0) { + applog(LOG_ERR, "bitmain_initialize bitmain_set_rxstatus error(%d)", sendlen); + return -1; + } + + ret = bitmain_send_data(sendbuf, sendlen, bitmain); + if (unlikely(ret == BTM_SEND_ERROR)) { + applog(LOG_ERR, "bitmain_initialize bitmain_send_data error"); + return -1; + } + while(trycount >= 0) { + ret = bitmain_read(bitmain, data+readlen, BITMAIN_READBUF_SIZE, BITMAIN_RESET_TIMEOUT, C_BITMAIN_DATA_RXSTATUS); + if(ret > 0) { + readlen += ret; + if(readlen > BITMAIN_READ_SIZE) { + for(i = 0; i < readlen; i++) { + if(data[i] == 0xa1) { + if (opt_debug) { + applog(LOG_DEBUG, "%s%d initset: get:", bitmain->drv->name, bitmain->device_id); + hexdump(data, readlen); + } + memcpy(&packethead, data+i, sizeof(struct bitmain_packet_head)); + packethead.length = htole16(packethead.length); + + if(packethead.length > 1130) { + applog(LOG_ERR, "bitmain_initialize rxstatus datalen=%d error", packethead.length+4); + continue; + } + if(readlen-i < packethead.length+4) { + applog(LOG_ERR, "bitmain_initialize rxstatus datalen=%d<%d low", readlen-i, packethead.length+4); + continue; + } + if (bitmain_parse_rxstatus(data+i, packethead.length+4, &rxstatusdata) != 0) { + applog(LOG_ERR, "bitmain_initialize bitmain_parse_rxstatus error"); + continue; + } + info->chain_num = rxstatusdata.chain_num; + info->fifo_space = rxstatusdata.fifo_space; + info->hw_version[0] = rxstatusdata.hw_version[0]; + info->hw_version[1] = rxstatusdata.hw_version[1]; + info->hw_version[2] = rxstatusdata.hw_version[2]; + info->hw_version[3] = rxstatusdata.hw_version[3]; + info->nonce_error = 0; + info->last_nonce_error = 0; + sprintf(g_miner_version, "%d.%d.%d.%d", info->hw_version[0], info->hw_version[1], info->hw_version[2], info->hw_version[3]); + applog(LOG_ERR, "bitmain_initialize rxstatus v(%d) chain(%d) fifo(%d) hwv1(%d) hwv2(%d) hwv3(%d) hwv4(%d) nerr(%d) freq=%d", + rxstatusdata.version, info->chain_num, info->fifo_space, info->hw_version[0], info->hw_version[1], info->hw_version[2], info->hw_version[3], + rxstatusdata.nonce_error, info->frequency); + + memcpy(info->chain_asic_exist, rxstatusdata.chain_asic_exist, BITMAIN_MAX_CHAIN_NUM*32); + memcpy(info->chain_asic_status, rxstatusdata.chain_asic_status, BITMAIN_MAX_CHAIN_NUM*32); + for(i = 0; i < rxstatusdata.chain_num; i++) { + info->chain_asic_num[i] = rxstatusdata.chain_asic_num[i]; + memset(info->chain_asic_status_t[i], 0, 320); + j = 0; + mod = 0; + + if(info->chain_asic_num[i] <= 0) { + asicnum = 0; + } else { + mod = info->chain_asic_num[i] % 32; + if(mod == 0) { + asicnum = info->chain_asic_num[i] / 32; + } else { + asicnum = info->chain_asic_num[i] / 32 + 1; + } + } + if(asicnum > 0) { + for(m = asicnum-1; m >= 0; m--) { + tmp = mod ? (32-mod):0; + for(r = tmp;r < 32;r++){ + if((r-tmp)%8 == 0 && (r-tmp) !=0){ + info->chain_asic_status_t[i][j] = ' '; + j++; + } + checkbit = num2bit(r); + if(rxstatusdata.chain_asic_exist[i*8+m] & checkbit) { + if(rxstatusdata.chain_asic_status[i*8+m] & checkbit) { + info->chain_asic_status_t[i][j] = 'o'; + } else { + info->chain_asic_status_t[i][j] = 'x'; + } + } else { + info->chain_asic_status_t[i][j] = '-'; + } + j++; + } + info->chain_asic_status_t[i][j] = ' '; + j++; + mod = 0; + } + } + applog(LOG_DEBUG, "bitmain_initialize chain(%d) asic_num=%d asic_exist=%08x%08x%08x%08x%08x%08x%08x%08x asic_status=%08x%08x%08x%08x%08x%08x%08x%08x", + i, info->chain_asic_num[i], + info->chain_asic_exist[i*8+0], info->chain_asic_exist[i*8+1], info->chain_asic_exist[i*8+2], info->chain_asic_exist[i*8+3], info->chain_asic_exist[i*8+4], info->chain_asic_exist[i*8+5], info->chain_asic_exist[i*8+6], info->chain_asic_exist[i*8+7], + info->chain_asic_status[i*8+0], info->chain_asic_status[i*8+1], info->chain_asic_status[i*8+2], info->chain_asic_status[i*8+3], info->chain_asic_status[i*8+4], info->chain_asic_status[i*8+5], info->chain_asic_status[i*8+6], info->chain_asic_status[i*8+7]); + applog(LOG_ERR, "bitmain_initialize chain(%d) asic_num=%d asic_status=%s", i, info->chain_asic_num[i], info->chain_asic_status_t[i]); + } + bitmain_update_temps(bitmain, info, &rxstatusdata); + statusok = 1; + break; + } + } + if(statusok) { + break; + } + } + } + trycount--; + p.tv_sec = 0; + p.tv_nsec = BITMAIN_RESET_PITCH; + nanosleep(&p, NULL); + } + + p.tv_sec = 0; + p.tv_nsec = BITMAIN_RESET_PITCH; + nanosleep(&p, NULL); + + cgtime(&info->last_status_time); + + if(statusok) { + applog(LOG_ERR, "bitmain_initialize start send txconfig"); + if(opt_bitmain_hwerror) + hwerror_eft = 1; + else + hwerror_eft = 0; + if(opt_bitmain_nobeeper) + beeper_ctrl = 0; + else + beeper_ctrl = 1; + if(opt_bitmain_notempoverctrl) + tempover_ctrl = 0; + else + tempover_ctrl = 1; + if(opt_bitmain_homemode) + home_mode= 1; + else + home_mode= 0; + sendlen = bitmain_set_txconfig((struct bitmain_txconfig_token *)sendbuf, 1, 1, 1, 1, 1, 0, 1, hwerror_eft, beeper_ctrl, tempover_ctrl,home_mode, + info->chain_num, info->asic_num, BITMAIN_DEFAULT_FAN_MAX_PWM, info->timeout, + info->frequency, info->voltage, 0, 0, 0x04, info->reg_data); + if(sendlen <= 0) { + applog(LOG_ERR, "bitmain_initialize bitmain_set_txconfig error(%d)", sendlen); + return -1; + } + + ret = bitmain_send_data(sendbuf, sendlen, bitmain); + if (unlikely(ret == BTM_SEND_ERROR)) { + applog(LOG_ERR, "bitmain_initialize bitmain_send_data error"); + return -1; + } + applog(LOG_WARNING, "BMM%d: InitSet succeeded", bitmain->device_id); + } else { + applog(LOG_WARNING, "BMS%d: InitSet error", bitmain->device_id); + return -1; + } + return 0; +} + +static void bitmain_usb_init(struct cgpu_info *bitmain) +{ + int err, interface; + +#ifndef WIN32 + return; +#endif + + if (bitmain->usbinfo.nodev) + return; + + interface = usb_interface(bitmain); + + // Reset + err = usb_transfer(bitmain, FTDI_TYPE_OUT, FTDI_REQUEST_RESET, + FTDI_VALUE_RESET, interface, C_RESET); + + applog(LOG_DEBUG, "%s%i: reset got err %d", + bitmain->drv->name, bitmain->device_id, err); + + if (bitmain->usbinfo.nodev) + return; + + // Set latency + err = usb_transfer(bitmain, FTDI_TYPE_OUT, FTDI_REQUEST_LATENCY, + BITMAIN_LATENCY, interface, C_LATENCY); + + applog(LOG_DEBUG, "%s%i: latency got err %d", + bitmain->drv->name, bitmain->device_id, err); + + if (bitmain->usbinfo.nodev) + return; + + // Set data + err = usb_transfer(bitmain, FTDI_TYPE_OUT, FTDI_REQUEST_DATA, + FTDI_VALUE_DATA_BTM, interface, C_SETDATA); + + applog(LOG_DEBUG, "%s%i: data got err %d", + bitmain->drv->name, bitmain->device_id, err); + + if (bitmain->usbinfo.nodev) + return; + + // Set the baud + err = usb_transfer(bitmain, FTDI_TYPE_OUT, FTDI_REQUEST_BAUD, FTDI_VALUE_BAUD_BTM, + (FTDI_INDEX_BAUD_BTM & 0xff00) | interface, + C_SETBAUD); + + applog(LOG_DEBUG, "%s%i: setbaud got err %d", + bitmain->drv->name, bitmain->device_id, err); + + if (bitmain->usbinfo.nodev) + return; + + // Set Modem Control + err = usb_transfer(bitmain, FTDI_TYPE_OUT, FTDI_REQUEST_MODEM, + FTDI_VALUE_MODEM, interface, C_SETMODEM); + + applog(LOG_DEBUG, "%s%i: setmodemctrl got err %d", + bitmain->drv->name, bitmain->device_id, err); + + if (bitmain->usbinfo.nodev) + return; + + // Set Flow Control + err = usb_transfer(bitmain, FTDI_TYPE_OUT, FTDI_REQUEST_FLOW, + FTDI_VALUE_FLOW, interface, C_SETFLOW); + + applog(LOG_DEBUG, "%s%i: setflowctrl got err %d", + bitmain->drv->name, bitmain->device_id, err); + + if (bitmain->usbinfo.nodev) + return; + + /* BitMain repeats the following */ + // Set Modem Control + err = usb_transfer(bitmain, FTDI_TYPE_OUT, FTDI_REQUEST_MODEM, + FTDI_VALUE_MODEM, interface, C_SETMODEM); + + applog(LOG_DEBUG, "%s%i: setmodemctrl 2 got err %d", + bitmain->drv->name, bitmain->device_id, err); + + if (bitmain->usbinfo.nodev) + return; + + // Set Flow Control + err = usb_transfer(bitmain, FTDI_TYPE_OUT, FTDI_REQUEST_FLOW, + FTDI_VALUE_FLOW, interface, C_SETFLOW); + + applog(LOG_DEBUG, "%s%i: setflowctrl 2 got err %d", + bitmain->drv->name, bitmain->device_id, err); +} + +static struct cgpu_info * bitmain_usb_detect_one(libusb_device *dev, struct usb_find_devices *found) +{ + int baud, chain_num, asic_num, timeout, frequency = 0; + char frequency_t[256] = {0}; + uint8_t reg_data[4] = {0}; + uint8_t voltage[2] = {0}; + char voltage_t[8] = {0}; + int this_option_offset = ++option_offset; + struct bitmain_info *info; + struct cgpu_info *bitmain; + bool configured; + int ret; + + if (opt_bitmain_options == NULL) + return NULL; + + bitmain = usb_alloc_cgpu(&bitmain_drv, BITMAIN_MINER_THREADS); + + baud = BITMAIN_IO_SPEED; + chain_num = BITMAIN_DEFAULT_CHAIN_NUM; + asic_num = BITMAIN_DEFAULT_ASIC_NUM; + timeout = BITMAIN_DEFAULT_TIMEOUT; + frequency = BITMAIN_DEFAULT_FREQUENCY; + + if (!usb_init(bitmain, dev, found)) + goto shin; + + configured = get_options(this_option_offset, &baud, &chain_num, + &asic_num, &timeout, &frequency, frequency_t, reg_data, voltage, voltage_t); + get_option_freq(&timeout, &frequency, frequency_t, reg_data); + get_option_voltage(voltage, voltage_t); + + /* Even though this is an FTDI type chip, we want to do the parsing + * all ourselves so set it to std usb type */ + bitmain->usbdev->usb_type = USB_TYPE_STD; + + /* We have a real BitMain! */ + bitmain_usb_init(bitmain); + + bitmain->device_data = calloc(sizeof(struct bitmain_info), 1); + if (unlikely(!(bitmain->device_data))) + quit(1, "Failed to calloc bitmain_info data"); + info = bitmain->device_data; + + if (configured) { + info->baud = baud; + info->chain_num = chain_num; + info->asic_num = asic_num; + info->timeout = timeout; + info->frequency = frequency; + strcpy(info->frequency_t, frequency_t); + memcpy(info->reg_data, reg_data, 4); + memcpy(info->voltage, voltage, 2); + strcpy(info->voltage_t, voltage_t); + } else { + info->baud = BITMAIN_IO_SPEED; + info->chain_num = BITMAIN_DEFAULT_CHAIN_NUM; + info->asic_num = BITMAIN_DEFAULT_ASIC_NUM; + info->timeout = BITMAIN_DEFAULT_TIMEOUT; + info->frequency = BITMAIN_DEFAULT_FREQUENCY; + sprintf(info->frequency_t, "%d", BITMAIN_DEFAULT_FREQUENCY); + memset(info->reg_data, 0, 4); + info->voltage[0] = BITMAIN_DEFAULT_VOLTAGE0; + info->voltage[1] = BITMAIN_DEFAULT_VOLTAGE1; + strcpy(info->voltage_t, BITMAIN_DEFAULT_VOLTAGE_T); + } + + info->fan_pwm = BITMAIN_DEFAULT_FAN_MIN_PWM; + info->temp_max = 0; + /* This is for check the temp/fan every 3~4s */ + info->temp_history_count = (4 / (float)((float)info->timeout * ((float)1.67/0x32))) + 1; + if (info->temp_history_count <= 0) + info->temp_history_count = 1; + + info->temp_history_index = 0; + info->temp_sum = 0; + info->temp_old = 0; + + if (!add_cgpu(bitmain)) + goto unshin; + + applog(LOG_ERR, "------bitmain usb detect one------"); + ret = bitmain_initialize(bitmain); + if (ret && !configured) + goto unshin; + + update_usb_stats(bitmain); + + info->errorcount = 0; + + applog(LOG_DEBUG, "BitMain Detected: %s " + "(chain_num=%d asic_num=%d timeout=%d frequency=%d)", + bitmain->device_path, info->chain_num, info->asic_num, info->timeout, + info->frequency); + + return bitmain; + +unshin: + + usb_uninit(bitmain); + +shin: + + free(bitmain->device_data); + bitmain->device_data = NULL; + + bitmain = usb_free_cgpu(bitmain); + + return NULL; +} + +static bool bitmain_detect_one(const char * devpath) +{ + int baud, chain_num, asic_num, timeout, frequency = 0; + char frequency_t[256] = {0}; + uint8_t reg_data[4] = {0}; + uint8_t voltage[2] = {0}; + char voltage_t[8] = {0}; + int this_option_offset = ++option_offset; + struct bitmain_info *info; + struct cgpu_info *bitmain; + bool configured; + int ret; + + if (opt_bitmain_options == NULL) + return false; + + bitmain = btm_alloc_cgpu(&bitmain_drv, BITMAIN_MINER_THREADS); + + configured = get_options(this_option_offset, &baud, &chain_num, + &asic_num, &timeout, &frequency, frequency_t, reg_data, voltage, voltage_t); + get_option_freq(&timeout, &frequency, frequency_t, reg_data); + get_option_voltage(voltage, voltage_t); + + if (!btm_init(bitmain, opt_bitmain_dev)) + goto shin; + applog(LOG_ERR, "bitmain_detect_one btm init ok"); + + bitmain->device_data = calloc(sizeof(struct bitmain_info), 1); + /* make sure initialize successfully*/ + memset(bitmain->device_data,0,sizeof(struct bitmain_info)); + if (unlikely(!(bitmain->device_data))) + quit(1, "Failed to calloc bitmain_info data"); + info = bitmain->device_data; + + if (configured) { + info->baud = baud; + info->chain_num = chain_num; + info->asic_num = asic_num; + info->timeout = timeout; + info->frequency = frequency; + strcpy(info->frequency_t, frequency_t); + memcpy(info->reg_data, reg_data, 4); + memcpy(info->voltage, voltage, 2); + strcpy(info->voltage_t, voltage_t); + } else { + info->baud = BITMAIN_IO_SPEED; + info->chain_num = BITMAIN_DEFAULT_CHAIN_NUM; + info->asic_num = BITMAIN_DEFAULT_ASIC_NUM; + info->timeout = BITMAIN_DEFAULT_TIMEOUT; + info->frequency = BITMAIN_DEFAULT_FREQUENCY; + sprintf(info->frequency_t, "%d", BITMAIN_DEFAULT_FREQUENCY); + memset(info->reg_data, 0, 4); + info->voltage[0] = BITMAIN_DEFAULT_VOLTAGE0; + info->voltage[1] = BITMAIN_DEFAULT_VOLTAGE1; + strcpy(info->voltage_t, BITMAIN_DEFAULT_VOLTAGE_T); + } + + info->fan_pwm = BITMAIN_DEFAULT_FAN_MIN_PWM; + info->temp_max = 0; + /* This is for check the temp/fan every 3~4s */ + info->temp_history_count = (4 / (float)((float)info->timeout * ((float)1.67/0x32))) + 1; + if (info->temp_history_count <= 0) + info->temp_history_count = 1; + + info->temp_history_index = 0; + info->temp_sum = 0; + info->temp_old = 0; + + if (!add_cgpu(bitmain)) + goto unshin; + + ret = bitmain_initialize(bitmain); + applog(LOG_ERR, "bitmain_detect_one stop bitmain_initialize %d", ret); + if (ret && !configured) + goto unshin; + + info->errorcount = 0; + + applog(LOG_ERR, "BitMain Detected: %s " + "(chain_num=%d asic_num=%d timeout=%d freq=%d-%s volt=%02x%02x-%s)", + bitmain->device_path, info->chain_num, info->asic_num, info->timeout, + info->frequency, info->frequency_t, info->voltage[0], info->voltage[1], info->voltage_t); + + return true; + +unshin: + btm_uninit(bitmain); + +shin: + free(bitmain->device_data); + bitmain->device_data = NULL; + + bitmain = usb_free_cgpu(bitmain); + + return false; +} + +static void bitmain_detect(bool __maybe_unused hotplug) +{ + applog(LOG_DEBUG, "BTM detect dev: %s", opt_bitmain_dev); + if(strlen(opt_bitmain_dev) <= 0) { + opt_bitmain_dev_usb = true; + } else { + opt_bitmain_dev_usb = false; + } + if(opt_bitmain_dev_usb) { + usb_detect(&bitmain_drv, bitmain_usb_detect_one); + } else { + btm_detect(&bitmain_drv, bitmain_detect_one); + } +} + +static void do_bitmain_close(struct thr_info *thr) +{ + struct cgpu_info *bitmain = thr->cgpu; + struct bitmain_info *info = bitmain->device_data; + + pthread_join(info->read_thr, NULL); + pthread_join(info->write_thr, NULL); + bitmain_running_reset(bitmain, info); + + info->no_matching_work = 0; + + cgsem_destroy(&info->write_sem); +} + +static void get_bitmain_statline_before(char *buf, size_t bufsiz, struct cgpu_info *bitmain) +{ + struct bitmain_info *info = bitmain->device_data; + int lowfan = 10000; + int i = 0; + + /* Find the lowest fan speed of the ASIC cooling fans. */ + for(i = 0; i < info->fan_num; i++) { + if (info->fan[i] >= 0 && info->fan[i] < lowfan) + lowfan = info->fan[i]; + } + + tailsprintf(buf, bufsiz, "%2d/%3dC %04dR | ", info->temp_avg, info->temp_max, lowfan); +} + +/* We use a replacement algorithm to only remove references to work done from + * the buffer when we need the extra space for new work. */ +static bool bitmain_fill(struct cgpu_info *bitmain) +{ + struct bitmain_info *info = bitmain->device_data; + int subid, slot; + struct work *work; + bool ret = true; + int sendret = 0, sendcount = 0, neednum = 0, queuednum = 0, sendnum = 0, sendlen = 0; + uint8_t sendbuf[BITMAIN_SENDBUF_SIZE]; + cgtimer_t ts_start; + int senderror = 0; + struct timeval now; + int timediff = 0; + int needwait=0; // add by clement. use a flag to indicate need sleep or not. + + //applog(LOG_DEBUG, "BTM bitmain_fill start--------"); + mutex_lock(&info->qlock); + if(info->fifo_space <= 0) { + //applog(LOG_DEBUG, "BTM bitmain_fill fifo space empty--------"); + ret = true; + needwait=1; // add by clement. DEVICE FIFO is full, no space for new works. So we need sleep. + goto out_unlock; + } + if (bitmain->queued >= BITMAIN_MAX_WORK_QUEUE_NUM) { + ret = true; + } else { + ret = false; + } + while(info->fifo_space > 0) { + neednum = info->fifo_spacefifo_space:BITMAIN_MAX_WORK_NUM; + queuednum = bitmain->queued; + applog(LOG_DEBUG, "BTM: Work task queued(%d) fifo space(%d) needsend(%d)", queuednum, info->fifo_space, neednum); + if(queuednum < neednum) { + while(true) { + work = get_queued(bitmain); + if (unlikely(!work)) { + break; + } else { + applog(LOG_DEBUG, "BTM get work queued number:%d neednum:%d", queuednum, neednum); + subid = bitmain->queued++; + work->subid = subid; + slot = bitmain->work_array + subid; + if (slot >= BITMAIN_ARRAY_SIZE) { // slot=edited by clement , old code is if (slot > BITMAIN_ARRAY_SIZE), not sure ,just fixed it. + applog(LOG_DEBUG, "bitmain_fill array cyc %d", BITMAIN_ARRAY_SIZE); + slot = 0; + } + if (likely(bitmain->works[slot])) { + applog(LOG_DEBUG, "bitmain_fill work_completed %d", slot); + work_completed(bitmain, bitmain->works[slot]); + } + bitmain->works[slot] = work; + queuednum++; + if(queuednum >= neednum) { + break; + } + } + } + } + if(queuednum < BITMAIN_MAX_DEAL_QUEUE_NUM) { + /* by clement + if(queuednum < neednum) { + applog(LOG_DEBUG, "BTM: No enough work to send, queue num=%d", queuednum); + break; + } + */ + + needwait=1; // if queuednum is not enough, we just wait and sleep. queuednum must be >= BITMAIN_MAX_DEAL_QUEUE_NUM, then send to device + break; + } + + sendnum = queuednum < neednum ? queuednum : neednum; + sendlen = bitmain_set_txtask(sendbuf, &(info->last_work_block), bitmain->works, BITMAIN_ARRAY_SIZE, bitmain->work_array, sendnum, &sendcount); + bitmain->queued -= sendnum; + info->send_full_space += sendnum; + if (bitmain->queued < 0) + bitmain->queued = 0; + if (bitmain->work_array + sendnum > BITMAIN_ARRAY_SIZE) { + bitmain->work_array = bitmain->work_array + sendnum-BITMAIN_ARRAY_SIZE; + } else { + bitmain->work_array += sendnum; + } + applog(LOG_DEBUG, "BTM: Send work array %d", bitmain->work_array); + if (sendlen > 0) { + info->fifo_space -= sendcount; + if (info->fifo_space < 0) + info->fifo_space = 0; + sendret = bitmain_send_data(sendbuf, sendlen, bitmain); + if (unlikely(sendret == BTM_SEND_ERROR)) { + applog(LOG_ERR, "BTM%i: Comms error(buffer)", bitmain->device_id); + //dev_error(bitmain, REASON_DEV_COMMS_ERROR); + info->reset = true; + info->errorcount++; + senderror = 1; + if (info->errorcount > 1000) { + info->errorcount = 0; + applog(LOG_ERR, "%s%d: Device disappeared, shutting down thread", bitmain->drv->name, bitmain->device_id); + bitmain->shutdown = true; + } + break; + } else { + applog(LOG_DEBUG, "bitmain_send_data send ret=%d", sendret); + info->errorcount = 0; + } + } else { + applog(LOG_DEBUG, "BTM: Send work bitmain_set_txtask error: %d", sendlen); + break; + } + } + +out_unlock: + cgtime(&now); + timediff = now.tv_sec - info->last_status_time.tv_sec; + if(timediff < 0) timediff = -timediff; + if (timediff > BITMAIN_SEND_STATUS_TIME) { + applog(LOG_DEBUG, "BTM: Send RX Status Token fifo_space(%d) timediff(%d)", info->fifo_space, timediff); + copy_time(&(info->last_status_time), &now); + + sendlen = bitmain_set_rxstatus((struct bitmain_rxstatus_token *) sendbuf, 0, 0, 0, 0); + if (sendlen > 0) { + sendret = bitmain_send_data(sendbuf, sendlen, bitmain); + if (unlikely(sendret == BTM_SEND_ERROR)) { + applog(LOG_ERR, "BTM%i: Comms error(buffer)", bitmain->device_id); + //dev_error(bitmain, REASON_DEV_COMMS_ERROR); + info->reset = true; + info->errorcount++; + senderror = 1; + if (info->errorcount > 1000) { + info->errorcount = 0; + applog(LOG_ERR, "%s%d: Device disappeared, shutting down thread", bitmain->drv->name, bitmain->device_id); + bitmain->shutdown = true; + } + } else { + info->errorcount = 0; + if (info->fifo_space <= 0) { + senderror = 1; + } + } + } + } + + if(info->send_full_space > BITMAIN_SEND_FULL_SPACE) { + info->send_full_space = 0; + mutex_unlock(&info->qlock); // add by clement. we need unlock first, then sleep. So we can let other thread run as soon as possible. + + ret = true; + cgsleep_ms(1); // just sleep a liitle. + } + else + { + mutex_unlock(&info->qlock); // add by clement. we need unlock first, then sleep. So we can let other thread run as soon as possible. + + if(needwait) + cgsleep_ms(1); // add by clement. if there is no work on queue, we need wait for a little time. Or this thread will hold CPU near 99%. + // In fact, we need more time in gen_hash thread!!! + } + + if(senderror) { + ret = true; + applog(LOG_DEBUG, "bitmain_fill send task sleep"); + //cgsleep_ms(1); + } + return ret; +} + +static int64_t bitmain_scanhash(struct thr_info *thr) +{ + struct cgpu_info *bitmain = thr->cgpu; + struct bitmain_info *info = bitmain->device_data; + const int chain_num = info->chain_num; + struct timeval now, then, tdiff; + int64_t hash_count, us_timeout; + struct timespec abstime; + int ret; + + /* Half nonce range */ + us_timeout = 0x80000000ll / info->asic_num / info->frequency; + tdiff.tv_sec = us_timeout / 1000000; + tdiff.tv_usec = us_timeout - (tdiff.tv_sec * 1000000); + cgtime(&now); + timeradd(&now, &tdiff, &then); + abstime.tv_sec = then.tv_sec; + abstime.tv_nsec = then.tv_usec * 1000; + + //applog(LOG_DEBUG, "bitmain_scanhash info->qlock start"); + mutex_lock(&info->qlock); + hash_count = 0xffffffffull * (uint64_t)info->nonces; + bitmain->results += info->nonces + info->idle; + if (bitmain->results > chain_num) + bitmain->results = chain_num; + if (!info->reset) + bitmain->results--; + info->nonces = info->idle = 0; + mutex_unlock(&info->qlock); + //applog(LOG_DEBUG, "bitmain_scanhash info->qlock stop"); + + /* Check for nothing but consecutive bad results or consistently less + * results than we should be getting and reset the FPGA if necessary */ + //if (bitmain->results < -chain_num && !info->reset) { + // applog(LOG_ERR, "BTM%d: Result return rate low, resetting!", + // bitmain->device_id); + // info->reset = true; + //} + + if (unlikely(bitmain->usbinfo.nodev)) { + applog(LOG_ERR, "BTM%d: Device disappeared, shutting down thread", + bitmain->device_id); + bitmain->shutdown = true; + } + + /* This hashmeter is just a utility counter based on returned shares */ + return hash_count; +} + +static void bitmain_flush_work(struct cgpu_info *bitmain) +{ + struct bitmain_info *info = bitmain->device_data; + int i = 0; + + mutex_lock(&info->qlock); + /* Will overwrite any work queued */ + applog(LOG_ERR, "bitmain_flush_work queued=%d array=%d", bitmain->queued, bitmain->work_array); + if(bitmain->queued > 0) { + if (bitmain->work_array + bitmain->queued > BITMAIN_ARRAY_SIZE) { + bitmain->work_array = bitmain->work_array + bitmain->queued-BITMAIN_ARRAY_SIZE; + } else { + bitmain->work_array += bitmain->queued; + } + } + bitmain->queued = 0; + //bitmain->work_array = 0; + //for(i = 0; i < BITMAIN_ARRAY_SIZE; i++) { + // bitmain->works[i] = NULL; + //} + //pthread_cond_signal(&info->qcond); + mutex_unlock(&info->qlock); +} + +static struct api_data *bitmain_api_stats(struct cgpu_info *cgpu) +{ + struct api_data *root = NULL; + struct bitmain_info *info = cgpu->device_data; + char buf[64]; + int i = 0; + double hwp = (cgpu->hw_errors + cgpu->diff1) ? + (double)(cgpu->hw_errors) / (double)(cgpu->hw_errors + cgpu->diff1) : 0; + + root = api_add_int(root, "baud", &(info->baud), false); + root = api_add_int(root, "miner_count", &(info->chain_num), false); + root = api_add_int(root, "asic_count", &(info->asic_num), false); + root = api_add_int(root, "timeout", &(info->timeout), false); + root = api_add_string(root, "frequency", info->frequency_t, false); + root = api_add_string(root, "voltage", info->voltage_t, false); + root = api_add_int(root, "hwv1", &(info->hw_version[0]), false); + root = api_add_int(root, "hwv2", &(info->hw_version[1]), false); + root = api_add_int(root, "hwv3", &(info->hw_version[2]), false); + root = api_add_int(root, "hwv4", &(info->hw_version[3]), false); + + root = api_add_int(root, "fan_num", &(info->fan_num), false); + root = api_add_int(root, "fan1", &(info->fan[0]), false); + root = api_add_int(root, "fan2", &(info->fan[1]), false); + root = api_add_int(root, "fan3", &(info->fan[2]), false); + root = api_add_int(root, "fan4", &(info->fan[3]), false); + root = api_add_int(root, "fan5", &(info->fan[4]), false); + root = api_add_int(root, "fan6", &(info->fan[5]), false); + root = api_add_int(root, "fan7", &(info->fan[6]), false); + root = api_add_int(root, "fan8", &(info->fan[7]), false); + root = api_add_int(root, "fan9", &(info->fan[8]), false); + root = api_add_int(root, "fan10", &(info->fan[9]), false); + root = api_add_int(root, "fan11", &(info->fan[10]), false); + root = api_add_int(root, "fan12", &(info->fan[11]), false); + root = api_add_int(root, "fan13", &(info->fan[12]), false); + root = api_add_int(root, "fan14", &(info->fan[13]), false); + root = api_add_int(root, "fan15", &(info->fan[14]), false); + root = api_add_int(root, "fan16", &(info->fan[15]), false); + + root = api_add_int(root, "temp_num", &(info->temp_num), false); + root = api_add_int(root, "temp1", &(info->temp[0]), false); + root = api_add_int(root, "temp2", &(info->temp[1]), false); + root = api_add_int(root, "temp3", &(info->temp[2]), false); + root = api_add_int(root, "temp4", &(info->temp[3]), false); + root = api_add_int(root, "temp5", &(info->temp[4]), false); + root = api_add_int(root, "temp6", &(info->temp[5]), false); + root = api_add_int(root, "temp7", &(info->temp[6]), false); + root = api_add_int(root, "temp8", &(info->temp[7]), false); + root = api_add_int(root, "temp9", &(info->temp[8]), false); + root = api_add_int(root, "temp10", &(info->temp[9]), false); + root = api_add_int(root, "temp11", &(info->temp[10]), false); + root = api_add_int(root, "temp12", &(info->temp[11]), false); + root = api_add_int(root, "temp13", &(info->temp[12]), false); + root = api_add_int(root, "temp14", &(info->temp[13]), false); + root = api_add_int(root, "temp15", &(info->temp[14]), false); + root = api_add_int(root, "temp16", &(info->temp[15]), false); + root = api_add_int(root, "temp_avg", &(info->temp_avg), false); + root = api_add_int(root, "temp_max", &(info->temp_max), false); + root = api_add_percent(root, "Device Hardware%", &hwp, true); + root = api_add_int(root, "no_matching_work", &(info->no_matching_work), false); + /* + for (i = 0; i < info->chain_num; i++) { + char mcw[24]; + + sprintf(mcw, "match_work_count%d", i + 1); + root = api_add_int(root, mcw, &(info->matching_work[i]), false); + }*/ + + root = api_add_int(root, "chain_acn1", &(info->chain_asic_num[0]), false); + root = api_add_int(root, "chain_acn2", &(info->chain_asic_num[1]), false); + root = api_add_int(root, "chain_acn3", &(info->chain_asic_num[2]), false); + root = api_add_int(root, "chain_acn4", &(info->chain_asic_num[3]), false); + root = api_add_int(root, "chain_acn5", &(info->chain_asic_num[4]), false); + root = api_add_int(root, "chain_acn6", &(info->chain_asic_num[5]), false); + root = api_add_int(root, "chain_acn7", &(info->chain_asic_num[6]), false); + root = api_add_int(root, "chain_acn8", &(info->chain_asic_num[7]), false); + root = api_add_int(root, "chain_acn9", &(info->chain_asic_num[8]), false); + root = api_add_int(root, "chain_acn10", &(info->chain_asic_num[9]), false); + root = api_add_int(root, "chain_acn11", &(info->chain_asic_num[10]), false); + root = api_add_int(root, "chain_acn12", &(info->chain_asic_num[11]), false); + root = api_add_int(root, "chain_acn13", &(info->chain_asic_num[12]), false); + root = api_add_int(root, "chain_acn14", &(info->chain_asic_num[13]), false); + root = api_add_int(root, "chain_acn15", &(info->chain_asic_num[14]), false); + root = api_add_int(root, "chain_acn16", &(info->chain_asic_num[15]), false); + + //applog(LOG_ERR, "chain asic status:%s", info->chain_asic_status_t[0]); + root = api_add_string(root, "chain_acs1", info->chain_asic_status_t[0], false); + root = api_add_string(root, "chain_acs2", info->chain_asic_status_t[1], false); + root = api_add_string(root, "chain_acs3", info->chain_asic_status_t[2], false); + root = api_add_string(root, "chain_acs4", info->chain_asic_status_t[3], false); + root = api_add_string(root, "chain_acs5", info->chain_asic_status_t[4], false); + root = api_add_string(root, "chain_acs6", info->chain_asic_status_t[5], false); + root = api_add_string(root, "chain_acs7", info->chain_asic_status_t[6], false); + root = api_add_string(root, "chain_acs8", info->chain_asic_status_t[7], false); + root = api_add_string(root, "chain_acs9", info->chain_asic_status_t[8], false); + root = api_add_string(root, "chain_acs10", info->chain_asic_status_t[9], false); + root = api_add_string(root, "chain_acs11", info->chain_asic_status_t[10], false); + root = api_add_string(root, "chain_acs12", info->chain_asic_status_t[11], false); + root = api_add_string(root, "chain_acs13", info->chain_asic_status_t[12], false); + root = api_add_string(root, "chain_acs14", info->chain_asic_status_t[13], false); + root = api_add_string(root, "chain_acs15", info->chain_asic_status_t[14], false); + root = api_add_string(root, "chain_acs16", info->chain_asic_status_t[15], false); + + //root = api_add_int(root, "chain_acs1", &(info->chain_asic_status[0]), false); + //root = api_add_int(root, "chain_acs2", &(info->chain_asic_status[1]), false); + //root = api_add_int(root, "chain_acs3", &(info->chain_asic_status[2]), false); + //root = api_add_int(root, "chain_acs4", &(info->chain_asic_status[3]), false); + + return root; +} + +static void bitmain_shutdown(struct thr_info *thr) +{ + do_bitmain_close(thr); +} + +char *set_bitmain_dev(char *arg) +{ + if(arg == NULL || strlen(arg) <= 0) { + memcpy(opt_bitmain_dev, 0, 256); + } else { + strncpy(opt_bitmain_dev, arg, 256); + } + applog(LOG_DEBUG, "BTM set device: %s", opt_bitmain_dev); + return NULL; +} + +char *set_bitmain_fan(char *arg) +{ + int val1, val2, ret; + + ret = sscanf(arg, "%d-%d", &val1, &val2); + if (ret < 1) + return "No values passed to bitmain-fan"; + if (ret == 1) + val2 = val1; + + if (val1 < 0 || val1 > 100 || val2 < 0 || val2 > 100 || val2 < val1) + return "Invalid value passed to bitmain-fan"; + + opt_bitmain_fan_min = val1 * BITMAIN_PWM_MAX / 100; + opt_bitmain_fan_max = val2 * BITMAIN_PWM_MAX / 100; + + return NULL; +} + +char *set_bitmain_freq(char *arg) +{ + int val1, val2, ret; + + ret = sscanf(arg, "%d-%d", &val1, &val2); + if (ret < 1) + return "No values passed to bitmain-freq"; + if (ret == 1) + val2 = val1; + + if (val1 < BITMAIN_MIN_FREQUENCY || val1 > BITMAIN_MAX_FREQUENCY || + val2 < BITMAIN_MIN_FREQUENCY || val2 > BITMAIN_MAX_FREQUENCY || + val2 < val1) + return "Invalid value passed to bitmain-freq"; + + opt_bitmain_freq_min = val1; + opt_bitmain_freq_max = val2; + + return NULL; +} + +struct device_drv bitmain_drv = { + .drv_id = DRIVER_bitmain, + .dname = "Bitmain", + .name = "BTM", + .drv_detect = bitmain_detect, + .thread_prepare = bitmain_prepare, + .hash_work = hash_queued_work, + .queue_full = bitmain_fill, + .scanwork = bitmain_scanhash, + .flush_work = bitmain_flush_work, + .get_api_stats = bitmain_api_stats, + .get_statline_before = get_bitmain_statline_before, + .reinit_device = bitmain_init, + .thread_shutdown = bitmain_shutdown, +}; diff --git a/driver-bitmain.h b/driver-bitmain.h new file mode 100644 index 0000000..7336fcc --- /dev/null +++ b/driver-bitmain.h @@ -0,0 +1,314 @@ +/* + * Copyright 2013 BitMain project + * Copyright 2013 BitMain + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#ifndef BITMAIN_H +#define BITMAIN_H + +#ifdef USE_BITMAIN + +#include "util.h" + +//#define BITMAIN_TYPE_S1 +//#define BITMAIN_TYPE_S2 +//#define BITMAIN_TYPE_S3 +#define BITMAIN_TYPE_S4 + +#define BITMAIN_RESET_FAULT_DECISECONDS 1 +#define BITMAIN_MINER_THREADS 1 + +#define BITMAIN_IO_SPEED 115200 +#define BITMAIN_HASH_TIME_FACTOR ((float)1.67/0x32) +#define BITMAIN_RESET_PITCH (300*1000*1000) + +#define BITMAIN_TOKEN_TYPE_TXCONFIG 0x51 +#define BITMAIN_TOKEN_TYPE_TXTASK 0x52 +#define BITMAIN_TOKEN_TYPE_RXSTATUS 0x53 + +#define BITMAIN_DATA_TYPE_RXSTATUS 0xa1 +#define BITMAIN_DATA_TYPE_RXNONCE 0xa2 + +#define BITMAIN_FAN_FACTOR 60 +#define BITMAIN_PWM_MAX 0xA0 +#define BITMAIN_DEFAULT_FAN_MIN 20 +#define BITMAIN_DEFAULT_FAN_MAX 100 +#define BITMAIN_DEFAULT_FAN_MAX_PWM 0xA0 /* 100% */ +#define BITMAIN_DEFAULT_FAN_MIN_PWM 0x20 /* 20% */ + +#define BITMAIN_TEMP_TARGET 50 +#define BITMAIN_TEMP_HYSTERESIS 3 +#define BITMAIN_TEMP_OVERHEAT 60 + +#define BITMAIN_DEFAULT_TIMEOUT 0x2D +#define BITMAIN_MIN_FREQUENCY 10 +#define BITMAIN_MAX_FREQUENCY 1000000 +#define BITMAIN_TIMEOUT_FACTOR 12690 +#define BITMAIN_DEFAULT_FREQUENCY 282 +#define BITMAIN_DEFAULT_VOLTAGE_T "0725" +#define BITMAIN_DEFAULT_VOLTAGE0 0x07 +#define BITMAIN_DEFAULT_VOLTAGE1 0x25 +#define BITMAIN_DEFAULT_CHAIN_NUM 8 +#define BITMAIN_DEFAULT_ASIC_NUM 32 +#define BITMAIN_DEFAULT_REG_DATA 0 + +#define BITMAIN_AUTO_CYCLE 1024 + +#define BITMAIN_FTDI_READSIZE 2048 +#define BITMAIN_USB_PACKETSIZE 512 +#define BITMAIN_SENDBUF_SIZE 8192 +#define BITMAIN_READBUF_SIZE 8192 +#define BITMAIN_RESET_TIMEOUT 100 +#define BITMAIN_READ_TIMEOUT 18 /* Enough to only half fill the buffer */ +#define BITMAIN_LATENCY 1 + +#ifdef BITMAIN_TYPE_S1 +#define BITMAIN_MAX_WORK_NUM 8 +#define BITMAIN_MAX_WORK_QUEUE_NUM 64 +#define BITMAIN_MAX_DEAL_QUEUE_NUM 1 +#define BITMAIN_MAX_NONCE_NUM 8 +#define BITMAIN_MAX_CHAIN_NUM 8 +#define BITMAIN_MAX_TEMP_NUM 32 +#define BITMAIN_MAX_FAN_NUM 32 +#define BITMAIN_ARRAY_SIZE 16384 +#define BITMAIN_SEND_STATUS_TIME 10 //s +#define BITMAIN_SEND_FULL_SPACE 128 +#endif + +#ifdef BITMAIN_TYPE_S2 +#define BITMAIN_MAX_WORK_NUM 64 +#define BITMAIN_MAX_WORK_QUEUE_NUM 4096 +#define BITMAIN_MAX_DEAL_QUEUE_NUM 32 +#define BITMAIN_MAX_NONCE_NUM 128 +#define BITMAIN_MAX_CHAIN_NUM 16 +#define BITMAIN_MAX_TEMP_NUM 32 +#define BITMAIN_MAX_FAN_NUM 32 +#define BITMAIN_ARRAY_SIZE 16384 +#define BITMAIN_SEND_STATUS_TIME 15 //s +#define BITMAIN_SEND_FULL_SPACE 512 +#endif + +#ifdef BITMAIN_TYPE_S3 +#define BITMAIN_MAX_WORK_NUM 8 +#define BITMAIN_MAX_WORK_QUEUE_NUM 1024 +#define BITMAIN_MAX_DEAL_QUEUE_NUM 2 +#define BITMAIN_MAX_NONCE_NUM 128 +#define BITMAIN_MAX_CHAIN_NUM 8 +#define BITMAIN_MAX_TEMP_NUM 32 +#define BITMAIN_MAX_FAN_NUM 32 +#define BITMAIN_ARRAY_SIZE 16384 +#define BITMAIN_SEND_STATUS_TIME 15 //s +#define BITMAIN_SEND_FULL_SPACE 256 +#endif + +#ifdef BITMAIN_TYPE_S4 +#define BITMAIN_MAX_WORK_NUM 64 +#define BITMAIN_MAX_WORK_QUEUE_NUM 4096 +#define BITMAIN_MAX_DEAL_QUEUE_NUM 32 +#define BITMAIN_MAX_NONCE_NUM 128 +#define BITMAIN_MAX_CHAIN_NUM 16 +#define BITMAIN_MAX_TEMP_NUM 32 +#define BITMAIN_MAX_FAN_NUM 32 +#define BITMAIN_ARRAY_SIZE 16384*2 +#define BITMAIN_SEND_STATUS_TIME 15 //s +#define BITMAIN_SEND_FULL_SPACE 512 +#endif + +struct bitmain_packet_head { + uint8_t token_type; + uint8_t version; + uint16_t length; +} __attribute__((packed, aligned(4))); + +struct bitmain_txconfig_token { + uint8_t token_type; + uint8_t version; + uint16_t length; + uint8_t reset :1; + uint8_t fan_eft :1; + uint8_t timeout_eft :1; + uint8_t frequency_eft :1; + uint8_t voltage_eft :1; + uint8_t chain_check_time_eft :1; + uint8_t chip_config_eft :1; + uint8_t hw_error_eft :1; + uint8_t beeper_ctrl :1; + uint8_t temp_over_ctrl :1; + uint8_t fan_home_mode :1; + uint8_t reserved1 :5; + uint8_t chain_check_time; + uint8_t reserved2; + + uint8_t chain_num; + uint8_t asic_num; + uint8_t fan_pwm_data; + uint8_t timeout_data; + + uint16_t frequency; + uint8_t voltage[2]; + + uint8_t reg_data[4]; + uint8_t chip_address; + uint8_t reg_address; + uint16_t crc; +} __attribute__((packed, aligned(4))); + +struct bitmain_txtask_work { + uint32_t work_id; + uint8_t midstate[32]; + uint8_t data2[12]; +} __attribute__((packed, aligned(4))); + +struct bitmain_txtask_token { + uint8_t token_type; + uint8_t version; + uint16_t length; + uint8_t new_block :1; + uint8_t reserved1 :7; + uint8_t diff; + uint16_t net_diff; + struct bitmain_txtask_work works[BITMAIN_MAX_WORK_NUM]; + uint16_t crc; +} __attribute__((packed, aligned(4))); + +struct bitmain_rxstatus_token { + uint8_t token_type; + uint8_t version; + uint16_t length; + uint8_t chip_status_eft :1; + uint8_t detect_get :1; + uint8_t reserved1 :6; + uint8_t reserved2[3]; + + uint8_t chip_address; + uint8_t reg_address; + uint16_t crc; +} __attribute__((packed, aligned(4))); + +struct bitmain_rxstatus_data { + uint8_t data_type; + uint8_t version; + uint16_t length; + uint8_t chip_value_eft :1; + uint8_t reserved1 :3; + uint8_t get_blk_num :4; + uint8_t chain_num; + uint16_t fifo_space; + uint8_t hw_version[4]; + uint8_t fan_num; + uint8_t temp_num; + uint16_t fan_exist; + uint32_t temp_exist; + uint32_t nonce_error; + uint32_t reg_value; + uint32_t chain_asic_exist[BITMAIN_MAX_CHAIN_NUM*8]; + uint32_t chain_asic_status[BITMAIN_MAX_CHAIN_NUM*8]; + uint8_t chain_asic_num[BITMAIN_MAX_CHAIN_NUM]; + uint8_t temp[BITMAIN_MAX_TEMP_NUM]; + uint8_t fan[BITMAIN_MAX_FAN_NUM]; + uint16_t crc; +} __attribute__((packed, aligned(4))); + +struct bitmain_rxnonce_nonce { + uint32_t work_id; + uint32_t nonce; +} __attribute__((packed, aligned(4))); + +struct bitmain_rxnonce_data { + uint8_t data_type; + uint8_t version; + uint16_t length; + uint16_t fifo_space; + uint16_t diff; + uint64_t total_nonce_num; + struct bitmain_rxnonce_nonce nonces[BITMAIN_MAX_NONCE_NUM]; + uint16_t crc; +} __attribute__((packed, aligned(4))); + +struct bitmain_info { + int baud; + int chain_num; + int asic_num; + int chain_asic_num[BITMAIN_MAX_CHAIN_NUM]; + uint32_t chain_asic_exist[BITMAIN_MAX_CHAIN_NUM*8]; + uint32_t chain_asic_status[BITMAIN_MAX_CHAIN_NUM*8]; + char chain_asic_status_t[BITMAIN_MAX_CHAIN_NUM][320]; + int timeout; + int errorcount; + uint32_t nonce_error; + uint32_t last_nonce_error; + uint8_t reg_data[4]; + + int fan_num; + int fan[BITMAIN_MAX_FAN_NUM]; + int temp_num; + int temp[BITMAIN_MAX_TEMP_NUM]; + + int temp_max; + int temp_avg; + int temp_history_count; + int temp_history_index; + int temp_sum; + int temp_old; + int fan_pwm; + uint64_t total_nonce_num; + + int frequency; + char frequency_t[256]; + uint8_t voltage[2]; + char voltage_t[8]; + + int diff; + + int no_matching_work; + //int matching_work[BITMAIN_DEFAULT_CHAIN_NUM]; + + struct thr_info *thr; + pthread_t read_thr; + pthread_t write_thr; + pthread_mutex_t lock; + pthread_mutex_t qlock; + pthread_cond_t qcond; + cgsem_t write_sem; + int nonces; + int fifo_space; + int hw_version[4]; + unsigned int last_work_block; + struct timeval last_status_time; + int send_full_space; + + int idle; + bool reset; + bool overheat; + bool optimal; +}; + +#define BITMAIN_READ_SIZE 12 + +#define BTM_GETS_ERROR -1 +#define BTM_GETS_OK 0 + +#define BTM_SEND_ERROR -1 +#define BTM_SEND_OK 0 + +#define BITMAIN_READ_TIME(baud) ((double)BITMAIN_READ_SIZE * (double)8.0 / (double)(baud)) +#define ASSERT1(condition) __maybe_unused static char sizeof_uint32_t_must_be_4[(condition)?1:-1] +ASSERT1(sizeof(uint32_t) == 4); + +extern struct bitmain_info **bitmain_info; +extern char opt_bitmain_dev[256]; +extern int opt_bitmain_temp; +extern int opt_bitmain_overheat; +extern int opt_bitmain_fan_min; +extern int opt_bitmain_fan_max; +extern bool opt_bitmain_auto; +extern char *set_bitmain_dev(char *arg); +extern char *set_bitmain_fan(char *arg); + +#endif /* USE_BITMAIN */ +#endif /* BITMAIN_H */ diff --git a/driver-blockerupter.c b/driver-blockerupter.c new file mode 100644 index 0000000..0c57ec8 --- /dev/null +++ b/driver-blockerupter.c @@ -0,0 +1,501 @@ +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef WIN32 +#include +#include +#include +#ifndef O_CLOEXEC +#define O_CLOEXEC 0 +#endif +#else +#include +#include +#endif + +#include "elist.h" +#include "miner.h" +#include "driver-blockerupter.h" +#include "usbutils.h" + +static void blockerupter_space_mode(struct cgpu_info *blockerupter) +{ + int interface; + unsigned int bits = 0; + + interface = usb_interface(blockerupter); + + bits |= CP210X_BITS_DATA_8; + bits |= CP210X_BITS_PARITY_SPACE; + + usb_transfer_data(blockerupter, CP210X_TYPE_OUT, CP210X_SET_LINE_CTL, bits, interface, NULL, 0, C_SETPARITY); +} + +static void blockerupter_mark_mode(struct cgpu_info *blockerupter) +{ + int interface; + unsigned int bits = 0; + + interface = usb_interface(blockerupter); + + bits |= CP210X_BITS_DATA_8; + bits |= CP210X_BITS_PARITY_MARK; + + usb_transfer_data(blockerupter, CP210X_TYPE_OUT, CP210X_SET_LINE_CTL, bits, interface, NULL, 0, C_SETPARITY); + +} + +static void blockerupter_init_com(struct cgpu_info *blockerupter) +{ + uint32_t baudrate; + int interface; + + if (blockerupter->usbinfo.nodev) + return; + + interface = usb_interface(blockerupter); + + // Enable the UART + usb_transfer_data(blockerupter, CP210X_TYPE_OUT, CP210X_REQUEST_IFC_ENABLE, + CP210X_VALUE_UART_ENABLE, interface, NULL, 0, C_ENABLE_UART); + if (blockerupter->usbinfo.nodev) + return; + + // Set data control + usb_transfer_data(blockerupter, CP210X_TYPE_OUT, CP210X_REQUEST_DATA, CP210X_VALUE_DATA, + interface, NULL, 0, C_SETDATA); + + if (blockerupter->usbinfo.nodev) + return; + + // Set the baud + baudrate = BET_BAUD; + + usb_transfer_data(blockerupter, CP210X_TYPE_OUT, CP210X_REQUEST_BAUD, 0, + interface, &baudrate, sizeof (baudrate), C_SETBAUD); + + // Set space mode + blockerupter_space_mode(blockerupter); +} + +static int blockerupter_send(struct cgpu_info *blockerupter, char *data, int len) +{ + int err; + int bytes_sent; + + if (unlikely(blockerupter->usbinfo.nodev)) + return SEND_FAIL; + + err = usb_write(blockerupter, data, len, &bytes_sent, C_BET_WRITE); + + if (err || bytes_sent != len) { + applog(LOG_DEBUG, "blockerupter: Send (%d/%d)", bytes_sent, len); + return SEND_FAIL; + } + + return SEND_OK; +} + +static int blockerupter_read(struct cgpu_info *blockerupter, char *data, int len) +{ + int err; + int bytes_read; + + if (unlikely(blockerupter->usbinfo.nodev)) + return READ_FAIL; + + err = usb_read_timeout(blockerupter, data, len, &bytes_read, 2, C_BET_READ); + + if (err || bytes_read != len) { + applog(LOG_DEBUG, "blockerupter: Read (%d/%d)", bytes_read, len); + return READ_FAIL; + } + + return READ_OK; +} + +static void blockerupter_setclock(struct cgpu_info *blockerupter, uint8_t clock) +{ + struct blockerupter_info *info; + info = blockerupter->device_data; + char command; + int err; + + command = C_GCK | clock; + info->clock = clock; + err = blockerupter_send(blockerupter, &command, 1); + if (!err) + applog(LOG_DEBUG, "%s%d: Set Clock to %d MHz", blockerupter->drv->name, + blockerupter->device_id, (clock + 1) * 10 / 2); +} + +static void blockerupter_setdiff(struct cgpu_info *blockerupter, int diff) +{ + struct blockerupter_info *info; + info = blockerupter->device_data; + char command,bits; + int err; + int local_diff; + + // min_diff for driver is 64 + if (diff >= 262144) { + bits = 3; + local_diff = 262144; + } else if (diff >= 4096) { + bits = 2; + local_diff = 4096; + } else { + bits = 1; + local_diff = 64; + } + + if (local_diff == info->diff) + return; + + command = C_DIF | bits; + err = blockerupter_send(blockerupter, &command, 1); + if (!err) { + applog(LOG_DEBUG, "%s%d: Set Diff Bits to %d", blockerupter->drv->name, + blockerupter->device_id, bits); + info->diff = local_diff; + } +} + +static void blockerupter_setrolling(struct cgpu_info *blockerupter, uint8_t rolling) +{ + struct blockerupter_info *info; + info = blockerupter->device_data; + char command; + int err; + + command = C_LPO | rolling; + err = blockerupter_send(blockerupter, &command, 1); + + if (!err) { + applog(LOG_DEBUG, "%s%d: Set nTime Rolling to %d seconds", blockerupter->drv->name, + blockerupter->device_id, (rolling + 1) * 30); + info->rolling = (rolling + 1) * 30; + } +} + +static void blockerupter_init(struct cgpu_info *blockerupter) +{ + struct blockerupter_info *info; + + info = blockerupter->device_data; + // Set Clock + if (!opt_bet_clk || opt_bet_clk< 19 || opt_bet_clk > 31) { + opt_bet_clk = BET_CLOCK_DEFAULT; + } + blockerupter_setclock(blockerupter, opt_bet_clk); + info->clock = (opt_bet_clk + 1) * 10; + info->expected = info->clock * 24 * 32 * info->found / 1000.0; + // Set Diff + blockerupter_setdiff(blockerupter, BET_DIFF_DEFAULT); + info->diff = BET_DIFF_DEFAULT; + // Set nTime Rolling + blockerupter_setrolling(blockerupter, BET_ROLLING_DEFAULT); + info->rolling = (BET_ROLLING_DEFAULT + 1) * 30; + cgtime(&info->start_time); +} + +static struct cgpu_info *blockerupter_detect_one(struct libusb_device *dev, struct usb_find_devices *found) +{ + struct blockerupter_info *info; + struct cgpu_info *blockerupter = usb_alloc_cgpu(&blockerupter_drv, 1); + int i, err; + char reset = C_RES; + + if (!usb_init(blockerupter, dev, found)) { + applog(LOG_ERR, "Blockerupter usb init failed"); + blockerupter = usb_free_cgpu(blockerupter); + return NULL; + } + + blockerupter->device_data = (struct blockerupter_info *) malloc(sizeof(struct blockerupter_info)); + info = blockerupter->device_data; + memset(info, 0, sizeof(blockerupter_info)); + blockerupter_init_com(blockerupter); + + err = blockerupter_send(blockerupter, &reset, 1); + if (err) { + applog(LOG_ERR, "Blockerupter detect failed"); + blockerupter = usb_free_cgpu(blockerupter); + return NULL; + } + cgsleep_ms(5000); + + + for (i = 0; i < BET_MAXBOARDS; i++) { + char detect, answer; + + answer = 0; + detect = C_ASK | (uint8_t)i; + blockerupter_send(blockerupter, &detect, 1); + blockerupter_read(blockerupter, &answer, 1); + if (answer == A_WAL) { + applog(LOG_DEBUG, "BlockErupter found Board: %d", i); + info->boards[i] = 1; + info->found++; + } else { + applog(LOG_DEBUG, "BlockErupter missing board: %d, received %02x", + i, answer); + } + } + + if (!info->found) { + usb_free_cgpu(blockerupter); + free(info); + return NULL; + } else { + blockerupter->threads = 1; + add_cgpu(blockerupter); + applog(LOG_DEBUG, "Add BlockErupter with %d/%d Boards", info->found, + BET_MAXBOARDS); + blockerupter_init(blockerupter); + return blockerupter; + } +} + +static inline void blockerupter_detect(bool __maybe_unused hotplug) +{ + usb_detect(&blockerupter_drv, blockerupter_detect_one); +} + +static struct api_data *blockerupter_api_stats(struct cgpu_info *blockerupter) +{ + struct blockerupter_info *info = blockerupter->device_data; + struct api_data *root = NULL; + struct timeval now, elapsed; + char buf[32]; + int i; + + cgtime(&now); + timersub(&now, &info->start_time, &elapsed); + + info->hashrate = elapsed.tv_sec ? info->hashes * 4.295 / elapsed.tv_sec : 0; + info->eff = info->hashrate / info->expected; + + root = api_add_int(root, "Nonces", &info->nonces, false); + root = api_add_uint8(root, "Board", &info->found, false); + root = api_add_int(root, "Clock", &info->clock, false); + root = api_add_int(root,"Accepted", &info->accepted, false); + root = api_add_double(root, "HashRate", &info->hashrate , false); + root = api_add_double(root, "Expected", &info->expected , false); + root = api_add_double(root, "Efficiency", &info->eff, false); + for (i = 0; i < BET_MAXBOARDS; i++) { + double brd_hashrate; + + if (info->boards[i]) { + sprintf(buf, "Board%02d accepted", i); + root = api_add_int(root, buf, &info->b_info[i].accepted, false); + sprintf(buf, "Board%02d nonces", i); + root = api_add_int(root, buf, &info->b_info[i].nonces, false); + sprintf(buf, "Board%02d hwerror", i); + root = api_add_double(root, buf, &info->b_info[i].hwe, false); + sprintf(buf, "Board%02d hashrate", i); + brd_hashrate = elapsed.tv_sec ? info->b_info[i].hashes * 4.295 / elapsed.tv_sec : 0; + root = api_add_double(root, buf, &brd_hashrate, false); + } + } + + return root; +} + +static bool blockerupter_prepare(struct thr_info *thr) +{ + struct cgpu_info *blockerupter = thr->cgpu; + struct blockerupter_info *info = blockerupter->device_data; + + cglock_init(&(info->pool.data_lock)); + + return true; +} + +static void blockerupter_sendjob(struct cgpu_info *blockerupter, int board) +{ + struct blockerupter_info *info = blockerupter->device_data; + struct thr_info *thr = blockerupter->thr[0]; + struct work *work; + uint8_t command, answer; + int err; + + work = get_work(thr, thr->id); + memcpy(&info->works[info->work_idx],work,sizeof(struct work)); + + blockerupter_setdiff(blockerupter,floor(work->work_difficulty)); + + command = C_JOB | (uint8_t)board; + blockerupter_send(blockerupter, (char *)&command, 1); + blockerupter_mark_mode(blockerupter); + cgsleep_ms(1); + + blockerupter_send(blockerupter, (char *)(work->midstate), 32); + blockerupter_send(blockerupter, (char *)&(work->data[64]), 12); + blockerupter_send(blockerupter, (char *)&work->nonce2, 4); + blockerupter_send(blockerupter, (char *)&info->work_idx, 1); + + cgsleep_ms(1); + blockerupter_space_mode(blockerupter); + + answer = 0; + err = blockerupter_read(blockerupter, (char *)&answer, 1); + + cgtime(&info->last_job); + + if (err || answer != A_GET) { + applog(LOG_ERR, "%s%d: Sync Error", blockerupter->drv->name, blockerupter->device_id); + } else { + info->b_info[board].job_count++; + applog(LOG_DEBUG, "%s%d: Sent work %d to board %d", blockerupter->drv->name, + blockerupter->device_id, info->work_idx, board); + } + + info->work_idx++; + if (info->work_idx >= BET_WORK_FIFO) + info->work_idx = 0; +} + +static uint64_t blockerupter_checknonce(struct cgpu_info *blockerupter, struct blockerupter_response *resp, int board) +{ + uint8_t test; + struct blockerupter_info *info; + struct thr_info *thr = blockerupter->thr[0]; + struct work work; + uint32_t nonce; + uint64_t hashes=0; + int i; + struct board_info *cur_brd; + struct asic_info *cur_asic; + + info = blockerupter->device_data; + work = info->works[resp->work_idx]; + + nonce = *(uint32_t *)resp->nonce; + + applog(LOG_DEBUG, "%s%d: Nonce %08x from board %d, asic %d for work %d", + blockerupter->drv->name, blockerupter->device_id, *(uint32_t *) resp->nonce, + board, resp->chip, resp->work_idx); + + memcpy(work.data + 4 + 32 + 32, resp->ntime, 4); + __bin2hex(work.ntime, resp->ntime, 4); + + info->nonces++; + cur_brd = &info->b_info[board]; + cur_brd->nonces++; + cur_asic = &info->b_info[board].asics[resp->chip]; + cur_asic->nonces++; + + for (i = 0; i < BET_NONCE_FIX; i++) { + test = test_nonce_diff(&work, nonce + i, (double)info->diff); + if (test) { + applog(LOG_DEBUG, "%s%d: Nonce Fix Pass @%d", blockerupter->drv->name, + blockerupter->device_id, i); + info->hashes += info->diff; + cur_brd->hashes += info->diff; + cur_asic->hashes += info->diff; + if (test_nonce_diff(&work, nonce + i, work.work_difficulty)) { + if (submit_nonce(thr, &work, nonce + i)) { + hashes += floor(work.work_difficulty) * (uint64_t) 0xffffffff; + info->accepted++; + cur_brd->accepted++; + cur_asic->accepted++; + } + } + break; + } + } + + if (i == BET_NONCE_FIX) { + applog(LOG_DEBUG, "%s%d: Nonce Fix Failed", blockerupter->drv->name, + blockerupter->device_id); + cur_brd->bad++; + cur_brd->hwe = cur_brd->nonces ? (double)cur_brd->bad / cur_brd->nonces : 0; + cur_asic->bad++; + cur_asic->hwe = cur_asic->nonces ? (double)cur_asic->bad / cur_asic->nonces : 0; + } + return hashes; +} + +static uint64_t blockerupter_getresp(struct cgpu_info *blockerupter, int board) +{ + struct blockerupter_response *resp; + int err; + uint64_t hashes = 0; + + resp = (struct blockerupter_response *) malloc(BET_RESP_SZ); + err = blockerupter_read(blockerupter, (char *)resp, BET_RESP_SZ); + if (!err) + hashes = blockerupter_checknonce(blockerupter, resp, board); + free(resp); + return hashes; +} + +static int64_t blockerupter_scanhash(struct thr_info *thr) +{ + struct cgpu_info *blockerupter = thr->cgpu; + struct blockerupter_info *info = blockerupter->device_data; + char ask; + uint8_t answer; + int i; + int64_t hashes=0; + + if (unlikely(blockerupter->usbinfo.nodev)) { + applog(LOG_ERR, "%s%d: Device disappeared, shutting down thread", + blockerupter->drv->name, blockerupter->device_id); + return -1; + } + + for (i = 0; i < BET_MAXBOARDS; i++) { + if (!info->boards[i]) + continue; + ask = C_ASK | (uint8_t)i; + blockerupter_send(blockerupter, &ask, 1); + cgsleep_ms(1); + answer = 0; + blockerupter_read(blockerupter, (char *)&answer, 1); + + switch (answer) { + case A_WAL: + blockerupter_sendjob(blockerupter, i); + break; + case A_YES: + hashes += blockerupter_getresp(blockerupter, i); + break; + case A_NO: + break; + default: + applog(LOG_ERR, "%s%d: Unexpected value %02x received", blockerupter->drv->name, + blockerupter->device_id, answer); + break; + } + } + + return hashes; +} + +static void blockerupter_flush_work(struct cgpu_info *blockerupter) +{ + uint8_t command = C_LPO | BET_ROLLING_DEFAULT; + + blockerupter_send(blockerupter, (char *)&command, 1); +} + +struct device_drv blockerupter_drv = { + .drv_id = DRIVER_blockerupter, + .dname = "blockerupter", + .name = "BET", + .min_diff = 64, + .get_api_stats = blockerupter_api_stats, + .drv_detect = blockerupter_detect, + .thread_prepare = blockerupter_prepare, + .hash_work = hash_driver_work, + .flush_work = blockerupter_flush_work, + .scanwork = blockerupter_scanhash +}; diff --git a/driver-blockerupter.h b/driver-blockerupter.h new file mode 100644 index 0000000..4e15f1e --- /dev/null +++ b/driver-blockerupter.h @@ -0,0 +1,130 @@ +#ifndef _BLOCKERUPTER_H +#define _BLOCKERUPTER_H + +/* +WIN32 Build + +1. Install mxe (check tutorial on http://mxe.cc) + +2. After install mxe +export PATH={PATH_TO_MXE}/usr/bin:$PATH +autoreconf -fi +./configure --host=i686-pc-mingw32 --enable-blockerupter --without-curses CFLAGS=-DCURL_STATICLIB +make + +3. Before starting cgminer +install WinUSB driver for detected CP2102x device with Zadig (Some users might need to reboot) +*/ + +#include "miner.h" +#include "util.h" + +#define BET_MAXBOARDS 32 +#define BET_MAXASICS 48 +#define BET_BAUD 460800 + +#define BET_CLOCK_MAX 29 +#define BET_CLOCK_DEFAULT 23 +#define BET_DIFF_DEFAULT 64 +#define BET_ROLLING_DEFAULT 5 +extern int opt_bet_clk; + +#define BET_WORK_FIFO 128 +#define BET_NONCE_FIX 4 + +#define SEND_OK 0 +#define SEND_FAIL 1 +#define READ_OK 0 +#define READ_FAIL 1 + +// Global Commands +// resets all mega88, recv nothing +#define C_RES (0 << 5) +// stop jobs on all boards, set nTime rolling to (BoardID+1)*30, recv nothing +#define C_LPO (1 << 5) +// set clock for all boards, clock = (BoardID+1)*5, recv nothing +#define C_GCK (2 << 5) +// set difficulty bits for all boards with last 2bits from BoardID, recv nothing +#define C_DIF (3 << 5) + +// Board Specific Commands (CMD|BoardID) +// Send midstate(32 bytes), remaining block header(12 bytes), extranonce2(4 bytes) and job index(1 byte) to board +// Recv 0x58 +#define C_JOB (4 << 5) +// Recv current status of board +#define C_ASK (5 << 5) +// Recv (max_asics) bytes of chip test result, (max asics) bytes of clocks, 1 byte of diff bits, 1 byte of max nTime rolling, 1 byte of firmware version. Total (max asics)*2+3 bytes +#define C_TRS (6 << 5) + +// answers on C_ASK|BoardID +// Idle, waiting for new job +#define A_WAL 0x56 +// Mining but no nonce yet +#define A_NO 0xa6 +// Found nonce, followed with midstate(32 bytes), remaining block header(12 bytes), extranonce2(4 bytes), nonce(4 bytes), job index(1 byte), chip index(1 byte). Total 54 bytes. +#define A_YES 0x5A + +// answer on C_JOB|BoardID +#define A_GET 0x58 + +#pragma pack(1) + +typedef struct asic_info { + int bad; + int accepted; + int nonces; + int hashes; + double hwe; +} asic_info; + +#pragma pack(1) + +typedef struct board_info { + int bad; + int job_count; + int nonces; + int accepted; + int hashes; + double hashrate; + double hwe; + struct asic_info asics[BET_MAXASICS]; +} board_info; + +#pragma pack(1) + +typedef struct blockerupter_info { + struct pool pool; + uint8_t found; + int clock; + int nonces; + int diff; + int rolling; + int accepted; + int hashes; + double hashrate; + double expected; + double eff; + uint8_t work_idx; + struct work works[BET_WORK_FIFO]; + uint8_t boards[BET_MAXBOARDS]; + board_info b_info[BET_MAXBOARDS]; + struct timeval start_time; + struct timeval last_job; +} blockerupter_info; + + +#pragma pack(1) + +typedef struct blockerupter_response { + uint8_t midstate[32]; + uint8_t merkle[4]; + uint8_t ntime[4]; + uint8_t diff[4]; + uint8_t exnonc2[4]; + uint8_t nonce[4]; + uint8_t work_idx; + uint8_t chip; +} blockerupter_response; +#define BET_RESP_SZ (sizeof(blockerupter_response)) + +#endif diff --git a/driver-bmsc.c b/driver-bmsc.c new file mode 100644 index 0000000..3795739 --- /dev/null +++ b/driver-bmsc.c @@ -0,0 +1,2031 @@ +/* + * Copyright 2012-2013 Andrew Smith + * Copyright 2013 Con Kolivas + * Copyright 2013 Lingchao Xu + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +/* + * Those code should be works fine with AntMiner U1 of Bmsc. + * Operation: + * No detection implement. + * Input: 64B = 32B midstate + 20B fill bytes + last 12 bytes of block head. + * Return: send back 40bits immediately when Bmsc found a valid nonce. + * no query protocol implemented here, if no data send back in ~11.3 + * seconds (full cover time on 32bit nonce range by 380MH/s speed) + * just send another work. + * Notice: + * 1. Bmsc will start calculate when you push a work to them, even they + * are busy. + * 2. Bmsc will stop work when: a valid nonce has been found or 40 bits + * nonce range is completely calculated. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" + +#ifdef WIN32 +#include +#endif + +#include "compat.h" +#include "miner.h" +#include "usbutils.h" + +// The serial I/O speed - Linux uses a define 'B115200' in bits/termios.h +#define BMSC_IO_SPEED 115200 + +#define BMSC_NONCE_ARRAY_SIZE 6 + +// The size of a successful nonce read +#define BMSC_READ_SIZE 5 + +// Ensure the sizes are correct for the Serial read +#if (BMSC_READ_SIZE != 5) +#error BMSC_READ_SIZE must be 5 +#endif +#define ASSERT1(condition) __maybe_unused static char sizeof_uint32_t_must_be_4[(condition)?1:-1] +ASSERT1(sizeof(uint32_t) == 4); + +// TODO: USB? Different calculation? - see usbstats to work it out e.g. 1/2 of normal send time +// or even use that number? 1/2 +// #define BMSC_READ_TIME(baud) ((double)BMSC_READ_SIZE * (double)8.0 / (double)(baud)) +// maybe 1ms? +#define BMSC_READ_TIME(baud) (0.001) + +// USB ms timeout to wait - user specified timeouts are multiples of this +#define BMSC_WAIT_TIMEOUT 100 +#define BMSC_CMR2_TIMEOUT 1 +#define BMSC_READ_BUF_LEN 8192 + +// Defined in multiples of BMSC_WAIT_TIMEOUT +// Must of course be greater than BMSC_READ_COUNT_TIMING/BMSC_WAIT_TIMEOUT +// There's no need to have this bigger, since the overhead/latency of extra work +// is pretty small once you get beyond a 10s nonce range time and 10s also +// means that nothing slower than 429MH/s can go idle so most bmsc devices +// will always mine without idling +#define BMSC_READ_TIME_LIMIT_MAX 100 + +// In timing mode: Default starting value until an estimate can be obtained +// 5000 ms allows for up to a ~840MH/s device +#define BMSC_READ_COUNT_TIMING 5000 +#define BMSC_READ_COUNT_MIN BMSC_WAIT_TIMEOUT +#define SECTOMS(s) ((int)((s) * 1000)) +// How many ms below the expected completion time to abort work +// extra in case the last read is delayed +#define BMSC_READ_REDUCE ((int)(BMSC_WAIT_TIMEOUT * 1.5)) + +// For a standard Bmsc (to 5 places) +// Since this rounds up a the last digit - it is a slight overestimate +// Thus the hash rate will be a VERY slight underestimate +// (by a lot less than the displayed accuracy) +// Minor inaccuracy of these numbers doesn't affect the work done, +// only the displayed MH/s +#define BMSC_REV3_HASH_TIME 0.0000000026316 +#define LANCELOT_HASH_TIME 0.0000000025000 +#define ASICMINERUSB_HASH_TIME 0.0000000029761 +// TODO: What is it? +#define CAIRNSMORE1_HASH_TIME 0.0000000027000 +// Per FPGA +#define CAIRNSMORE2_HASH_TIME 0.0000000066600 +#define NANOSEC 1000000000.0 + +#define CAIRNSMORE2_INTS 4 + +// Bmsc doesn't send a completion message when it finishes +// the full nonce range, so to avoid being idle we must abort the +// work (by starting a new work item) shortly before it finishes +// +// Thus we need to estimate 2 things: +// 1) How many hashes were done if the work was aborted +// 2) How high can the timeout be before the Bmsc is idle, +// to minimise the number of work items started +// We set 2) to 'the calculated estimate' - BMSC_READ_REDUCE +// to ensure the estimate ends before idle +// +// The simple calculation used is: +// Tn = Total time in seconds to calculate n hashes +// Hs = seconds per hash +// Xn = number of hashes +// W = code/usb overhead per work +// +// Rough but reasonable estimate: +// Tn = Hs * Xn + W (of the form y = mx + b) +// +// Thus: +// Line of best fit (using least squares) +// +// Hs = (n*Sum(XiTi)-Sum(Xi)*Sum(Ti))/(n*Sum(Xi^2)-Sum(Xi)^2) +// W = Sum(Ti)/n - (Hs*Sum(Xi))/n +// +// N.B. W is less when aborting work since we aren't waiting for the reply +// to be transferred back (BMSC_READ_TIME) +// Calculating the hashes aborted at n seconds is thus just n/Hs +// (though this is still a slight overestimate due to code delays) +// + +// Both below must be exceeded to complete a set of data +// Minimum how long after the first, the last data point must be +#define HISTORY_SEC 60 +// Minimum how many points a single BMSC_HISTORY should have +#define MIN_DATA_COUNT 5 +// The value MIN_DATA_COUNT used is doubled each history until it exceeds: +#define MAX_MIN_DATA_COUNT 100 + +static struct timeval history_sec = { HISTORY_SEC, 0 }; + +// Store the last INFO_HISTORY data sets +// [0] = current data, not yet ready to be included as an estimate +// Each new data set throws the last old set off the end thus +// keeping a ongoing average of recent data +#define INFO_HISTORY 10 + +#define BMSC_WORK_QUEUE_NUM 36 + +struct BMSC_HISTORY { + struct timeval finish; + double sumXiTi; + double sumXi; + double sumTi; + double sumXi2; + uint32_t values; + uint32_t hash_count_min; + uint32_t hash_count_max; +}; + +enum timing_mode { MODE_DEFAULT, MODE_SHORT, MODE_LONG, MODE_VALUE }; + +static const char *MODE_DEFAULT_STR = "default"; +static const char *MODE_SHORT_STR = "short"; +static const char *MODE_SHORT_STREQ = "short="; +static const char *MODE_LONG_STR = "long"; +static const char *MODE_LONG_STREQ = "long="; +static const char *MODE_VALUE_STR = "value"; +static const char *MODE_UNKNOWN_STR = "unknown"; + +struct BMSC_INFO { + enum sub_ident ident; + int intinfo; + + // time to calculate the golden_ob + uint64_t golden_hashes; + struct timeval golden_tv; + + struct BMSC_HISTORY history[INFO_HISTORY+1]; + uint32_t min_data_count; + + int timeout; + + // seconds per Hash + double Hs; + // ms til we abort + int read_time; + // ms limit for (short=/long=) read_time + int read_time_limit; + + enum timing_mode timing_mode; + bool do_bmsc_timing; + + bool start; + + double fullnonce; + int count; + double W; + uint32_t values; + uint64_t hash_count_range; + + // Determine the cost of history processing + // (which will only affect W) + uint64_t history_count; + struct timeval history_time; + + // bmsc-options + int baud; + int work_division; + int fpga_count; + uint32_t nonce_mask; + + uint8_t cmr2_speed; + bool speed_next_work; + bool flash_next_work; + + struct work * work_queue[BMSC_WORK_QUEUE_NUM]; + int work_queue_index; + + unsigned char nonce_bin[BMSC_NONCE_ARRAY_SIZE][BMSC_READ_SIZE+1]; + int nonce_index; +}; + +#define BMSC_MIDSTATE_SIZE 32 +#define BMSC_UNUSED_SIZE 15 +#define BMSC_WORK_SIZE 12 + +#define BMSC_WORK_DATA_OFFSET 64 + +#define BMSC_CMR2_SPEED_FACTOR 2.5 +#define BMSC_CMR2_SPEED_MIN_INT 100 +#define BMSC_CMR2_SPEED_DEF_INT 180 +#define BMSC_CMR2_SPEED_MAX_INT 220 +#define CMR2_INT_TO_SPEED(_speed) ((uint8_t)((float)_speed / BMSC_CMR2_SPEED_FACTOR)) +#define BMSC_CMR2_SPEED_MIN CMR2_INT_TO_SPEED(BMSC_CMR2_SPEED_MIN_INT) +#define BMSC_CMR2_SPEED_DEF CMR2_INT_TO_SPEED(BMSC_CMR2_SPEED_DEF_INT) +#define BMSC_CMR2_SPEED_MAX CMR2_INT_TO_SPEED(BMSC_CMR2_SPEED_MAX_INT) +#define BMSC_CMR2_SPEED_INC 1 +#define BMSC_CMR2_SPEED_DEC -1 +#define BMSC_CMR2_SPEED_FAIL -10 + +#define BMSC_CMR2_PREFIX ((uint8_t)0xB7) +#define BMSC_CMR2_CMD_SPEED ((uint8_t)0) +#define BMSC_CMR2_CMD_FLASH ((uint8_t)1) +#define BMSC_CMR2_DATA_FLASH_OFF ((uint8_t)0) +#define BMSC_CMR2_DATA_FLASH_ON ((uint8_t)1) +#define BMSC_CMR2_CHECK ((uint8_t)0x6D) + +struct BMSC_WORK { + uint8_t midstate[BMSC_MIDSTATE_SIZE]; + // These 4 bytes are for CMR2 bitstreams that handle MHz adjustment + uint8_t check; + uint8_t data; + uint8_t cmd; + uint8_t prefix; + uint8_t unused[BMSC_UNUSED_SIZE]; + uint8_t workid; + uint8_t work[BMSC_WORK_SIZE]; +}; + +#define END_CONDITION 0x0000ffff + +// Looking for options in --bmsc-timing and --bmsc-options: +// +// Code increments this each time we start to look at a device +// However, this means that if other devices are checked by +// the Bmsc code (e.g. Avalon only as at 20130517) +// they will count in the option offset +// +// This, however, is deterministic so that's OK +// +// If we were to increment after successfully finding an Bmsc +// that would be random since an Bmsc may fail and thus we'd +// not be able to predict the option order +// +// Devices are checked in the order libusb finds them which is ? +// +static int option_offset = -1; + +unsigned char CRC5(unsigned char *ptr, unsigned char len) +{ + unsigned char i, j, k; + unsigned char crc = 0x1f; + + unsigned char crcin[5] = {1, 1, 1, 1, 1}; + unsigned char crcout[5] = {1, 1, 1, 1, 1}; + unsigned char din = 0; + + j = 0x80; + k = 0; + for (i = 0; i < len; i++) + { + if (*ptr & j) { + din = 1; + } else { + din = 0; + } + crcout[0] = crcin[4] ^ din; + crcout[1] = crcin[0]; + crcout[2] = crcin[1] ^ crcin[4] ^ din; + crcout[3] = crcin[2]; + crcout[4] = crcin[3]; + + j = j >> 1; + k++; + if (k == 8) + { + j = 0x80; + k = 0; + ptr++; + } + memcpy(crcin, crcout, 5); + } + crc = 0; + if(crcin[4]) { + crc |= 0x10; + } + if(crcin[3]) { + crc |= 0x08; + } + if(crcin[2]) { + crc |= 0x04; + } + if(crcin[1]) { + crc |= 0x02; + } + if(crcin[0]) { + crc |= 0x01; + } + return crc; +} + +static void _transfer(struct cgpu_info *bmsc, uint8_t request_type, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, uint32_t *data, int siz, enum usb_cmds cmd) +{ + int err; + + err = usb_transfer_data(bmsc, request_type, bRequest, wValue, wIndex, data, siz, cmd); + + applog(LOG_DEBUG, "%s: bmgid %d %s got err %d", + bmsc->drv->name, bmsc->cgminer_id, + usb_cmdname(cmd), err); +} + +#define transfer(bmsc, request_type, bRequest, wValue, wIndex, cmd) \ + _transfer(bmsc, request_type, bRequest, wValue, wIndex, NULL, 0, cmd) + +static void bmsc_initialise(struct cgpu_info *bmsc, int baud) +{ + struct BMSC_INFO *info = (struct BMSC_INFO *)(bmsc->device_data); + uint16_t wValue, wIndex; + enum sub_ident ident; + int interface; + + if (bmsc->usbinfo.nodev) + return; + + interface = _usb_interface(bmsc, info->intinfo); + ident = usb_ident(bmsc); + + switch (ident) { + case IDENT_BLT: + case IDENT_LLT: + case IDENT_CMR1: + case IDENT_CMR2: + // Reset + transfer(bmsc, FTDI_TYPE_OUT, FTDI_REQUEST_RESET, FTDI_VALUE_RESET, + interface, C_RESET); + + if (bmsc->usbinfo.nodev) + return; + + // Latency + _usb_ftdi_set_latency(bmsc, info->intinfo); + + if (bmsc->usbinfo.nodev) + return; + + // Set data control + transfer(bmsc, FTDI_TYPE_OUT, FTDI_REQUEST_DATA, FTDI_VALUE_DATA_BLT, + interface, C_SETDATA); + + if (bmsc->usbinfo.nodev) + return; + + // default to BLT/LLT 115200 + wValue = FTDI_VALUE_BAUD_BLT; + wIndex = FTDI_INDEX_BAUD_BLT; + + if (ident == IDENT_CMR1 || ident == IDENT_CMR2) { + switch (baud) { + case 115200: + wValue = FTDI_VALUE_BAUD_CMR_115; + wIndex = FTDI_INDEX_BAUD_CMR_115; + break; + case 57600: + wValue = FTDI_VALUE_BAUD_CMR_57; + wIndex = FTDI_INDEX_BAUD_CMR_57; + break; + default: + quit(1, "bmsc_intialise() invalid baud (%d) for Cairnsmore1", baud); + break; + } + } + + // Set the baud + transfer(bmsc, FTDI_TYPE_OUT, FTDI_REQUEST_BAUD, wValue, + (wIndex & 0xff00) | interface, C_SETBAUD); + + if (bmsc->usbinfo.nodev) + return; + + // Set Modem Control + transfer(bmsc, FTDI_TYPE_OUT, FTDI_REQUEST_MODEM, FTDI_VALUE_MODEM, + interface, C_SETMODEM); + + if (bmsc->usbinfo.nodev) + return; + + // Set Flow Control + transfer(bmsc, FTDI_TYPE_OUT, FTDI_REQUEST_FLOW, FTDI_VALUE_FLOW, + interface, C_SETFLOW); + + if (bmsc->usbinfo.nodev) + return; + + // Clear any sent data + transfer(bmsc, FTDI_TYPE_OUT, FTDI_REQUEST_RESET, FTDI_VALUE_PURGE_TX, + interface, C_PURGETX); + + if (bmsc->usbinfo.nodev) + return; + + // Clear any received data + transfer(bmsc, FTDI_TYPE_OUT, FTDI_REQUEST_RESET, FTDI_VALUE_PURGE_RX, + interface, C_PURGERX); + break; + case IDENT_ICA: + // Set Data Control + transfer(bmsc, PL2303_CTRL_OUT, PL2303_REQUEST_CTRL, PL2303_VALUE_CTRL, + interface, C_SETDATA); + + if (bmsc->usbinfo.nodev) + return; + + // Set Line Control + uint32_t ica_data[2] = { PL2303_VALUE_LINE0, PL2303_VALUE_LINE1 }; + _transfer(bmsc, PL2303_CTRL_OUT, PL2303_REQUEST_LINE, PL2303_VALUE_LINE, + interface, &ica_data[0], PL2303_VALUE_LINE_SIZE, C_SETLINE); + + if (bmsc->usbinfo.nodev) + return; + + // Vendor + transfer(bmsc, PL2303_VENDOR_OUT, PL2303_REQUEST_VENDOR, PL2303_VALUE_VENDOR, + interface, C_VENDOR); + break; + case IDENT_AMU: + // Enable the UART + transfer(bmsc, CP210X_TYPE_OUT, CP210X_REQUEST_IFC_ENABLE, + CP210X_VALUE_UART_ENABLE, + interface, C_ENABLE_UART); + + if (bmsc->usbinfo.nodev) + return; + + // Set data control + transfer(bmsc, CP210X_TYPE_OUT, CP210X_REQUEST_DATA, CP210X_VALUE_DATA, + interface, C_SETDATA); + + if (bmsc->usbinfo.nodev) + return; + + // Set the baud + uint32_t data = CP210X_DATA_BAUD; + _transfer(bmsc, CP210X_TYPE_OUT, CP210X_REQUEST_BAUD, 0, + interface, &data, sizeof(data), C_SETBAUD); + break; + default: + quit(1, "bmsc_intialise() called with invalid %s cgid %i ident=%d", + bmsc->drv->name, bmsc->cgminer_id, ident); + } +} + +#define BTM_NONCE_ERROR -1 +#define BTM_NONCE_OK 0 +#define BTM_NONCE_RESTART 1 +#define BTM_NONCE_TIMEOUT 2 + +static int bmsc_get_nonce(struct cgpu_info *bmsc, unsigned char *buf, struct timeval *tv_start, + struct timeval *tv_finish, struct thr_info *thr, int read_time) +{ + struct BMSC_INFO *info = (struct BMSC_INFO *)(bmsc->device_data); + int err, amt, rc; + + if (bmsc->usbinfo.nodev) + return BTM_NONCE_ERROR; + + cgtime(tv_start); + err = usb_read_ii_timeout_cancellable(bmsc, info->intinfo, (char *)buf, + BMSC_READ_SIZE, &amt, read_time, + C_GETRESULTS); + cgtime(tv_finish); + + if (err < 0 && err != LIBUSB_ERROR_TIMEOUT) { + applog(LOG_ERR, "%s%i: Comms error (rerr=%d amt=%d)", bmsc->drv->name, + bmsc->device_id, err, amt); + dev_error(bmsc, REASON_DEV_COMMS_ERROR); + return BTM_NONCE_ERROR; + } + + if (amt >= BMSC_READ_SIZE) + return BTM_NONCE_OK; + + rc = SECTOMS(tdiff(tv_finish, tv_start)); + if (thr && thr->work_restart) { + applog(LOG_DEBUG, "Bmsc Read: Work restart at %d ms", rc); + return BTM_NONCE_RESTART; + } + + if (amt > 0) + applog(LOG_DEBUG, "Bmsc Read: Timeout reading for %d ms", rc); + else + applog(LOG_DEBUG, "Bmsc Read: No data for %d ms", rc); + return BTM_NONCE_TIMEOUT; +} + +static const char *timing_mode_str(enum timing_mode timing_mode) +{ + switch(timing_mode) { + case MODE_DEFAULT: + return MODE_DEFAULT_STR; + case MODE_SHORT: + return MODE_SHORT_STR; + case MODE_LONG: + return MODE_LONG_STR; + case MODE_VALUE: + return MODE_VALUE_STR; + default: + return MODE_UNKNOWN_STR; + } +} + +static void set_timing_mode(int this_option_offset, struct cgpu_info *bmsc, float readtimeout) +{ + struct BMSC_INFO *info = (struct BMSC_INFO *)(bmsc->device_data); + enum sub_ident ident; + double Hs; + char buf[BUFSIZ+1]; + char *ptr, *comma, *eq; + size_t max; + int i; + + ident = usb_ident(bmsc); + switch (ident) { + case IDENT_ICA: + info->Hs = BMSC_REV3_HASH_TIME; + break; + case IDENT_BLT: + case IDENT_LLT: + info->Hs = LANCELOT_HASH_TIME; + break; + case IDENT_AMU: + info->Hs = ASICMINERUSB_HASH_TIME; + break; + case IDENT_CMR1: + info->Hs = CAIRNSMORE1_HASH_TIME; + break; + case IDENT_CMR2: + info->Hs = CAIRNSMORE2_HASH_TIME; + break; + default: + quit(1, "Bmsc get_options() called with invalid %s ident=%d", + bmsc->drv->name, ident); + } + + info->read_time = 0; + info->read_time_limit = 0; // 0 = no limit + + info->fullnonce = info->Hs * (((double) 0xffffffff) + 1); + info->read_time = (int)(readtimeout * BMSC_WAIT_TIMEOUT); + + if(info->read_time < 0) + info->read_time = 1; + + info->timing_mode = MODE_DEFAULT; + info->do_bmsc_timing = false; + + info->min_data_count = MIN_DATA_COUNT; + + // All values are in multiples of BMSC_WAIT_TIMEOUT + info->read_time_limit *= BMSC_WAIT_TIMEOUT; + + applog(LOG_ERR, "%s%d Init: mode=%s read_time=%dms limit=%dms Hs=%e", + bmsc->drv->name, bmsc->cgminer_id, + timing_mode_str(info->timing_mode), + info->read_time, info->read_time_limit, info->Hs); +} + +static uint32_t mask(int work_division) +{ + uint32_t nonce_mask = 0x7fffffff; + + // yes we can calculate these, but this way it's easy to see what they are + switch (work_division) { + case 1: + nonce_mask = 0xffffffff; + break; + case 2: + nonce_mask = 0x7fffffff; + break; + case 4: + nonce_mask = 0x3fffffff; + break; + case 8: + nonce_mask = 0x1fffffff; + break; + default: + quit(1, "Invalid2 bmsc-options for work_division (%d) must be 1, 2, 4 or 8", work_division); + } + + return nonce_mask; +} + +static void get_options(int this_option_offset, struct cgpu_info *bmsc, int *baud, float *readtimeout) +{ + char buf[BUFSIZ+1]; + char *ptr, *comma, *colon, *colon2; + enum sub_ident ident; + size_t max; + int i, tmp; + float tmpf; + + if (opt_bmsc_options == NULL) + buf[0] = '\0'; + else { + ptr = opt_bmsc_options; + for (i = 0; i < this_option_offset; i++) { + comma = strchr(ptr, ','); + if (comma == NULL) + break; + ptr = comma + 1; + } + + comma = strchr(ptr, ','); + if (comma == NULL) + max = strlen(ptr); + else + max = comma - ptr; + + if (max > BUFSIZ) + max = BUFSIZ; + strncpy(buf, ptr, max); + buf[max] = '\0'; + } + + ident = usb_ident(bmsc); + switch (ident) { + case IDENT_ICA: + case IDENT_BLT: + case IDENT_LLT: + *baud = BMSC_IO_SPEED; + break; + case IDENT_AMU: + *baud = BMSC_IO_SPEED; + break; + case IDENT_CMR1: + *baud = BMSC_IO_SPEED; + break; + case IDENT_CMR2: + *baud = BMSC_IO_SPEED; + break; + default: + quit(1, "Bmsc get_options() called with invalid %s ident=%d", + bmsc->drv->name, ident); + } + + if (*buf) { + colon = strchr(buf, ':'); + if (colon) + *(colon++) = '\0'; + + if (*buf) { + tmp = atoi(buf); + switch (tmp) { + case 115200: + *baud = 115200; + break; + case 57600: + *baud = 57600; + break; + default: + quit(1, "Invalid bmsc-options for baud (%s) must be 115200 or 57600", buf); + } + } + + if (colon && *colon) { + tmpf = atof(colon); + if (tmpf > 0) { + *readtimeout = tmpf; + } else { + quit(1, "Invalid bmsc-options for timeout (%s) must be > 0", colon); + } + } + } +} + +static void get_bandops(unsigned char * core_buf, int *corenum, char *coreenable, int *coresleep) +{ + char buf[512] = {0}; + char *colon, *colon2, * colon3; + int i, len; + + if (opt_bmsc_bandops) { + len = strlen(opt_bmsc_bandops); + if(len <= 0 || len >= 512) { + quit(1, "Invalid bmsc-bandops %s %d", opt_bmsc_bandops, len); + } + strcpy(buf, opt_bmsc_bandops); + colon = strchr(buf, ':'); + if (colon) + *(colon++) = '\0'; + + if (*buf) { + if(strlen(buf) > 8 || strlen(buf)%2 != 0 || strlen(buf)/2 == 0) { + quit(1, "Invalid bitmain-options for core command, must be hex now: %s", buf); + } + memset(core_buf, 0, 4); + if(!hex2bin(core_buf, buf, strlen(buf)/2)) { + quit(1, "Invalid bitmain-options for core command, hex2bin error now: %s", buf); + } + } + + if (colon && *colon) { + colon2 = strchr(colon, ':'); + if (colon2) + *(colon2++) = '\0'; + + if (*colon) { + *corenum = atoi(colon); + if(*corenum <= 0 || *corenum >= 256) { + quit(1, "Invalid bitmain-bandops for asic core num, must %d be > 0 and < 256", *corenum); + } + } + + if(colon2 && *colon2) { + colon3 = strchr(colon2, ':'); + if (colon3) + *(colon3++) = '\0'; + + if(*colon2) { + strcpy(coreenable, colon2); + if(strlen(coreenable) != *corenum) { + quit(1, "Invalid bitmain-bandops for asic core enable, must be equal core num %d", *corenum); + } + } + + if (colon3 && *colon3) { + *coresleep = atoi(colon3); + } + } + } + } +} + +static struct cgpu_info *bmsc_detect_one(struct libusb_device *dev, struct usb_find_devices *found) +{ + int this_option_offset = ++option_offset; + struct BMSC_INFO *info; + struct timeval tv_start, tv_finish; + + // Block 171874 nonce = (0xa2870100) = 0x000187a2 + // N.B. golden_ob MUST take less time to calculate + // than the timeout set in bmsc_open() + // This one takes ~0.53ms on Rev3 Bmsc + const char golden_ob[] = + "4679ba4ec99876bf4bfe086082b40025" + "4df6c356451471139a3afa71e48f544a" + "00000000000000004000000000000000" + "0000001f87320b1a1426674f2fa722ce"; + const char golden_ob1[] = + "e1eb393a50f6ae97e306ea87c1c47eae" + "1f9ad02d729d9f86bd48a213a4600144" + "00000000000000004000000000000000" + "0000001ffb0b0719aaf19752dd5e83a4"; + const char golden_ob2[] = + "b65911ea2c4b0c52958cb408caebff32" + "8dece4e6a002fe2693ba9906ffde7e8a" + "00000000000000004000000000000000" + "0000001f20dc1c190642455201756658"; + const char golden_ob3[] = + "c99da189374bcc69a1134d6f4953addc" + "7420499b132b7f8f999b0c71fe7efbf2" + "00000000000000004000000000000000" + "0000001f20dc1c198e4145526d74dee3"; + const char golden_ob4[] = + "696af96144b6079c1b437fbc6e539e4d" + "996d25b027ea9eefdfaf4eff6add6986" + "00000000000000004000000000000000" + "0000001f20dc1c19f84e4552ac86dc14"; + + char bandops_ob[] = + "00000000000000000000000000000000" + "00000000000000000000000000000000" + "00000000000000000000000000000000" + "00000000000000000000000000000000"; + + const char golden_nonce[] = "000187a2"; + const char golden_nonce1[] = "0345182b"; + const char golden_nonce2[] = "466b30a5"; + const char golden_nonce3[] = "857e65ee"; + const char golden_nonce4[] = "c6f70284"; + const uint32_t golden_nonce_val = 0x000187a2; + unsigned char nonce_bin[BMSC_READ_SIZE]; + struct BMSC_WORK workdata; + char *nonce_hex; + int baud = 115200, work_division = 1, fpga_count = 1; + float readtimeout = 1.0; + struct cgpu_info *bmsc; + int ret, err, amount, tries, i; + bool ok; + bool cmr2_ok[CAIRNSMORE2_INTS]; + int cmr2_count; + + unsigned char cmd_buf[4] = {0}; + unsigned char rdreg_buf[4] = {0}; + unsigned char reg_data[4] = {0}; + unsigned char voltage_data[2] = {0}; + + unsigned char rebuf[BMSC_READ_BUF_LEN] = {0}; + int relen = 0; + int realllen = 0; + int nodata = 0; + char msg[10240] = {0}; + int sendfreqstatus = 1; + int k = 0; + + unsigned char core_cmd[4] = {0}; + int corenum = 0; + char coreenable[256] = {0}; + int coresleep = 0; + + if (opt_bmsc_options == NULL) + return NULL; + + if ((sizeof(workdata) << 1) != (sizeof(golden_ob) - 1)) + quithere(1, "Data and golden_ob sizes don't match"); + if ((sizeof(workdata) << 1) != (sizeof(bandops_ob) - 1)) + quithere(1, "Data and bandops_ob sizes don't match"); + + bmsc = usb_alloc_cgpu(&bmsc_drv, 1); + + if (!usb_init(bmsc, dev, found)) + goto shin; + + get_options(this_option_offset, bmsc, &baud, &readtimeout); + get_bandops(core_cmd, &corenum, coreenable, &coresleep); + + info = (struct BMSC_INFO *)calloc(1, sizeof(struct BMSC_INFO)); + if (unlikely(!info)) + quit(1, "Failed to malloc BMSC_INFO"); + bmsc->device_data = (void *)info; + + info->ident = usb_ident(bmsc); + info->start = true; + switch (info->ident) { + case IDENT_ICA: + case IDENT_BLT: + case IDENT_LLT: + case IDENT_AMU: + case IDENT_CMR1: + info->timeout = BMSC_WAIT_TIMEOUT; + break; + case IDENT_CMR2: + if (found->intinfo_count != CAIRNSMORE2_INTS) { + quithere(1, "CMR2 Interface count (%d) isn't expected: %d", + found->intinfo_count, + CAIRNSMORE2_INTS); + } + info->timeout = BMSC_CMR2_TIMEOUT; + cmr2_count = 0; + for (i = 0; i < CAIRNSMORE2_INTS; i++) + cmr2_ok[i] = false; + break; + default: + quit(1, "%s bmsc_detect_one() invalid %s ident=%d", + bmsc->drv->dname, bmsc->drv->dname, info->ident); + } +// For CMR2 test each USB Interface +cmr2_retry: + tries = 2; + ok = false; + while (!ok && tries-- > 0) { + bmsc_initialise(bmsc, baud); + + if(opt_bmsc_bootstart) { + applog(LOG_ERR, "---------------------start bootstart----------------------"); + cmd_buf[0] = 0xbb; + cmd_buf[1] = 0x00; + cmd_buf[2] = 0x00; + cmd_buf[3] = 0x00; //0-7 + cmd_buf[3] = CRC5(cmd_buf, 27); + cmd_buf[3] |= 0x80; + + cgsleep_ms(500); + applog(LOG_ERR, "Send bootstart off %02x%02x%02x%02x", cmd_buf[0], cmd_buf[1], cmd_buf[2], cmd_buf[3]); + err = usb_write(bmsc, (char * )cmd_buf, 4, &amount, C_SENDTESTWORK); + if (err != LIBUSB_SUCCESS || amount != 4) { + applog(LOG_ERR, "Write bootstart Comms error (werr=%d amount=%d)", err, amount); + continue; + } + + cmd_buf[0] = 0xbb; + cmd_buf[1] = 0x08; + cmd_buf[2] = 0x00; + cmd_buf[3] = 0x00; //0-7 + cmd_buf[3] = CRC5(cmd_buf, 27); + cmd_buf[3] |= 0x80; + + cgsleep_ms(500); + applog(LOG_ERR, "Send bootstart on %02x%02x%02x%02x", cmd_buf[0], cmd_buf[1], cmd_buf[2], cmd_buf[3]); + err = usb_write(bmsc, (char * )cmd_buf, 4, &amount, C_SENDTESTWORK); + if (err != LIBUSB_SUCCESS || amount != 4) { + applog(LOG_ERR, "Write bootstart Comms error (werr=%d amount=%d)", err, amount); + continue; + } + applog(LOG_ERR, "Send bootstart ok"); + } + + if(opt_bmsc_voltage) { + if(strlen(opt_bmsc_voltage) > 4 || strlen(opt_bmsc_voltage)%2 != 0 || strlen(opt_bmsc_voltage)/2 == 0) { + quit(1, "Invalid options for voltage data, must be hex now: %s", opt_bmsc_voltage); + } + memset(voltage_data, 0, 2); + if(!hex2bin(voltage_data, opt_bmsc_voltage, strlen(opt_bmsc_voltage)/2)) { + quit(1, "Invalid options for voltage data, hex2bin error now: %s", opt_bmsc_voltage); + } + cmd_buf[0] = 0xaa; + cmd_buf[1] = voltage_data[0]; + cmd_buf[1] &=0x0f; + cmd_buf[1] |=0xb0; + cmd_buf[2] = voltage_data[1]; + cmd_buf[3] = 0x00; //0-7 + cmd_buf[3] = CRC5(cmd_buf, 4*8 - 5); + cmd_buf[3] |= 0xc0; + + applog(LOG_ERR, "---------------------start voltage----------------------"); + cgsleep_ms(500); + applog(LOG_ERR, "Send voltage %02x%02x%02x%02x", cmd_buf[0], cmd_buf[1], cmd_buf[2], cmd_buf[3]); + err = usb_write(bmsc, (char * )cmd_buf, 4, &amount, C_SENDTESTWORK); + if (err != LIBUSB_SUCCESS || amount != 4) { + applog(LOG_ERR, "Write voltage Comms error (werr=%d amount=%d)", err, amount); + continue; + } + applog(LOG_ERR, "Send voltage ok"); + } + + if (opt_bmsc_gray) { + cmd_buf[0] = 3; + cmd_buf[0] |= 0x80; + cmd_buf[1] = 0; //16-23 + cmd_buf[2] = 0x80; //8-15 + cmd_buf[3] = 0x80; //0-7 + cmd_buf[3] = CRC5(cmd_buf, 27); + cmd_buf[3] |= 0x80; + + applog(LOG_ERR, "-----------------start gray-------------------"); + cgsleep_ms(500); + applog(LOG_ERR, "Send gray %02x%02x%02x%02x", cmd_buf[0], cmd_buf[1], cmd_buf[2], cmd_buf[3]); + err = usb_write_ii(bmsc, info->intinfo, (char * )cmd_buf, 4, &amount, C_SENDWORK); + if (err != LIBUSB_SUCCESS || amount != 4) { + applog(LOG_ERR, "%s%i: Write freq Comms error (werr=%d amount=%d)", bmsc->drv->name, bmsc->device_id, err, amount); + continue; + } + applog(LOG_DEBUG, "Send gray ok"); + } + + if (opt_bmsc_freq) { + if (strcmp(opt_bmsc_freq, "0") != 0) { + applog(LOG_DEBUG, "Device detect freq parameter=%s", opt_bmsc_freq); + if (strlen(opt_bmsc_freq) > 8 || strlen(opt_bmsc_freq) % 2 != 0 || strlen(opt_bmsc_freq) / 2 == 0) { + quit(1, "Invalid bmsc_freq for freq data, must be hex now: %s", opt_bmsc_freq); + } + memset(reg_data, 0, 4); + if (!hex2bin(reg_data, opt_bmsc_freq, strlen(opt_bmsc_freq) / 2)) { + quit(1, "Invalid bmsc_freq for freq data, hex2bin error now: %s", opt_bmsc_freq); + } + cmd_buf[0] = 2; + cmd_buf[0] |= 0x80; + cmd_buf[1] = reg_data[0]; //16-23 + cmd_buf[2] = reg_data[1]; //8-15 + cmd_buf[3] = 0; + cmd_buf[3] = CRC5(cmd_buf, 27); + applog(LOG_DEBUG, "Set_frequency cmd_buf[1]{%02x}cmd_buf[2]{%02x}", cmd_buf[1], cmd_buf[2]); + + rdreg_buf[0] = 4; + rdreg_buf[0] |= 0x80; + rdreg_buf[1] = 0; //16-23 + rdreg_buf[2] = 0x04; //8-15 + rdreg_buf[3] = 0; + rdreg_buf[3] = CRC5(rdreg_buf, 27); + + applog(LOG_ERR, "-----------------start freq-------------------"); + cgsleep_ms(500); + + applog(LOG_ERR, "Send frequency %02x%02x%02x%02x", cmd_buf[0], cmd_buf[1], cmd_buf[2], cmd_buf[3]); + err = usb_write_ii(bmsc, info->intinfo, (char * )cmd_buf, 4, &amount, C_SENDWORK); + if (err != LIBUSB_SUCCESS || amount != 4) { + applog(LOG_ERR, "%s%i: Write freq Comms error (werr=%d amount=%d)", bmsc->drv->name, bmsc->device_id, err, amount); + continue; + } + applog(LOG_DEBUG, "Send frequency ok"); + + cgsleep_ms(500); + + applog(LOG_ERR, "Send freq getstatus %02x%02x%02x%02x", rdreg_buf[0], rdreg_buf[1], rdreg_buf[2], rdreg_buf[3]); + + for(i = 0; i < 10; i++) { + usb_read_ii_timeout_cancellable(bmsc, info->intinfo, (char * )rebuf, BMSC_READ_SIZE, &relen, 100, C_GETRESULTS); + } + + err = usb_write_ii(bmsc, info->intinfo, (char * )rdreg_buf, 4, &amount, C_SENDWORK); + if (err != LIBUSB_SUCCESS || amount != 4) { + applog(LOG_ERR, "%s%i: Write freq getstatus Comms error (werr=%d amount=%d)", bmsc->drv->name, bmsc->device_id, err, amount); + continue; + } + applog(LOG_DEBUG, "Send freq getstatus ok"); + + nodata = 0; + realllen = 0; + while (1) { + relen = 0; + err = usb_read_ii_timeout_cancellable(bmsc, info->intinfo, (char * )rebuf + realllen, BMSC_READ_SIZE, &relen, 200, C_GETRESULTS); + if (err < 0 && err != LIBUSB_ERROR_TIMEOUT) { + applog(LOG_ERR, "%s%i: Read freq Comms error (rerr=%d relen=%d)", bmsc->drv->name, bmsc->device_id, err, relen); + break; + } else if (err == LIBUSB_ERROR_TIMEOUT) { + applog(LOG_DEBUG, "%s%i: Read freq Comms timeout (rerr=%d relen=%d)", bmsc->drv->name, bmsc->device_id, err, relen); + + nodata++; + if (nodata > 5) { + if (realllen <= 0) { + if (sendfreqstatus) { + sendfreqstatus = 0; + applog(LOG_ERR, "Send freq getstatus %02x%02x%02x%02x", rdreg_buf[0], rdreg_buf[1], rdreg_buf[2], rdreg_buf[3]); + usb_read_ii_timeout_cancellable(bmsc, info->intinfo, (char * )rebuf, BMSC_READ_SIZE, &relen, 200, C_GETRESULTS); + err = usb_write_ii(bmsc, info->intinfo, (char * )rdreg_buf, 4, &amount, C_SENDWORK); + if (err != LIBUSB_SUCCESS || amount != 4) { + applog(LOG_ERR, "%s%i: Write freq getstatus Comms error (werr=%d amount=%d)", bmsc->drv->name, bmsc->device_id, err, amount); + continue; + } + applog(LOG_DEBUG, "Send freq getstatus ok"); + } else { + applog(LOG_ERR, "------recv freq getstatus no data finish------"); + break; + } + } else { + applog(LOG_DEBUG, "Recv freq getstatus len=%d", realllen); + for (i = 0; i < realllen; i += 5) { + applog(LOG_ERR, "Recv %d freq getstatus=%02x%02x%02x%02x%02x", i / 5 + 1, rebuf[i], rebuf[i + 1], rebuf[i + 2], rebuf[i + 3], rebuf[i + 4]); + } + applog(LOG_ERR, "--------recv freq getstatus ok finish---------"); + break; + } + } + continue; + } else { + nodata = 0; + realllen += relen; + for (i = 0; i < relen; i++) { + sprintf(msg + i * 2, "%02x", rebuf[i]); + } + applog(LOG_DEBUG, "Read data(%d):%s", relen, msg); + } + } + } else { + applog(LOG_ERR, "Device detect freq 0 parameter"); + } + } + + if (opt_bmsc_rdreg) { + applog(LOG_DEBUG, "Device detect rdreg parameter=%s", opt_bmsc_rdreg); + if (strlen(opt_bmsc_rdreg) > 8 || strlen(opt_bmsc_rdreg) % 2 != 0 || strlen(opt_bmsc_rdreg) / 2 == 0) { + quit(1, "Invalid bmsc_rdreg for reg data, must be hex now: %s", opt_bmsc_rdreg); + } + memset(reg_data, 0, 4); + if (!hex2bin(reg_data, opt_bmsc_rdreg, strlen(opt_bmsc_rdreg) / 2)) { + quit(1, "Invalid bmsc_rdreg for reg data, hex2bin error now: %s", opt_bmsc_rdreg); + } + rdreg_buf[0] = 4; + rdreg_buf[0] |= 0x80; + rdreg_buf[1] = 0; //16-23 + rdreg_buf[2] = reg_data[0]; //8-15 + rdreg_buf[3] = 0; + rdreg_buf[3] = CRC5(rdreg_buf, 27); + applog(LOG_DEBUG, "Get_status rdreg_buf[1]{%02x}rdreg_buf[2]{%02x}", rdreg_buf[1], rdreg_buf[2]); + + applog(LOG_ERR, "-----------------start rdreg------------------"); + applog(LOG_ERR, "Send getstatus %02x%02x%02x%02x", rdreg_buf[0], rdreg_buf[1], rdreg_buf[2], rdreg_buf[3]); + + for(i = 0; i < 10; i++) { + usb_read_ii_timeout_cancellable(bmsc, info->intinfo, (char * )rebuf, BMSC_READ_SIZE, &relen, 100, C_GETRESULTS); + } + + err = usb_write_ii(bmsc, info->intinfo, (char * )rdreg_buf, 4, &amount, C_SENDWORK); + if (err != LIBUSB_SUCCESS || amount != 4) { + applog(LOG_ERR, "%s%i: Write rdreg Comms error (werr=%d amount=%d)", bmsc->drv->name, bmsc->device_id, err, amount); + continue; + } + applog(LOG_DEBUG, "Send getstatus ok"); + + nodata = 0; + realllen = 0; + while (1) { + relen = 0; + err = usb_read_ii_timeout_cancellable(bmsc, info->intinfo, (char * )rebuf + realllen, BMSC_READ_SIZE, &relen, 200, C_GETRESULTS); + if (err < 0 && err != LIBUSB_ERROR_TIMEOUT) { + applog(LOG_ERR, "%s%i: Read rdreg Comms error (rerr=%d relen=%d)", bmsc->drv->name, bmsc->device_id, err, relen); + break; + } else if (err == LIBUSB_ERROR_TIMEOUT) { + applog(LOG_DEBUG, "%s%i: Read rdreg Comms timeout (rerr=%d relen=%d)", bmsc->drv->name, bmsc->device_id, err, relen); + + nodata++; + if (nodata > 5) { + applog(LOG_DEBUG, "Recv rdreg getstatus len=%d", realllen); + for (i = 0; i < realllen; i += 5) { + applog(LOG_ERR, "Recv %d rdreg getstatus=%02x%02x%02x%02x%02x", i / 5 + 1, rebuf[i], rebuf[i + 1], rebuf[i + 2], rebuf[i + 3], rebuf[i + 4]); + } + applog(LOG_ERR, "---------recv rdreg getstatus finish----------"); + break; + } + continue; + } else { + nodata = 0; + realllen += relen; + for (i = 0; i < relen; i++) { + sprintf(msg + i * 2, "%02x", rebuf[i]); + } + applog(LOG_DEBUG, "Read data(%d):%s", relen, msg); + } + } + } + + if (opt_bmsc_bandops) { + unsigned char tmpbyte = 0; + cmd_buf[0] = core_cmd[0]; + cmd_buf[1] = core_cmd[1]; + cmd_buf[2] = core_cmd[2]; + tmpbyte = core_cmd[3] & 0xE0; + cmd_buf[3] = tmpbyte; + cmd_buf[3] = CRC5(cmd_buf, 27); + cmd_buf[3] |= tmpbyte; + + applog(LOG_ERR, "-----------------start bandops-------------------"); + applog(LOG_ERR, "SetBandOPS cmd:%02x%02x%02x%02x corenum:%d enable:%s sleep:%d", core_cmd[0], core_cmd[1], core_cmd[2], core_cmd[3], corenum, coreenable, coresleep); + cgsleep_ms(500); + applog(LOG_ERR, "Send bandops %02x%02x%02x%02x", cmd_buf[0], cmd_buf[1], cmd_buf[2], cmd_buf[3]); + err = usb_write_ii(bmsc, info->intinfo, (char * )cmd_buf, 4, &amount, C_SENDWORK); + if (err != LIBUSB_SUCCESS || amount != 4) { + applog(LOG_ERR, "%s%i: Write BandOPS Comms error (werr=%d amount=%d)", bmsc->drv->name, bmsc->device_id, err, amount); + continue; + } + applog(LOG_DEBUG, "Send bandops command ok"); + for(i = 0; i < corenum; i++) { + if(coreenable[i] == '1') { + bandops_ob[127] = '1'; + } else { + bandops_ob[127] = '0'; + } + amount = 0; + hex2bin((void *)(&workdata), bandops_ob, sizeof(workdata)); + applog(LOG_ERR, "Send %d %s", i, bandops_ob); + err = usb_write_ii(bmsc, info->intinfo, (char *)(&workdata), sizeof(workdata), &amount, C_SENDWORK); + if (err != LIBUSB_SUCCESS || amount != sizeof(workdata)) { + applog(LOG_ERR, "%d %s%i: Write BandOPS Enable Comms error (werr=%d amount=%d)", i, bmsc->drv->name, bmsc->device_id, err, amount); + break; + } + if(coresleep > 0) { + cgsleep_ms(coresleep); + } + } + if(i >= corenum) { + applog(LOG_DEBUG, "Send bandops core enable ok"); + } else { + continue; + } + } + cgsleep_ms(1000); + + applog(LOG_ERR, "-----------------start nonce------------------"); +#if 0 + applog(LOG_ERR, "Bmsc send golden nonce"); + + hex2bin((void *)(&workdata), golden_ob, sizeof(workdata)); + err = usb_write_ii(bmsc, info->intinfo, (char *)(&workdata), sizeof(workdata), &amount, C_SENDWORK); + if (err != LIBUSB_SUCCESS || amount != sizeof(workdata)) + continue; + + memset(nonce_bin, 0, sizeof(nonce_bin)); + ret = bmsc_get_nonce(bmsc, nonce_bin, &tv_start, &tv_finish, NULL, 500); + if (ret != BTM_NONCE_OK) { + applog(LOG_ERR, "Bmsc recv golden nonce timeout"); + continue; + } + + nonce_hex = bin2hex(nonce_bin, sizeof(nonce_bin)); + if (strncmp(nonce_hex, golden_nonce, 8) == 0) + ok = true; + else { + applog(LOG_ERR, "Bmsc recv golden nonce %s != %s and retry", nonce_hex, golden_nonce); + if (tries < 0 && info->ident != IDENT_CMR2) { + applog(LOG_ERR, "Bmsc Detect: Test failed at %s: get %s, should: %s", + bmsc->device_path, nonce_hex, golden_nonce); + } + } + + applog(LOG_ERR, "Bmsc recv golden nonce %s -- %s ", nonce_hex, golden_nonce); +#else + applog(LOG_ERR, "Bmsc send golden nonce1"); + hex2bin((void *)(&workdata), golden_ob1, sizeof(workdata)); + err = usb_write_ii(bmsc, info->intinfo, (char *)(&workdata), sizeof(workdata), &amount, C_SENDWORK); + if (err != LIBUSB_SUCCESS || amount != sizeof(workdata)) + continue; + + memset(nonce_bin, 0, sizeof(nonce_bin)); + ret = bmsc_get_nonce(bmsc, nonce_bin, &tv_start, &tv_finish, NULL, 500); + if (ret != BTM_NONCE_OK) { + applog(LOG_ERR, "Bmsc recv golden nonce timeout"); + continue; + } + + nonce_hex = bin2hex(nonce_bin, sizeof(nonce_bin)); + if (strncmp(nonce_hex, golden_nonce1, 8) == 0) + ok = true; + else { + applog(LOG_ERR, "Bmsc recv golden nonce %s != %s and retry", nonce_hex, golden_nonce1); + applog(LOG_ERR,"The first chip may not work,reconnrect the device will get better stats"); + cgsleep_ms(1000); + if (tries < 0 && info->ident != IDENT_CMR2) { + applog(LOG_ERR, "Bmsc Detect: Test failed at %s: get %s, should: %s", + bmsc->device_path, nonce_hex, golden_nonce1); + } + } + applog(LOG_ERR, "Bmsc recv golden nonce1 %s -- %s ", nonce_hex, golden_nonce1); + + applog(LOG_ERR, "Bmsc send golden nonce2"); + + hex2bin((void *)(&workdata), golden_ob2, sizeof(workdata)); + err = usb_write_ii(bmsc, info->intinfo, (char *)(&workdata), sizeof(workdata), &amount, C_SENDWORK); + if (err != LIBUSB_SUCCESS || amount != sizeof(workdata)) + continue; + + memset(nonce_bin, 0, sizeof(nonce_bin)); + ret = bmsc_get_nonce(bmsc, nonce_bin, &tv_start, &tv_finish, NULL, 500); + if (ret != BTM_NONCE_OK) { + applog(LOG_ERR, "Bmsc recv golden nonce timeout"); + continue; + } + + nonce_hex = bin2hex(nonce_bin, sizeof(nonce_bin)); + if (strncmp(nonce_hex, golden_nonce2, 8) == 0) + ok = true; + else { + applog(LOG_ERR, "Bmsc recv golden nonce %s != %s and retry", nonce_hex, golden_nonce2); + applog(LOG_ERR,"The second chip may not work,reconnrect the device will get better stats"); + cgsleep_ms(1000); + if (tries < 0 && info->ident != IDENT_CMR2) { + applog(LOG_ERR, "Bmsc Detect: Test failed at %s: get %s, should: %s", + bmsc->device_path, nonce_hex, golden_nonce2); + } + } + applog(LOG_ERR, "Bmsc recv golden nonce2 %s -- %s ", nonce_hex, golden_nonce2); + applog(LOG_ERR, "Bmsc send golden nonce3"); + hex2bin((void *)(&workdata), golden_ob3, sizeof(workdata)); + err = usb_write_ii(bmsc, info->intinfo, (char *)(&workdata), sizeof(workdata), &amount, C_SENDWORK); + if (err != LIBUSB_SUCCESS || amount != sizeof(workdata)) + continue; + + memset(nonce_bin, 0, sizeof(nonce_bin)); + ret = bmsc_get_nonce(bmsc, nonce_bin, &tv_start, &tv_finish, NULL, 500); + if (ret != BTM_NONCE_OK) { + applog(LOG_ERR, "Bmsc recv golden nonce timeout"); + continue; + } + + nonce_hex = bin2hex(nonce_bin, sizeof(nonce_bin)); + if (strncmp(nonce_hex, golden_nonce3, 8) == 0) + ok = true; + else { + applog(LOG_ERR, "Bmsc recv golden nonce %s != %s and retry", nonce_hex, golden_nonce3); + applog(LOG_ERR,"The third chip may not work,reconnrect the device will get better stats"); + cgsleep_ms(1000); + if (tries < 0 && info->ident != IDENT_CMR2) { + applog(LOG_ERR, "Bmsc Detect: Test failed at %s: get %s, should: %s", + bmsc->device_path, nonce_hex, golden_nonce3); + } + } + + applog(LOG_ERR, "Bmsc recv golden nonce %s -- %s ", nonce_hex, golden_nonce3); + applog(LOG_ERR, "Bmsc send golden nonce4"); + + hex2bin((void *)(&workdata), golden_ob4, sizeof(workdata)); + err = usb_write_ii(bmsc, info->intinfo, (char *)(&workdata), sizeof(workdata), &amount, C_SENDWORK); + if (err != LIBUSB_SUCCESS || amount != sizeof(workdata)) + continue; + + memset(nonce_bin, 0, sizeof(nonce_bin)); + ret = bmsc_get_nonce(bmsc, nonce_bin, &tv_start, &tv_finish, NULL, 500); + if (ret != BTM_NONCE_OK) { + applog(LOG_ERR, "Bmsc recv golden nonce4 timeout"); + continue; + } + + nonce_hex = bin2hex(nonce_bin, sizeof(nonce_bin)); + if (strncmp(nonce_hex, golden_nonce4, 8) == 0) + ok = true; + + else { + applog(LOG_ERR, "Bmsc recv golden nonce %s != %s and retry", nonce_hex, golden_nonce4); + applog(LOG_ERR,"The fourth chip may not work,reconnrect the device will get better stats"); + cgsleep_ms(1000); + if (tries < 0 && info->ident != IDENT_CMR2) { + applog(LOG_ERR, "Bmsc Detect: Test failed at %s: get %s, should: %s", + bmsc->device_path, nonce_hex, golden_nonce4); + } + } + + applog(LOG_ERR, "Bmsc recv golden nonce %s -- %s ", nonce_hex, golden_nonce4); +#endif + free(nonce_hex); + } + + if (!ok) { + if (info->ident != IDENT_CMR2) + goto unshin; + + if (info->intinfo < CAIRNSMORE2_INTS-1) { + info->intinfo++; + goto cmr2_retry; + } + } else { + if (info->ident == IDENT_CMR2) { + applog(LOG_DEBUG, + "Bmsc Detect: " + "Test succeeded at %s i%d: got %s", + bmsc->device_path, info->intinfo, golden_nonce); + + cmr2_ok[info->intinfo] = true; + cmr2_count++; + if (info->intinfo < CAIRNSMORE2_INTS-1) { + info->intinfo++; + goto cmr2_retry; + } + } + } + + if (info->ident == IDENT_CMR2) { + if (cmr2_count == 0) { + applog(LOG_ERR, + "Bmsc Detect: Test failed at %s: for all %d CMR2 Interfaces", + bmsc->device_path, CAIRNSMORE2_INTS); + goto unshin; + } + + // set the interface to the first one that succeeded + for (i = 0; i < CAIRNSMORE2_INTS; i++) + if (cmr2_ok[i]) { + info->intinfo = i; + break; + } + } else { + applog(LOG_DEBUG, + "Bmsc Detect: " + "Test succeeded at %s: got %s", + bmsc->device_path, golden_nonce); + } + + /* We have a real Bmsc! */ + if (!add_cgpu(bmsc)) + goto unshin; + + update_usb_stats(bmsc); + + applog(LOG_INFO, "%s%d: Found at %s", + bmsc->drv->name, bmsc->device_id, bmsc->device_path); + + if (info->ident == IDENT_CMR2) { + applog(LOG_INFO, "%s%d: with %d Interface%s", + bmsc->drv->name, bmsc->device_id, + cmr2_count, cmr2_count > 1 ? "s" : ""); + + // Assume 1 or 2 are running FPGA pairs + if (cmr2_count < 3) { + work_division = fpga_count = 2; + info->Hs /= 2; + } + } + + applog(LOG_DEBUG, "%s%d: Init baud=%d work_division=%d fpga_count=%d readtimeout=%f", + bmsc->drv->name, bmsc->device_id, baud, work_division, fpga_count, readtimeout); + + info->baud = baud; + info->work_division = work_division; + info->fpga_count = fpga_count; + info->nonce_mask = mask(work_division); + info->work_queue_index = 0; + for(k = 0; k < BMSC_WORK_QUEUE_NUM; k++) { + info->work_queue[k] = NULL; + } + + info->golden_hashes = (golden_nonce_val & info->nonce_mask) * fpga_count; + timersub(&tv_finish, &tv_start, &(info->golden_tv)); + + set_timing_mode(this_option_offset, bmsc, readtimeout); + + if (info->ident == IDENT_CMR2) { + int i; + for (i = info->intinfo + 1; i < bmsc->usbdev->found->intinfo_count; i++) { + struct cgpu_info *cgtmp; + struct BMSC_INFO *intmp; + + if (!cmr2_ok[i]) + continue; + + cgtmp = usb_copy_cgpu(bmsc); + if (!cgtmp) { + applog(LOG_ERR, "%s%d: Init failed initinfo %d", + bmsc->drv->name, bmsc->device_id, i); + continue; + } + + cgtmp->usbinfo.usbstat = USB_NOSTAT; + + intmp = (struct BMSC_INFO *)malloc(sizeof(struct BMSC_INFO)); + if (unlikely(!intmp)) + quit(1, "Failed2 to malloc BMSC_INFO"); + + cgtmp->device_data = (void *)intmp; + + // Initialise everything to match + memcpy(intmp, info, sizeof(struct BMSC_INFO)); + + intmp->intinfo = i; + + bmsc_initialise(cgtmp, baud); + + if (!add_cgpu(cgtmp)) { + usb_uninit(cgtmp); + free(intmp); + continue; + } + + update_usb_stats(cgtmp); + } + } + + return bmsc; + +unshin: + + usb_uninit(bmsc); + free(info); + bmsc->device_data = NULL; + +shin: + + bmsc = usb_free_cgpu(bmsc); + + return NULL; +} + +static void bmsc_detect(bool __maybe_unused hotplug) +{ + usb_detect(&bmsc_drv, bmsc_detect_one); +} + +static bool bmsc_prepare(__maybe_unused struct thr_info *thr) +{ +// struct cgpu_info *bmsc = thr->cgpu; + return true; +} + +static void cmr2_command(struct cgpu_info *bmsc, uint8_t cmd, uint8_t data) +{ + struct BMSC_INFO *info = (struct BMSC_INFO *)(bmsc->device_data); + struct BMSC_WORK workdata; + int amount; + + memset((void *)(&workdata), 0, sizeof(workdata)); + + workdata.prefix = BMSC_CMR2_PREFIX; + workdata.cmd = cmd; + workdata.data = data; + workdata.check = workdata.data ^ workdata.cmd ^ workdata.prefix ^ BMSC_CMR2_CHECK; + + usb_write_ii(bmsc, info->intinfo, (char *)(&workdata), sizeof(workdata), &amount, C_SENDWORK); +} + +static void cmr2_commands(struct cgpu_info *bmsc) +{ + struct BMSC_INFO *info = (struct BMSC_INFO *)(bmsc->device_data); + + if (info->speed_next_work) { + info->speed_next_work = false; + cmr2_command(bmsc, BMSC_CMR2_CMD_SPEED, info->cmr2_speed); + return; + } + + if (info->flash_next_work) { + info->flash_next_work = false; + cmr2_command(bmsc, BMSC_CMR2_CMD_FLASH, BMSC_CMR2_DATA_FLASH_ON); + cgsleep_ms(250); + cmr2_command(bmsc, BMSC_CMR2_CMD_FLASH, BMSC_CMR2_DATA_FLASH_OFF); + cgsleep_ms(250); + cmr2_command(bmsc, BMSC_CMR2_CMD_FLASH, BMSC_CMR2_DATA_FLASH_ON); + cgsleep_ms(250); + cmr2_command(bmsc, BMSC_CMR2_CMD_FLASH, BMSC_CMR2_DATA_FLASH_OFF); + return; + } +} + +static int64_t bmsc_scanwork(struct thr_info *thr) +{ + struct cgpu_info *bmsc = thr->cgpu; + struct BMSC_INFO *info = (struct BMSC_INFO *)(bmsc->device_data); + int ret, err, amount; + unsigned char nonce_bin[BMSC_READ_SIZE]; + struct BMSC_WORK workdata; + char *ob_hex; + uint32_t nonce; + int64_t hash_count = 0; + struct timeval tv_start, tv_finish, elapsed; + struct timeval tv_history_start, tv_history_finish; + double Ti, Xi; + int curr_hw_errors, i; + bool was_hw_error; + struct work *work = NULL; + struct work *worktmp = NULL; + + struct BMSC_HISTORY *history0, *history; + int count; + double Hs, W, fullnonce; + int read_time; + bool limited; + int64_t estimate_hashes; + uint32_t values; + int64_t hash_count_range; + unsigned char workid = 0; + int submitfull = 0; + bool submitnonceok = true; + + // Device is gone + if (bmsc->usbinfo.nodev) + return -1; + + elapsed.tv_sec = elapsed.tv_usec = 0; + +retry: + work = get_work(thr, thr->id); + memset((void *)(&workdata), 0, sizeof(workdata)); + memcpy(&(workdata.midstate), work->midstate, BMSC_MIDSTATE_SIZE); + memcpy(&(workdata.work), work->data + BMSC_WORK_DATA_OFFSET, BMSC_WORK_SIZE); + rev((void *)(&(workdata.midstate)), BMSC_MIDSTATE_SIZE); + rev((void *)(&(workdata.work)), BMSC_WORK_SIZE); + + if(work->midstate[BMSC_MIDSTATE_SIZE-1] == 0xaa) + goto retry; + workdata.workid = work->id; + workid = work->id; + workid = workid & 0x1F; + + // We only want results for the work we are about to send + usb_buffer_clear(bmsc); + + if(info->work_queue[workid]) { + free(info->work_queue[workid]); + info->work_queue[workid] = NULL; + } + info->work_queue[workid] = copy_work(work); + + err = usb_write_ii(bmsc, info->intinfo, (char *)(&workdata), sizeof(workdata), &amount, C_SENDWORK); + if (err < 0 || amount != sizeof(workdata)) { + applog(LOG_ERR, "%s%i: Comms error (werr=%d amt=%d)", bmsc->drv->name, bmsc->device_id, err, amount); + dev_error(bmsc, REASON_DEV_COMMS_ERROR); + bmsc_initialise(bmsc, info->baud); + goto out; + } + + if (opt_debug) { + ob_hex = bin2hex((void *)(&workdata), sizeof(workdata)); + applog(LOG_DEBUG, "%s%d: sent %s", bmsc->drv->name, bmsc->device_id, ob_hex); + free(ob_hex); + } + + /* Bmsc will return 4 bytes (BMSC_READ_SIZE) nonces or nothing */ + memset(nonce_bin, 0, sizeof(nonce_bin)); + ret = bmsc_get_nonce(bmsc, nonce_bin, &tv_start, &tv_finish, thr, info->read_time); + if (ret == BTM_NONCE_ERROR) + goto out; + + // aborted before becoming idle, get new work + if (ret == BTM_NONCE_TIMEOUT || ret == BTM_NONCE_RESTART) { + timersub(&tv_finish, &tv_start, &elapsed); + + // ONLY up to just when it aborted + // We didn't read a reply so we don't subtract BMSC_READ_TIME + estimate_hashes = ((double)(elapsed.tv_sec) + ((double)(elapsed.tv_usec))/((double)1000000)) / info->Hs; + + // If some Serial-USB delay allowed the full nonce range to + // complete it can't have done more than a full nonce + if (unlikely(estimate_hashes > 0xffffffff)) + estimate_hashes = 0xffffffff; + + applog(LOG_DEBUG, "%s%d: no nonce = 0x%08lX hashes (%ld.%06lds)", bmsc->drv->name, bmsc->device_id, (long unsigned int)estimate_hashes, elapsed.tv_sec, elapsed.tv_usec); + + hash_count = 0; + goto out; + } + + memcpy((char *)&nonce, nonce_bin, sizeof(nonce_bin)); + nonce = htobe32(nonce); + curr_hw_errors = bmsc->hw_errors; + + workid = nonce_bin[4]; + workid = workid & 0x1F; + worktmp = info->work_queue[workid]; + if(info->start && workid == 0x1f){ + goto out; + }else{ + info->start = false; + } + if(worktmp) { + submitfull = 0; + if(submit_nonce_1(thr, worktmp, nonce, &submitfull)) { + submitnonceok = true; + submit_nonce_2(worktmp); + } else { + if(submitfull) { + submitnonceok = true; + } else { + submitnonceok = false; + } + } + cg_logwork(worktmp, nonce_bin, submitnonceok); + } else { + applog(LOG_ERR, "%s%d: work %02x not find error", bmsc->drv->name, bmsc->device_id, workid); + } + + was_hw_error = (curr_hw_errors > bmsc->hw_errors); + + hash_count = (nonce & info->nonce_mask); + hash_count++; + hash_count *= info->fpga_count; + + hash_count = 0xffffffff; + + if (opt_debug || info->do_bmsc_timing) + timersub(&tv_finish, &tv_start, &elapsed); + + applog(LOG_DEBUG, "%s%d: nonce = 0x%08x = 0x%08lX hashes (%ld.%06lds)", bmsc->drv->name, bmsc->device_id, nonce, (long unsigned int)hash_count, elapsed.tv_sec, elapsed.tv_usec); + +out: + free_work(work); + return hash_count; +} +/* +static int64_t bmsc_scanwork(struct thr_info *thr) +{ + struct cgpu_info *bmsc = thr->cgpu; + struct BMSC_INFO *info = (struct BMSC_INFO *)(bmsc->device_data); + int ret, err, amount; + unsigned char nonce_bin[BMSC_READ_SIZE]; + struct BMSC_WORK workdata; + char *ob_hex; + uint32_t nonce; + int64_t hash_count = 0; + int64_t hash_done = 0; + struct timeval tv_start, tv_finish, elapsed; + struct timeval tv_history_start, tv_history_finish; + double Ti, Xi; + int curr_hw_errors; + bool was_hw_error; + struct work *work; + + struct BMSC_HISTORY *history0, *history; + double Hs, W, fullnonce; + bool limited; + int64_t estimate_hashes; + uint32_t values; + int64_t hash_count_range; + + int i = 0, count = 0, nofullcount = 0, readalllen = 0, readlen = 0, read_time = 0, nofull = 0; + bool nonceok = false; + bool noncedup = false; + + char testbuf[256] = {0}; + char testtmp[256] = {0}; + int asicnum = 0; + int k = 0; + + // Device is gone + if (bmsc->usbinfo.nodev) + return -1; + + elapsed.tv_sec = elapsed.tv_usec = 0; + + work = get_work(thr, thr->id); + memset((void *)(&workdata), 0, sizeof(workdata)); + memcpy(&(workdata.midstate), work->midstate, BMSC_MIDSTATE_SIZE); + memcpy(&(workdata.work), work->data + BMSC_WORK_DATA_OFFSET, BMSC_WORK_SIZE); + rev((void *)(&(workdata.midstate)), BMSC_MIDSTATE_SIZE); + rev((void *)(&(workdata.work)), BMSC_WORK_SIZE); + + applog(LOG_DEBUG, "bmsc_scanhash start ------------"); + + readalllen = 0; + readlen = 0; + if(info->work_queue != NULL) { + while (true) { + if (bmsc->usbinfo.nodev) + return -1; + amount = 0; + memset(nonce_bin, 0, sizeof(nonce_bin)); + err = usb_read_once_timeout(bmsc, (char *)nonce_bin+readlen, 5-readlen, &amount, BMSC_WAIT_TIMEOUT, C_GETRESULTS); + if (err < 0 && err != LIBUSB_ERROR_TIMEOUT) { + applog(LOG_ERR, "%s%i: Comms error (rerr=%d amt=%d)", bmsc->drv->name, bmsc->device_id, err, amount); + dev_error(bmsc, REASON_DEV_COMMS_ERROR); + return 0; + } + if (amount > 0) { + readalllen += amount; + readlen += amount; + if (readlen >= 5) { + nonceok = false; + + memcpy((char *) &nonce, nonce_bin, BMSC_READ_SIZE); + noncedup = false; + for(i = 0; i < BMSC_NONCE_ARRAY_SIZE; i++) { + if(memcmp(nonce_bin, info->nonce_bin[i], BMSC_READ_SIZE) == 0) { + noncedup = true; + break; + } + } + if (!noncedup) { + if(info->nonce_index < 0 || info->nonce_index >= BMSC_NONCE_ARRAY_SIZE) + info->nonce_index = 0; + + memcpy(info->nonce_bin[info->nonce_index], nonce_bin, BMSC_READ_SIZE); + info->nonce_index++; + + nonce = htobe32(nonce); + + nofull = 0; + if (submit_nonce_1(thr, info->work_queue, nonce, &nofull)) { + applog(LOG_DEBUG, "Bmsc nonce(0x%08x) match old work", nonce); + submit_nonce_2(info->work_queue); + nonceok = true; + } else { + if(!nofull) { + applog(LOG_DEBUG, "Bmsc nonce(0x%08x) not match old work", nonce); + usb_buffer_clear(bmsc); + inc_hw_errors(thr); + break; + } else { + nofullcount++; + } + } + } else { + applog(LOG_DEBUG, "Bmsc nonce duplication"); + } + + if (nonceok) { + count++; + hash_count = (nonce & info->nonce_mask); + hash_count++; + hash_count *= info->fpga_count; + hash_done += 0xffffffff;//hash_count; + + applog(LOG_DEBUG, "%s%d: nonce = 0x%08x = 0x%08lX hashes (%ld.%06lds)", + bmsc->drv->name, bmsc->device_id, nonce, (long unsigned int )hash_count, elapsed.tv_sec, elapsed.tv_usec); + } + readlen = 0; + } + } else { + //usb_buffer_clear(bmsc); + applog(LOG_DEBUG, "bmsc_scanhash usb_read_once_timeout read time out"); + break; + } + } + } + + err = usb_write(bmsc, (char *)(&workdata), sizeof(workdata), &amount, C_SENDWORK); + if (err < 0 || amount != sizeof(workdata)) { + applog(LOG_ERR, "%s%i: Comms error (werr=%d amt=%d)", bmsc->drv->name, bmsc->device_id, err, amount); + dev_error(bmsc, REASON_DEV_COMMS_ERROR); + bmsc_initialise(bmsc, info->baud); + return 0; + } + + if (opt_debug) { + ob_hex = bin2hex((char *)&workdata, sizeof(workdata)); + applog(LOG_DEBUG, "%s%d: sent %s", bmsc->drv->name, bmsc->device_id, ob_hex); + free(ob_hex); + } + + cgtime(&tv_start); + readlen = 0; + while(true) { + if (bmsc->usbinfo.nodev) + return -1; + amount = 0; + memset(nonce_bin, 0, sizeof(nonce_bin)); + err = usb_read_once_timeout(bmsc, (char *)nonce_bin+readlen, 5-readlen, &amount, BMSC_WAIT_TIMEOUT, C_GETRESULTS); + cgtime(&tv_finish); + if (err < 0 && err != LIBUSB_ERROR_TIMEOUT) { + applog(LOG_ERR, "%s%i: Comms error (rerr=%d amt=%d)", bmsc->drv->name, bmsc->device_id, err, amount); + dev_error(bmsc, REASON_DEV_COMMS_ERROR); + return 0; + } + if(amount > 0) { + readalllen += amount; + readlen += amount; + if (readlen >= 5) { + nonceok = false; + + memcpy((char *) &nonce, nonce_bin, BMSC_READ_SIZE); + noncedup = false; + for(i = 0; i < BMSC_NONCE_ARRAY_SIZE; i++) { + if(memcmp(nonce_bin, info->nonce_bin[i], BMSC_READ_SIZE) == 0) { + noncedup = true; + break; + } + } + if(!noncedup) { + if(info->nonce_index < 0 || info->nonce_index >= BMSC_NONCE_ARRAY_SIZE) + info->nonce_index = 0; + + memcpy(info->nonce_bin[info->nonce_index], nonce_bin, BMSC_READ_SIZE); + info->nonce_index++; + + nonce = htobe32(nonce); + nofull = 0; + if (submit_nonce_1(thr, work, nonce, &nofull)) { + applog(LOG_DEBUG, "Bmsc nonce(0x%08x) match current work", nonce); + submit_nonce_2(work); + nonceok = true; + } else { + if(!nofull) { + if (info->work_queue != NULL) { + nofull = 0; + if (submit_nonce_1(thr, info->work_queue, nonce, &nofull)) { + applog(LOG_DEBUG, "Bmsc nonce(0x%08x) match old work", nonce); + submit_nonce_2(info->work_queue); + nonceok = true; + } else { + if(!nofull) { + applog(LOG_DEBUG, "Bmsc nonce(0x%08x) not match work", nonce); + usb_buffer_clear(bmsc); + inc_hw_errors(thr); + break; + } else { + nofullcount++; + } + } + } else { + applog(LOG_DEBUG, "Bmsc nonce(0x%08x) no old work", nonce); + } + } else { + nofullcount++; + } + } + } else { + applog(LOG_DEBUG, "Bmsc nonce duplication"); + } + + if(nonceok) { + count++; + hash_count = (nonce & info->nonce_mask); + hash_count++; + hash_count *= info->fpga_count; + hash_done += 0xffffffff;//hash_count; + + applog(LOG_DEBUG, "%s%d: nonce = 0x%08x = 0x%08lX hashes (%ld.%06lds)", + bmsc->drv->name, bmsc->device_id, nonce, (long unsigned int )hash_count, elapsed.tv_sec, elapsed.tv_usec); + } + readlen = 0; + } + } else { + applog(LOG_DEBUG, "bmsc_scanhash usb_read_once_timeout read time out"); + } + + read_time = SECTOMS(tdiff(&tv_finish, &tv_start)); + if(read_time >= info->read_time) { + if (readalllen > 0) + applog(LOG_DEBUG, "Bmsc Read: Nonce ok:%d below:%d in %d ms", count, nofullcount, read_time); + else + applog(LOG_DEBUG, "Bmsc Read: No nonce work %d for %d ms", work->id, read_time); + + break; + } + } + + if(info->work_queue != NULL) { + free_work(info->work_queue); + info->work_queue = NULL; + } + + info->work_queue = copy_work(work); + + applog(LOG_DEBUG, "bmsc_scanhash stop ------------"); +out: + free_work(work); + return hash_count; +}*/ + +static struct api_data *bmsc_api_stats(struct cgpu_info *cgpu) +{ + struct api_data *root = NULL; + struct BMSC_INFO *info = (struct BMSC_INFO *)(cgpu->device_data); + + // Warning, access to these is not locked - but we don't really + // care since hashing performance is way more important than + // locking access to displaying API debug 'stats' + // If locking becomes an issue for any of them, use copy_data=true also + root = api_add_int(root, "read_time", &(info->read_time), false); + root = api_add_int(root, "read_time_limit", &(info->read_time_limit), false); + root = api_add_double(root, "fullnonce", &(info->fullnonce), false); + root = api_add_int(root, "count", &(info->count), false); + root = api_add_hs(root, "Hs", &(info->Hs), false); + root = api_add_double(root, "W", &(info->W), false); + root = api_add_uint(root, "total_values", &(info->values), false); + root = api_add_uint64(root, "range", &(info->hash_count_range), false); + root = api_add_uint64(root, "history_count", &(info->history_count), false); + root = api_add_timeval(root, "history_time", &(info->history_time), false); + root = api_add_uint(root, "min_data_count", &(info->min_data_count), false); + root = api_add_uint(root, "timing_values", &(info->history[0].values), false); + root = api_add_const(root, "timing_mode", timing_mode_str(info->timing_mode), false); + root = api_add_bool(root, "is_timing", &(info->do_bmsc_timing), false); + root = api_add_int(root, "baud", &(info->baud), false); + root = api_add_int(root, "work_division", &(info->work_division), false); + root = api_add_int(root, "fpga_count", &(info->fpga_count), false); + + return root; +} + +static void bmsc_statline_before(char *buf, size_t bufsiz, struct cgpu_info *cgpu) +{ + struct BMSC_INFO *info = (struct BMSC_INFO *)(cgpu->device_data); + + if (info->ident == IDENT_CMR2 && info->cmr2_speed > 0) + tailsprintf(buf, bufsiz, "%5.1fMhz", (float)(info->cmr2_speed) * BMSC_CMR2_SPEED_FACTOR); + else + tailsprintf(buf, bufsiz, " "); + + tailsprintf(buf, bufsiz, " | "); +} + +static void bmsc_shutdown(__maybe_unused struct thr_info *thr) +{ + // TODO: ? +} + +static void bmsc_identify(struct cgpu_info *cgpu) +{ + struct BMSC_INFO *info = (struct BMSC_INFO *)(cgpu->device_data); + + if (info->ident == IDENT_CMR2) + info->flash_next_work = true; +} + +static char *bmsc_set(struct cgpu_info *cgpu, char *option, char *setting, char *replybuf) +{ + struct BMSC_INFO *info = (struct BMSC_INFO *)(cgpu->device_data); + int val; + + if (info->ident != IDENT_CMR2) { + strcpy(replybuf, "no set options available"); + return replybuf; + } + + if (strcasecmp(option, "help") == 0) { + sprintf(replybuf, "clock: range %d-%d", + BMSC_CMR2_SPEED_MIN_INT, BMSC_CMR2_SPEED_MAX_INT); + return replybuf; + } + + if (strcasecmp(option, "clock") == 0) { + if (!setting || !*setting) { + sprintf(replybuf, "missing clock setting"); + return replybuf; + } + + val = atoi(setting); + if (val < BMSC_CMR2_SPEED_MIN_INT || val > BMSC_CMR2_SPEED_MAX_INT) { + sprintf(replybuf, "invalid clock: '%s' valid range %d-%d", + setting, + BMSC_CMR2_SPEED_MIN_INT, + BMSC_CMR2_SPEED_MAX_INT); + } + + info->cmr2_speed = CMR2_INT_TO_SPEED(val); + info->speed_next_work = true; + + return NULL; + } + + sprintf(replybuf, "Unknown option: %s", option); + return replybuf; +} + +struct device_drv bmsc_drv = { + .drv_id = DRIVER_bmsc, + .dname = "Bitmain", + .name = "BTM", + .drv_detect = bmsc_detect, + .hash_work = &hash_driver_work, + .get_api_stats = bmsc_api_stats, + .get_statline_before = bmsc_statline_before, + .set_device = bmsc_set, + .identify_device = bmsc_identify, + .thread_prepare = bmsc_prepare, + .scanwork = bmsc_scanwork, + .thread_shutdown = bmsc_shutdown, +}; diff --git a/driver-btm-c5.c b/driver-btm-c5.c new file mode 100644 index 0000000..7286f74 --- /dev/null +++ b/driver-btm-c5.c @@ -0,0 +1,4824 @@ +#include "config.h" +#include + +#include +#include +#include +#include +#include +#include +#include +#include + + +#ifndef WIN32 +#include +#include +#include +#include +#ifndef O_CLOEXEC +#define O_CLOEXEC 0 +#endif +#else +#include "compat.h" +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "elist.h" +#include "miner.h" +#include "usbutils.h" +#include "hexdump.c" +#include "util.h" +#include "driver-btm-c5.h" +#include "sha2_c5.h" + +//global various +int fd; // axi fpga +int fd_fpga_mem; // fpga memory +int fpga_version; +int pcb_version; +unsigned int *axi_fpga_addr = NULL; // axi address +unsigned int *fpga_mem_addr = NULL; // fpga memory address +unsigned int *nonce2_jobid_address = NULL; // the value should be filled in NONCE2_AND_JOBID_STORE_ADDRESS +unsigned int *job_start_address_1 = NULL; // the value should be filled in JOB_START_ADDRESS +unsigned int *job_start_address_2 = NULL; // the value should be filled in JOB_START_ADDRESS +struct thr_info *read_nonce_reg_id; // thread id for read nonce and register +struct thr_info *check_system_work_id; // thread id for check system +struct thr_info *read_temp_id; +struct thr_info *read_hash_rate; +struct thr_info *pic_heart_beat; +struct thr_info *change_voltage_to_old; +struct thr_info *send_mac_thr; + + + +bool gBegin_get_nonce = false; +struct timeval tv_send_job = {0, 0}; + +pthread_mutex_t reg_mutex = PTHREAD_MUTEX_INITIALIZER; +pthread_mutex_t nonce_mutex = PTHREAD_MUTEX_INITIALIZER; +pthread_mutex_t reg_read_mutex = PTHREAD_MUTEX_INITIALIZER; +pthread_mutex_t iic_mutex = PTHREAD_MUTEX_INITIALIZER; + +uint64_t h = 0; + + +uint32_t given_id = 2; +uint32_t c_coinbase_padding = 0; +uint32_t c_merkles_num = 0; +uint32_t l_coinbase_padding = 0; +uint32_t l_merkles_num = 0; +int last_temperature = 0, temp_highest = 0; + +bool opt_bitmain_fan_ctrl = false; +int opt_bitmain_fan_pwm = 0; +int opt_bitmain_c5_freq = 600; +int opt_bitmain_c5_voltage = 176; +int ADD_FREQ = 0; +int ADD_FREQ1 = 0; +uint8_t de_voltage = 176; + +bool opt_bitmain_new_cmd_type_vil = false; +bool status_error = false; +bool once_error = false; +bool iic_ok = false; +int check_iic = 0; +bool update_temp =false; +uint64_t rate[BITMAIN_MAX_CHAIN_NUM] = {0}; +int rate_error[BITMAIN_MAX_CHAIN_NUM] = {0}; +char displayed_rate[BITMAIN_MAX_CHAIN_NUM][16]; +uint8_t chain_voltage[BITMAIN_MAX_CHAIN_NUM] = {0}; +unsigned char hash_board_id[BITMAIN_MAX_CHAIN_NUM][12]; + + +#define id_string_len 34 +#define AUTH_URL "auth.minerlink.com" +#define PORT "7000" + +static bool need_send = true; +char * mac; +bool stop_mining = false; +char hash_board_id_string[BITMAIN_MAX_CHAIN_NUM*id_string_len]; + + +struct nonce_content temp_nonce_buf[MAX_RETURNED_NONCE_NUM]; +struct reg_content temp_reg_buf[MAX_RETURNED_NONCE_NUM]; +struct nonce_buf nonce_read_out; +struct reg_buf reg_value_buf; + + +#define USE_IIC 1 +#define TEMP_CALI 0 +#define MID_OR_BOT 1 + + +#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) + + +void *gpio0_vaddr=NULL; +struct all_parameters *dev; +unsigned int is_first_job = 0; + +//other equipment related + +// -------------------------------------------------------------- +// CRC16 check table +// -------------------------------------------------------------- +const uint8_t chCRCHTalbe[] = // CRC high byte table +{ + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, + 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, + 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40 +}; + +const uint8_t chCRCLTalbe[] = // CRC low byte table +{ + 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, + 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, + 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, + 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, + 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, + 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, + 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, + 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, + 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, + 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, + 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, + 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, + 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, + 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, + 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, + 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, + 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, + 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, + 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, + 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C, + 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, + 0x41, 0x81, 0x80, 0x40 +}; + + +//crc +uint16_t CRC16(const uint8_t* p_data, uint16_t w_len) +{ + uint8_t chCRCHi = 0xFF; // CRC high byte initialize + uint8_t chCRCLo = 0xFF; // CRC low byte initialize + uint16_t wIndex = 0; // CRC cycling index + + while (w_len--) + { + wIndex = chCRCLo ^ *p_data++; + chCRCLo = chCRCHi ^ chCRCHTalbe[wIndex]; + chCRCHi = chCRCLTalbe[wIndex]; + } + return ((chCRCHi << 8) | chCRCLo); +} + +unsigned char CRC5(unsigned char *ptr, unsigned char len) +{ + unsigned char i, j, k; + unsigned char crc = 0x1f; + + unsigned char crcin[5] = {1, 1, 1, 1, 1}; + unsigned char crcout[5] = {1, 1, 1, 1, 1}; + unsigned char din = 0; + + j = 0x80; + k = 0; + for (i = 0; i < len; i++) + { + if (*ptr & j) + { + din = 1; + } + else + { + din = 0; + } + crcout[0] = crcin[4] ^ din; + crcout[1] = crcin[0]; + crcout[2] = crcin[1] ^ crcin[4] ^ din; + crcout[3] = crcin[2]; + crcout[4] = crcin[3]; + + j = j >> 1; + k++; + if (k == 8) + { + j = 0x80; + k = 0; + ptr++; + } + memcpy(crcin, crcout, 5); + } + crc = 0; + if(crcin[4]) + { + crc |= 0x10; + } + if(crcin[3]) + { + crc |= 0x08; + } + if(crcin[2]) + { + crc |= 0x04; + } + if(crcin[1]) + { + crc |= 0x02; + } + if(crcin[0]) + { + crc |= 0x01; + } + return crc; +} + +// pic +unsigned int get_pic_iic() +{ + int ret = -1; + ret = *(axi_fpga_addr + IIC_COMMAND); + + applog(LOG_DEBUG,"%s: IIC_COMMAND is 0x%x\n", __FUNCTION__, ret); + return ret; +} + +unsigned char set_pic_iic(unsigned int data) +{ + unsigned int ret=0; + unsigned char ret_data = 0; + + *((unsigned int *)(axi_fpga_addr + IIC_COMMAND)) = data & 0x7fffffff; + applog(LOG_DEBUG,"%s: set IIC_COMMAND is 0x%x\n", __FUNCTION__, data & 0x7fffffff); + + while(1) + { + ret = get_pic_iic(); + if(ret & 0x80000000) + { + ret_data = (unsigned char)(ret & 0x000000ff); + return ret_data; + } + else + { + applog(LOG_DEBUG,"%s: waiting write pic iic\n", __FUNCTION__); + cgsleep_us(1000); + } + } +} + +unsigned char write_pic_iic(bool read, bool reg_addr_valid, unsigned char reg_addr, unsigned char chain, unsigned char data) +{ + unsigned int value = 0x00000000; + unsigned char ret = 0; + + if(read) + { + value |= IIC_READ; + } + + if(reg_addr_valid) + { + value |= IIC_REG_ADDR_VALID; + value |= IIC_REG_ADDR(reg_addr); + } + + value |= IIC_ADDR_HIGH_4_BIT; + + value |= IIC_CHAIN_NUMBER(chain); + + value |= data; + + ret = set_pic_iic(value); + + return ret; +} + +void send_pic_command(unsigned char chain) +{ + write_pic_iic(false, false, 0x0, chain, PIC_COMMAND_1); + write_pic_iic(false, false, 0x0, chain, PIC_COMMAND_2); +} + + +void set_pic_iic_flash_addr_pointer(unsigned char chain, unsigned char addr_H, unsigned char addr_L) +{ + send_pic_command(chain); + write_pic_iic(false, false, 0x0, chain, SET_PIC_FLASH_POINTER); + write_pic_iic(false, false, 0x0, chain, addr_H); + write_pic_iic(false, false, 0x0, chain, addr_L); +} + +void send_data_to_pic_iic(unsigned char chain, unsigned char command, unsigned char *buf, unsigned char length) +{ + unsigned char i=0; + + write_pic_iic(false, false, 0x0, chain, command); + for(i=0; ichain_exist[i] == 1) + { + enable_pic_dc_dc(i); + cgsleep_ms(1); + } + } +} + +void set_pic_voltage_all(int voltage) +{ + unsigned char i; + for(i=0; i < BITMAIN_MAX_CHAIN_NUM; i++) + { + if(dev->chain_exist[i] == 1) + { + cgsleep_ms(100); + jump_to_app_from_loader(i); + cgsleep_ms(100); + set_pic_voltage(i,voltage); + cgsleep_ms(1); + } + } +} + +void enable_pic_dac(unsigned char chain) +{ + send_pic_command(chain); + write_pic_iic(false, false, 0x0, chain, ENABLE_VOLTAGE); + write_pic_iic(false, false, 0x0, chain, 1); +} + +void disable_pic_dac(unsigned char chain) +{ + send_pic_command(chain); + write_pic_iic(false, false, 0x0, chain, ENABLE_VOLTAGE); + write_pic_iic(false, false, 0x0, chain, 0); +} + + +void pic_heart_beat_each_chain(unsigned char chain) +{ + send_pic_command(chain); + write_pic_iic(false, false, 0x0, chain, SEND_HEART_BEAT); +} + +//FPGA related +int get_nonce2_and_job_id_store_address(void) +{ + int ret = -1; + ret = *((unsigned int *)(axi_fpga_addr + NONCE2_AND_JOBID_STORE_ADDRESS)); + applog(LOG_DEBUG,"%s: NONCE2_AND_JOBID_STORE_ADDRESS is 0x%x\n", __FUNCTION__, ret); + return ret; +} + +void set_nonce2_and_job_id_store_address(unsigned int value) +{ + *((unsigned int *)(axi_fpga_addr + NONCE2_AND_JOBID_STORE_ADDRESS)) = value; + applog(LOG_DEBUG,"%s: set NONCE2_AND_JOBID_STORE_ADDRESS is 0x%x\n", __FUNCTION__, value); + get_nonce2_and_job_id_store_address(); +} + +int get_job_start_address(void) +{ + int ret = -1; + ret = *((unsigned int *)(axi_fpga_addr + JOB_START_ADDRESS)); + applog(LOG_DEBUG,"%s: JOB_START_ADDRESS is 0x%x\n", __FUNCTION__, ret); + return ret; +} + +void set_job_start_address(unsigned int value) +{ + *((unsigned int *)(axi_fpga_addr + JOB_START_ADDRESS)) = value; + applog(LOG_DEBUG,"%s: set JOB_START_ADDRESS is 0x%x\n", __FUNCTION__, value); + get_job_start_address(); +} + +int get_QN_write_data_command(void) +{ + int ret = -1; + ret = *((axi_fpga_addr + QN_WRITE_DATA_COMMAND)); + applog(LOG_DEBUG,"%s: QN_WRITE_DATA_COMMAND is 0x%x\n", __FUNCTION__, ret); + return ret; +} + +void set_QN_write_data_command(unsigned int value) +{ + *(axi_fpga_addr + QN_WRITE_DATA_COMMAND) = value; + applog(LOG_DEBUG,"%s: set QN_WRITE_DATA_COMMAND is 0x%x\n", __FUNCTION__, value); + get_QN_write_data_command(); +} + +int bitmain_axi_init() +{ + unsigned int data; + int ret=0; + + fd = open("/dev/axi_fpga_dev", O_RDWR); + if(fd < 0) + { + applog(LOG_DEBUG,"/dev/axi_fpga_dev open failed. fd = %d\n", fd); + perror("open"); + return -1; + } + + axi_fpga_addr = mmap(NULL, TOTAL_LEN, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); + if(!axi_fpga_addr) + { + applog(LOG_DEBUG,"mmap axi_fpga_addr failed. axi_fpga_addr = 0x%x\n", axi_fpga_addr); + return -1; + } + applog(LOG_DEBUG,"mmap axi_fpga_addr = 0x%x\n", axi_fpga_addr); + + //check the value in address 0xff200000 + data = *axi_fpga_addr; + if((data & 0x0000FFFF) != HARDWARE_VERSION_VALUE) + { + applog(LOG_DEBUG,"data = 0x%x, and it's not equal to HARDWARE_VERSION_VALUE : 0x%x\n", data, HARDWARE_VERSION_VALUE); + //return -1; + } + applog(LOG_DEBUG,"axi_fpga_addr data = 0x%x\n", data); + + fd_fpga_mem = open("/dev/fpga_mem", O_RDWR); + if(fd_fpga_mem < 0) + { + applog(LOG_DEBUG,"/dev/fpga_mem open failed. fd_fpga_mem = %d\n", fd_fpga_mem); + perror("open"); + return -1; + } + + fpga_mem_addr = mmap(NULL, FPGA_MEM_TOTAL_LEN, PROT_READ|PROT_WRITE, MAP_SHARED, fd_fpga_mem, 0); + if(!fpga_mem_addr) + { + applog(LOG_DEBUG,"mmap fpga_mem_addr failed. fpga_mem_addr = 0x%x\n", fpga_mem_addr); + return -1; + } + applog(LOG_DEBUG,"mmap fpga_mem_addr = 0x%x\n", fpga_mem_addr); + + nonce2_jobid_address = fpga_mem_addr; + job_start_address_1 = fpga_mem_addr + NONCE2_AND_JOBID_STORE_SPACE/sizeof(int); + job_start_address_2 = fpga_mem_addr + (NONCE2_AND_JOBID_STORE_SPACE + JOB_STORE_SPACE)/sizeof(int); + + applog(LOG_DEBUG,"job_start_address_1 = 0x%x\n", job_start_address_1); + applog(LOG_DEBUG,"job_start_address_2 = 0x%x\n", job_start_address_2); + + set_nonce2_and_job_id_store_address(PHY_MEM_NONCE2_JOBID_ADDRESS); + set_job_start_address(PHY_MEM_JOB_START_ADDRESS_1); + + + dev = calloc(sizeof(struct all_parameters), sizeof(char)); + if(!dev) + { + applog(LOG_DEBUG,"kmalloc for dev failed.\n"); + return -1; + } + else + { + dev->current_job_start_address = job_start_address_1; + applog(LOG_DEBUG,"kmalloc for dev success.\n"); + } + return ret; +} + +int bitmain_axi_close() +{ + int ret = 0; + + ret = munmap((void *)axi_fpga_addr, TOTAL_LEN); + if(ret<0) + { + applog(LOG_DEBUG,"munmap failed!\n"); + } + + ret = munmap((void *)fpga_mem_addr, FPGA_MEM_TOTAL_LEN); + if(ret<0) + { + applog(LOG_DEBUG,"munmap failed!\n"); + } + + //free_pages((unsigned long)nonce2_jobid_address, NONCE2_AND_JOBID_STORE_SPACE_ORDER); + //free(temp_job_start_address_1); + //free(temp_job_start_address_2); + + close(fd); + close(fd_fpga_mem); +} + +int get_fan_control(void) +{ + int ret = -1; + ret = *((unsigned int *)(axi_fpga_addr + FAN_CONTROL)); + applog(LOG_DEBUG,"%s: FAN_CONTROL is 0x%x\n", __FUNCTION__, ret); + return ret; +} + +void set_fan_control(unsigned int value) +{ + *((unsigned int *)(axi_fpga_addr + FAN_CONTROL)) = value; + applog(LOG_DEBUG,"%s: set FAN_CONTROL is 0x%x\n", __FUNCTION__, value); + get_fan_control(); +} + +int get_hash_on_plug(void) +{ + int ret = -1; + ret = *(axi_fpga_addr + HASH_ON_PLUG); + + applog(LOG_DEBUG,"%s: HASH_ON_PLUG is 0x%x\n", __FUNCTION__, ret); + return ret; +} + +int get_hardware_version(void) +{ + int ret = -1; + ret = *((int *)(axi_fpga_addr + HARDWARE_VERSION)); + + applog(LOG_DEBUG,"%s: HARDWARE_VERSION is 0x%x\n", __FUNCTION__, ret); + return ret; +} + +int get_fan_speed(unsigned char *fan_id, unsigned int *fan_speed) +{ + int ret = -1; + ret = *((unsigned int *)(axi_fpga_addr + FAN_SPEED)); + *fan_speed = 0x000000ff & ret; + *fan_id = (unsigned char)(0x00000007 & (ret >> 8)); + if(*fan_speed > 0) + { + applog(LOG_DEBUG,"%s: fan_id is 0x%x, fan_speed is 0x%x\n", __FUNCTION__, *fan_id, *fan_speed); + } + return ret; +} + +int get_temperature_0_3(void) +{ + int ret = -1; + ret = *((int *)(axi_fpga_addr + TEMPERATURE_0_3)); + //applog(LOG_DEBUG,"%s: TEMPERATURE_0_3 is 0x%x\n", __FUNCTION__, ret); + return ret; +} + +int get_temperature_4_7(void) +{ + int ret = -1; + ret = *((int *)(axi_fpga_addr + TEMPERATURE_4_7)); + //applog(LOG_DEBUG,"%s: TEMPERATURE_4_7 is 0x%x\n", __FUNCTION__, ret); + return ret; +} + +int get_temperature_8_11(void) +{ + int ret = -1; + ret = *((int *)(axi_fpga_addr + TEMPERATURE_8_11)); + //applog(LOG_DEBUG,"%s: TEMPERATURE_8_11 is 0x%x\n", __FUNCTION__, ret); + return ret; +} + +int get_temperature_12_15(void) +{ + int ret = -1; + ret = *((int *)(axi_fpga_addr + TEMPERATURE_12_15)); + //applog(LOG_DEBUG,"%s: TEMPERATURE_12_15 is 0x%x\n", __FUNCTION__, ret); + return ret; +} + +int get_time_out_control(void) +{ + int ret = -1; + ret = *((unsigned int *)(axi_fpga_addr + TIME_OUT_CONTROL)); + applog(LOG_DEBUG,"%s: TIME_OUT_CONTROL is 0x%x\n", __FUNCTION__, ret); + return ret; +} + +void set_time_out_control(unsigned int value) +{ + *((unsigned int *)(axi_fpga_addr + TIME_OUT_CONTROL)) = value; + applog(LOG_DEBUG,"%s: set FAN_CONTROL is 0x%x\n", __FUNCTION__, value); + get_time_out_control(); +} + +int get_BC_command_buffer(unsigned int *buf) +{ + int ret = -1; + ret = *((unsigned int *)(axi_fpga_addr + BC_COMMAND_BUFFER)); + *(buf + 0) = ret; //this is for FIL + ret = *((unsigned int *)(axi_fpga_addr + BC_COMMAND_BUFFER + 1)); + *(buf + 1) = ret; + ret = *((unsigned int *)(axi_fpga_addr + BC_COMMAND_BUFFER + 2)); + *(buf + 2) = ret; + applog(LOG_DEBUG,"%s: BC_COMMAND_BUFFER buf[0]: 0x%x, buf[1]: 0x%x, buf[2]: 0x%x\n", __FUNCTION__, *(buf + 0), *(buf + 1), *(buf + 2)); + return ret; +} + +void set_BC_command_buffer(unsigned int *value) +{ + unsigned int buf[4] = {0}; + *((unsigned int *)(axi_fpga_addr + BC_COMMAND_BUFFER)) = *(value + 0); //this is for FIL + *((unsigned int *)(axi_fpga_addr + BC_COMMAND_BUFFER + 1)) = *(value + 1); + *((unsigned int *)(axi_fpga_addr + BC_COMMAND_BUFFER + 2)) = *(value + 2); + applog(LOG_DEBUG,"%s: set BC_COMMAND_BUFFER value[0]: 0x%x, value[1]: 0x%x, value[2]: 0x%x\n", __FUNCTION__, *(value + 0), *(value + 1), *(value + 2)); + get_BC_command_buffer(buf); +} + +int get_nonce_number_in_fifo(void) +{ + int ret = -1; + ret = *((unsigned int *)(axi_fpga_addr + NONCE_NUMBER_IN_FIFO)); + //applog(LOG_DEBUG,"%s: NONCE_NUMBER_IN_FIFO is 0x%x\n", __FUNCTION__, ret); + return ret; +} + +int get_return_nonce(unsigned int *buf) +{ + int ret = -1; + ret = *((unsigned int *)(axi_fpga_addr + RETURN_NONCE)); + *(buf + 0) = ret; + ret = *((unsigned int *)(axi_fpga_addr + RETURN_NONCE + 1)); + *(buf + 1) = ret; //there is nonce3 + //applog(LOG_DEBUG,"%s: RETURN_NONCE buf[0] is 0x%x, buf[1] is 0x%x\n", __FUNCTION__, *(buf + 0), *(buf + 1)); + return ret; +} + +int get_BC_write_command(void) +{ + int ret = -1; + ret = *((unsigned int *)(axi_fpga_addr + BC_WRITE_COMMAND)); + applog(LOG_DEBUG,"%s: BC_WRITE_COMMAND is 0x%x\n", __FUNCTION__, ret); + return ret; +} + +void set_BC_write_command(unsigned int value) +{ + *((unsigned int *)(axi_fpga_addr + BC_WRITE_COMMAND)) = value; + //applog(LOG_DEBUG,"%s: set BC_WRITE_COMMAND is 0x%x\n", __FUNCTION__, value); + + if(value & BC_COMMAND_BUFFER_READY) + { + while(get_BC_write_command() & BC_COMMAND_BUFFER_READY) + { + cgsleep_ms(1); + //applog(LOG_DEBUG,"%s ---\n", __FUNCTION__); + } + } + else + { + get_BC_write_command(); + } +} + +int get_ticket_mask(void) +{ + int ret = -1; + ret = *((unsigned int *)(axi_fpga_addr + TICKET_MASK_FPGA)); + applog(LOG_DEBUG,"%s: TICKET_MASK_FPGA is 0x%x\n", __FUNCTION__, ret); + return ret; +} + +void set_ticket_mask(unsigned int value) +{ + *((unsigned int *)(axi_fpga_addr + TICKET_MASK_FPGA)) = value; + applog(LOG_DEBUG,"%s: set TICKET_MASK_FPGA is 0x%x\n", __FUNCTION__, value); + get_ticket_mask(); +} + +int get_job_id(void) +{ + int ret = -1; + ret = *((unsigned int *)(axi_fpga_addr + JOB_ID)); + applog(LOG_DEBUG,"%s: JOB_ID is 0x%x\n", __FUNCTION__, ret); + return ret; +} + +void set_job_id(unsigned int value) +{ + *((unsigned int *)(axi_fpga_addr + JOB_ID)) = value; + applog(LOG_DEBUG,"%s: set JOB_ID is 0x%x\n", __FUNCTION__, value); + get_job_id(); +} + +int get_job_length(void) +{ + int ret = -1; + ret = *((unsigned int *)(axi_fpga_addr + JOB_LENGTH)); + applog(LOG_DEBUG,"%s: JOB_LENGTH is 0x%x\n", __FUNCTION__, ret); + return ret; +} + +void set_job_length(unsigned int value) +{ + *((unsigned int *)(axi_fpga_addr + JOB_LENGTH)) = value; + applog(LOG_DEBUG,"%s: set JOB_LENGTH is 0x%x\n", __FUNCTION__, value); + get_job_id(); +} + + +int get_block_header_version(void) +{ + int ret = -1; + ret = *((unsigned int *)(axi_fpga_addr + BLOCK_HEADER_VERSION)); + applog(LOG_DEBUG,"%s: BLOCK_HEADER_VERSION is 0x%x\n", __FUNCTION__, ret); + return ret; +} + +void set_block_header_version(unsigned int value) +{ + *((unsigned int *)(axi_fpga_addr + BLOCK_HEADER_VERSION)) = value; + applog(LOG_DEBUG,"%s: set BLOCK_HEADER_VERSION is 0x%x\n", __FUNCTION__, value); + get_block_header_version(); +} + +int get_time_stamp() +{ + int ret = -1; + ret = *((unsigned int *)(axi_fpga_addr + TIME_STAMP)); + applog(LOG_DEBUG,"%s: TIME_STAMP is 0x%x\n", __FUNCTION__, ret); + return ret; +} + +void set_time_stamp(unsigned int value) +{ + *((unsigned int *)(axi_fpga_addr + TIME_STAMP)) = value; + applog(LOG_DEBUG,"%s: set TIME_STAMP is 0x%x\n", __FUNCTION__, value); + get_time_stamp(); +} + +int get_target_bits(void) +{ + int ret = -1; + ret = *((unsigned int *)(axi_fpga_addr + TARGET_BITS)); + applog(LOG_DEBUG,"%s: TARGET_BITS is 0x%x\n", __FUNCTION__, ret); + return ret; +} + +void set_target_bits(unsigned int value) +{ + *((unsigned int *)(axi_fpga_addr + TARGET_BITS)) = value; + applog(LOG_DEBUG,"%s: set TARGET_BITS is 0x%x\n", __FUNCTION__, value); + get_target_bits(); +} + +int get_pre_header_hash(unsigned int *buf) +{ + int ret = -1; + *(buf + 0) = *((unsigned int *)(axi_fpga_addr + PRE_HEADER_HASH)); + *(buf + 1) = *((unsigned int *)(axi_fpga_addr + PRE_HEADER_HASH + 1)); + *(buf + 2) = *((unsigned int *)(axi_fpga_addr + PRE_HEADER_HASH + 2)); + *(buf + 3) = *((unsigned int *)(axi_fpga_addr + PRE_HEADER_HASH + 3)); + *(buf + 4) = *((unsigned int *)(axi_fpga_addr + PRE_HEADER_HASH + 4)); + *(buf + 5) = *((unsigned int *)(axi_fpga_addr + PRE_HEADER_HASH + 5)); + *(buf + 6) = *((unsigned int *)(axi_fpga_addr + PRE_HEADER_HASH + 6)); + *(buf + 7) = *((unsigned int *)(axi_fpga_addr + PRE_HEADER_HASH + 7)); + applog(LOG_DEBUG,"%s: PRE_HEADER_HASH buf[0]: 0x%x, buf[1]: 0x%x, buf[2]: 0x%x, buf[3]: 0x%x, buf[4]: 0x%x, buf[5]: 0x%x, buf[6]: 0x%x, buf[7]: 0x%x\n", __FUNCTION__, *(buf + 0), *(buf + 1), *(buf + 2), *(buf + 3), *(buf + 4), *(buf + 5), *(buf + 6), *(buf + 7)); + ret = *(buf + 7); + return ret; +} + +void set_pre_header_hash(unsigned int *value) +{ + unsigned int buf[8] = {0}; + *(axi_fpga_addr + PRE_HEADER_HASH) = *(value + 0); + *(axi_fpga_addr + PRE_HEADER_HASH + 1) = *(value + 1); + *(axi_fpga_addr + PRE_HEADER_HASH + 2) = *(value + 2); + *(axi_fpga_addr + PRE_HEADER_HASH + 3) = *(value + 3); + *(axi_fpga_addr + PRE_HEADER_HASH + 4) = *(value + 4); + *(axi_fpga_addr + PRE_HEADER_HASH + 5) = *(value + 5); + *(axi_fpga_addr + PRE_HEADER_HASH + 6) = *(value + 6); + *(axi_fpga_addr + PRE_HEADER_HASH + 7) = *(value + 7); + applog(LOG_DEBUG,"%s: set PRE_HEADER_HASH value[0]: 0x%x, value[1]: 0x%x, value[2]: 0x%x, value[3]: 0x%x, value[4]: 0x%x, value[5]: 0x%x, value[6]: 0x%x, value[7]: 0x%x\n", __FUNCTION__, *(value + 0), *(value + 1), *(value + 2), *(value + 3), *(value + 4), *(value + 5), *(value + 6), *(value + 7)); + //get_pre_header_hash(buf); +} + +int get_coinbase_length_and_nonce2_length(void) +{ + int ret = -1; + ret = *((unsigned int *)(axi_fpga_addr + COINBASE_AND_NONCE2_LENGTH)); + applog(LOG_DEBUG,"%s: COINBASE_AND_NONCE2_LENGTH is 0x%x\n", __FUNCTION__, ret); + return ret; +} + +void set_coinbase_length_and_nonce2_length(unsigned int value) +{ + *((unsigned int *)(axi_fpga_addr + COINBASE_AND_NONCE2_LENGTH)) = value; + applog(LOG_DEBUG,"%s: set COINBASE_AND_NONCE2_LENGTH is 0x%x\n", __FUNCTION__, value); + get_coinbase_length_and_nonce2_length(); +} + +int get_work_nonce2(unsigned int *buf) +{ + int ret = -1; + *(buf + 0) = *((unsigned int *)(axi_fpga_addr + WORK_NONCE_2)); + *(buf + 1) = *((unsigned int *)(axi_fpga_addr + WORK_NONCE_2 + 1)); + applog(LOG_DEBUG,"%s: WORK_NONCE_2 buf[0]: 0x%x, buf[1]: 0x%x\n", __FUNCTION__, *(buf + 0), *(buf + 1)); + return ret; +} + +void set_work_nonce2(unsigned int *value) +{ + unsigned int buf[2] = {0}; + *((unsigned int *)(axi_fpga_addr + WORK_NONCE_2)) = *(value + 0); + *((unsigned int *)(axi_fpga_addr + WORK_NONCE_2 + 1)) = *(value + 1); + applog(LOG_DEBUG,"%s: set WORK_NONCE_2 value[0]: 0x%x, value[1]: 0x%x\n", __FUNCTION__, *(value + 0), *(value + 1)); + get_work_nonce2(buf); +} + +int get_merkle_bin_number(void) +{ + int ret = -1; + ret = *((unsigned int *)(axi_fpga_addr + MERKLE_BIN_NUMBER)); + ret = ret & 0x0000ffff; + applog(LOG_DEBUG,"%s: MERKLE_BIN_NUMBER is 0x%x\n", __FUNCTION__, ret); + return ret; +} + +void set_merkle_bin_number(unsigned int value) +{ + *((unsigned int *)(axi_fpga_addr + MERKLE_BIN_NUMBER)) = value & 0x0000ffff; + applog(LOG_DEBUG,"%s: set MERKLE_BIN_NUMBER is 0x%x\n", __FUNCTION__, value & 0x0000ffff); + get_merkle_bin_number(); +} + +int get_nonce_fifo_interrupt(void) +{ + int ret = -1; + ret = *((unsigned int *)(axi_fpga_addr + NONCE_FIFO_INTERRUPT)); + applog(LOG_DEBUG,"%s: NONCE_FIFO_INTERRUPT is 0x%x\n", __FUNCTION__, ret); + return ret; +} + +void set_nonce_fifo_interrupt(unsigned int value) +{ + *((unsigned int *)(axi_fpga_addr + NONCE_FIFO_INTERRUPT)) = value; + applog(LOG_DEBUG,"%s: set NONCE_FIFO_INTERRUPT is 0x%x\n", __FUNCTION__, value); + get_nonce_fifo_interrupt(); +} + +int get_dhash_acc_control(void) +{ + int ret = -1; + ret = *((unsigned int *)(axi_fpga_addr + DHASH_ACC_CONTROL)); + applog(LOG_DEBUG,"%s: DHASH_ACC_CONTROL is 0x%x\n", __FUNCTION__, ret); + return ret; +} + +void set_dhash_acc_control(unsigned int value) +{ + int a = 10; + *((unsigned int *)(axi_fpga_addr + DHASH_ACC_CONTROL)) = value; + applog(LOG_DEBUG,"%s: set DHASH_ACC_CONTROL is 0x%x\n", __FUNCTION__, value); + while (a>0) + { + if ((value | NEW_BLOCK) == (get_dhash_acc_control() |NEW_BLOCK)) + break; + *((unsigned int *)(axi_fpga_addr + DHASH_ACC_CONTROL)) = value; + a--; + cgsleep_ms(2); + } + if (a == 0) + applog(LOG_DEBUG,"%s set DHASH_ACC_CONTROL failed!",__FUNCTION__); +} + +void set_TW_write_command(unsigned int *value) +{ + unsigned int i; + for(i=0; ichain_num = 0; + + ret = get_hash_on_plug(); + + if(ret < 0) + { + applog(LOG_DEBUG,"%s: get_hash_on_plug functions error\n"); + } + else + { + for(i=0; i < BITMAIN_MAX_CHAIN_NUM; i++) + { + if((ret >> i) & 0x1) + { + dev->chain_exist[i] = 1; + dev->chain_num++; + } + else + { + dev->chain_exist[i] = 0; + } + } + } +} + +void check_fan() +{ + unsigned char i=0, j=0; + unsigned char fan_id = 0; + unsigned int fan_speed; + + for(j=0; j < 2; j++) //means check for twice to make sure find out all fan + { + for(i=0; i < BITMAIN_MAX_FAN_NUM; i++) + { + if(get_fan_speed(&fan_id, &fan_speed) != -1) + { + dev->fan_speed_value[fan_id] = fan_speed * 60 * 2; + if((fan_speed > 0) && (dev->fan_exist[fan_id] == 0)) + { + dev->fan_exist[fan_id] = 1; + dev->fan_num++; + dev->fan_exist_map |= (0x1 << fan_id); + } + else if((fan_speed == 0) && (dev->fan_exist[fan_id] == 1)) + { + dev->fan_exist[fan_id] = 0; + dev->fan_num--; + dev->fan_exist_map &= !(0x1 << fan_id); + } + if(dev->fan_speed_top1 < dev->fan_speed_value[fan_id]) + dev->fan_speed_top1 = dev->fan_speed_value[fan_id]; + } + } + } +} + +int get_all_temperature() +{ + int ret = 0; + int ret_value = 0; + unsigned int i = 0; + int temperature = 0, highest_temp = 0; + int temperature_gap[BITMAIN_MAX_CHAIN_NUM] = {0}; + int biggest_gap = 0; + + + dev->temp_top1_last = dev->temp_top1; + dev->temp_num = 0; + dev->temp_top1 = 0; + dev->temp_sensor_map = 0; + + ret = get_temperature_0_3(); + if(ret != -1) + { + for(i=0; i<4; i++) + { + temperature = (ret >> i*8) & 0xff; + if((dev->chain_exist[i] == 1) && temperature) + { + dev->temp_sensor_map |= (0x1 << i); + dev->temp_num++; + dev->temp[i] = temperature; + } + else if((dev->chain_exist[i] == 0) && temperature) + { + ret_value = -1; + applog(LOG_DEBUG,"%s: chain%d is not exist, but it has temperature:%d\n", __FUNCTION__, i, temperature); + } + else if((dev->chain_exist[i] == 1) && !temperature) + { + ret_value = -1; + applog(LOG_DEBUG,"%s: chain%d is exist, but its temperature is:%d\n", __FUNCTION__, i, temperature); + } + else + { + //applog(LOG_DEBUG,"%s: no chain%d no temperature\n", __FUNCTION__, i); + } + } + } + + ret = get_temperature_4_7(); + if(ret != -1) + { + for(i=0; i<4; i++) + { + temperature = (ret >> i*8) & 0xff; + if((dev->chain_exist[i+4] == 1) && temperature) + { + dev->temp_sensor_map |= (0x1 << (i+4)); + dev->temp_num++; + dev->temp[i+4] = temperature; + } + else if((dev->chain_exist[i+4] == 0) && temperature) + { + ret_value = -1; + applog(LOG_DEBUG,"%s: chain%d is not exist, but it has temperature:%d\n", __FUNCTION__, i+4, temperature); + } + else if((dev->chain_exist[i+4] == 1) && !temperature) + { + ret_value = -1; + applog(LOG_DEBUG,"%s: chain%d is exist, but its temperature is:%d\n", __FUNCTION__, i+4, temperature); + } + else + { + //applog(LOG_DEBUG,"%s: no chain%d no temperature\n", __FUNCTION__, i+4); + } + } + } + + ret = get_temperature_8_11(); + if(ret != -1) + { + for(i=0; i<4; i++) + { + temperature = (ret >> i*8) & 0xff; + if((dev->chain_exist[i+8] == 1) && temperature) + { + dev->temp_sensor_map |= (0x1 << (i+8)); + dev->temp_num++; + dev->temp[i+8] = temperature; + } + else if((dev->chain_exist[i+8] == 0) && temperature) + { + ret_value = -1; + applog(LOG_DEBUG,"%s: chain%d is not exist, but it has temperature:%d\n", __FUNCTION__, i+8, temperature); + } + else if((dev->chain_exist[i+8] == 1) && !temperature) + { + ret_value = -1; + applog(LOG_DEBUG,"%s: chain%d is exist, but its temperature is:%d\n", __FUNCTION__, i+8, temperature); + } + else + { + //applog(LOG_DEBUG,"%s: no chain%d no temperature\n", __FUNCTION__, i+8); + } + } + } + + ret = get_temperature_12_15(); + if(ret != -1) + { + for(i=0; i<4; i++) + { + temperature = (ret >> i*8) & 0xff; + if((dev->chain_exist[i+12] == 1) && temperature) + { + dev->temp_sensor_map |= (0x1 << (i+12)); + dev->temp_num++; + dev->temp[i+12] = temperature; + } + else if((dev->chain_exist[i+12] == 0) && temperature) + { + ret_value = -1; + applog(LOG_DEBUG,"%s: chain%d is not exist, but it has temperature:%d\n", __FUNCTION__, i+12, temperature); + } + else if((dev->chain_exist[i+12] == 1) && !temperature) + { + ret_value = -1; + applog(LOG_DEBUG,"%s: chain%d is exist, but its temperature is:%d\n", __FUNCTION__, i+12, temperature); + } + else + { + //applog(LOG_DEBUG,"%s: no chain%d no temperature\n", __FUNCTION__, i+12); + } + } + } + + for(i=0; itemp[i] > highest_temp) + { + highest_temp = dev->temp[i]; + dev->temp_top1 = dev->temp[i]; + } + } + + if((dev->temp_top1 - dev->temp_top1_last < 2) && (dev->temp_top1 - dev->temp_top1_last > -2)) + { + dev->temp_top1_last = dev->temp_top1; + } + return ret_value; +} + +void set_PWM(unsigned char pwm_percent) +{ + uint16_t pwm_high_value = 0, pwm_low_value = 0; + int temp_pwm_percent = 0; + + temp_pwm_percent = pwm_percent; + + if(temp_pwm_percent < MIN_PWM_PERCENT) + { + temp_pwm_percent = MIN_PWM_PERCENT; + } + + if(temp_pwm_percent > MAX_PWM_PERCENT) + { + temp_pwm_percent = MAX_PWM_PERCENT; + } + + pwm_high_value = temp_pwm_percent * PWM_SCALE / 100; + pwm_low_value = (100 - temp_pwm_percent) * PWM_SCALE / 100; + dev->pwm_value = (pwm_high_value << 16) | pwm_low_value; + dev->pwm_percent = temp_pwm_percent; + + set_fan_control(dev->pwm_value); +} + +void set_PWM_according_to_temperature() +{ + int pwm_percent = 0, temp_change = 0; + temp_highest = dev->temp_top1; + if(temp_highest >= MAX_FAN_TEMP) + { + applog(LOG_DEBUG,"%s: Temperature is higher than %d 'C\n", __FUNCTION__, temp_highest); + } + + if(dev->fan_eft) + { + if((dev->fan_pwm >= 0) && (dev->fan_pwm <= 100)) + { + set_PWM(dev->fan_pwm); + return; + } + } + + temp_change = temp_highest - last_temperature; + + if(temp_highest >= MAX_FAN_TEMP || temp_highest == 0) + { + set_PWM(MAX_PWM_PERCENT); + dev->fan_pwm = MAX_PWM_PERCENT; + applog(LOG_DEBUG,"%s: Set PWM percent : MAX_PWM_PERCENT\n", __FUNCTION__); + return; + } + + if(temp_highest <= MIN_FAN_TEMP) + { + set_PWM(MIN_PWM_PERCENT); + dev->fan_pwm = MIN_PWM_PERCENT; + applog(LOG_DEBUG,"%s: Set PWM percent : MIN_PWM_PERCENT\n", __FUNCTION__); + return; + } + + if(temp_change >= TEMP_INTERVAL || temp_change <= -TEMP_INTERVAL) + { + pwm_percent = MIN_PWM_PERCENT + (temp_highest -MIN_FAN_TEMP) * PWM_ADJUST_FACTOR; + if(pwm_percent < 0) + { + pwm_percent = 0; + } + dev->fan_pwm = pwm_percent; + applog(LOG_DEBUG,"%s: Set PWM percent : %d\n", __FUNCTION__, pwm_percent); + set_PWM(pwm_percent); + last_temperature = temp_highest; + } +} + +static void get_plldata(int type,int freq,uint32_t * reg_data,uint16_t * reg_data2, uint32_t *vil_data) +{ + uint32_t i; + char freq_str[10]; + sprintf(freq_str,"%d", freq); + char plldivider1[32] = {0}; + char plldivider2[32] = {0}; + char vildivider[32] = {0}; + + if(type == 1385) + { + for(i=0; i < sizeof(freq_pll_1385)/sizeof(freq_pll_1385[0]); i++) + { + if( memcmp(freq_pll_1385[i].freq, freq_str, sizeof(freq_pll_1385[i].freq)) == 0) + break; + } + } + + if(i == sizeof(freq_pll_1385)/sizeof(freq_pll_1385[0])) + { + i = 4; + } + + sprintf(plldivider1, "%08x", freq_pll_1385[i].fildiv1); + sprintf(plldivider2, "%04x", freq_pll_1385[i].fildiv2); + sprintf(vildivider, "%04x", freq_pll_1385[i].vilpll); + + *reg_data = freq_pll_1385[i].fildiv1; + *reg_data2 = freq_pll_1385[i].fildiv2; + *vil_data = freq_pll_1385[i].vilpll; +} + +void set_frequency(unsigned short int frequency) +{ + unsigned char buf[9] = {0,0,0,0,0,0,0,0,0}; + unsigned int cmd_buf[3] = {0,0,0}; + unsigned char i; + unsigned int ret, value; + uint32_t reg_data_pll = 0; + uint16_t reg_data_pll2 = 0; + uint32_t reg_data_vil = 0; + + applog(LOG_DEBUG,"\n--- %s\n", __FUNCTION__); + + get_plldata(1385, frequency, ®_data_pll, ®_data_pll2, ®_data_vil); + applog(LOG_DEBUG,"%s: frequency = %d\n", __FUNCTION__, frequency); + + for(i = 0; i < BITMAIN_MAX_CHAIN_NUM; i++) + { + if(dev->chain_exist[i] == 1) + { + //applog(LOG_DEBUG,"%s: i = %d\n", __FUNCTION__, i); + if(!opt_multi_version) // fil mode + { + memset(buf,0,sizeof(buf)); + memset(cmd_buf,0,sizeof(cmd_buf)); + buf[0] = 0; + buf[0] |= SET_PLL_DIVIDER1; + buf[1] = (reg_data_pll >> 16) & 0xff; + buf[2] = (reg_data_pll >> 8) & 0xff; + buf[3] = (reg_data_pll >> 0) & 0xff; + buf[3] |= CRC5(buf, 4*8 - 5); + cmd_buf[0] = buf[0]<<24 | buf[1]<<16 | buf[2]<<8 | buf[3]; + + set_BC_command_buffer(cmd_buf); + ret = get_BC_write_command(); + value = BC_COMMAND_BUFFER_READY | BC_COMMAND_EN_CHAIN_ID| (i << 16) | (ret & 0x1f); + set_BC_write_command(value); + + cgsleep_us(3000); + + memset(buf,0,sizeof(buf)); + memset(cmd_buf,0,sizeof(cmd_buf)); + buf[0] = SET_PLL_DIVIDER2; + buf[0] |= COMMAND_FOR_ALL; + buf[1] = 0; //addr + buf[2] = reg_data_pll2 >> 8; + buf[3] = reg_data_pll2& 0x0ff; + buf[3] |= CRC5(buf, 4*8 - 5); + cmd_buf[0] = buf[0]<<24 | buf[1]<<16 | buf[2]<<8 | buf[3]; + + set_BC_command_buffer(cmd_buf); + ret = get_BC_write_command(); + value = BC_COMMAND_BUFFER_READY | BC_COMMAND_EN_CHAIN_ID| (i << 16) | (ret & 0x1f); + set_BC_write_command(value); + + dev->freq[i] = frequency; + + cgsleep_us(5000); + } + else // vil + { + memset(buf,0,9); + memset(cmd_buf,0,3*sizeof(int)); + buf[0] = VIL_COMMAND_TYPE | VIL_ALL | SET_CONFIG; + buf[1] = 0x09; + buf[2] = 0; + buf[3] = PLL_PARAMETER; + buf[4] = (reg_data_vil >> 24) & 0xff; + buf[5] = (reg_data_vil >> 16) & 0xff; + buf[6] = (reg_data_vil >> 8) & 0xff; + buf[7] = (reg_data_vil >> 0) & 0xff; + buf[8] = CRC5(buf, 8*8); + + cmd_buf[0] = buf[0]<<24 | buf[1]<<16 | buf[2]<<8 | buf[3]; + cmd_buf[1] = buf[4]<<24 | buf[5]<<16 | buf[6]<<8 | buf[7];; + cmd_buf[2] = buf[8]<<24; + + set_BC_command_buffer(cmd_buf); + ret = get_BC_write_command(); + value = BC_COMMAND_BUFFER_READY | BC_COMMAND_EN_CHAIN_ID| (i << 16) | (ret & 0x1f); + set_BC_write_command(value); + + dev->freq[i] = frequency; + cgsleep_us(10000); + } + } + } +} + +void set_frequency_with_addr(unsigned short int frequency,unsigned char mode,unsigned char addr, unsigned char chain) +{ + unsigned char buf[9] = {0,0,0,0,0,0,0,0,0}; + unsigned int cmd_buf[3] = {0,0,0}; + unsigned char i; + unsigned int ret, value; + uint32_t reg_data_pll = 0; + uint16_t reg_data_pll2 = 0; + uint32_t reg_data_vil = 0; + i = chain; + + applog(LOG_DEBUG,"\n--- %s\n", __FUNCTION__); + + get_plldata(1385, frequency, ®_data_pll, ®_data_pll2, ®_data_vil); + applog(LOG_DEBUG,"%s: frequency = %d\n", __FUNCTION__, frequency); + + //applog(LOG_DEBUG,"%s: i = %d\n", __FUNCTION__, i); + if(!opt_multi_version) // fil mode + { + memset(buf,0,sizeof(buf)); + memset(cmd_buf,0,sizeof(cmd_buf)); + buf[0] = 0; + buf[0] |= SET_PLL_DIVIDER1; + buf[1] = (reg_data_pll >> 16) & 0xff; + buf[2] = (reg_data_pll >> 8) & 0xff; + buf[3] = (reg_data_pll >> 0) & 0xff; + buf[3] |= CRC5(buf, 4*8 - 5); + cmd_buf[0] = buf[0]<<24 | buf[1]<<16 | buf[2]<<8 | buf[3]; + + set_BC_command_buffer(cmd_buf); + ret = get_BC_write_command(); + value = BC_COMMAND_BUFFER_READY | BC_COMMAND_EN_CHAIN_ID| (i << 16) | (ret & 0x1f); + set_BC_write_command(value); + + cgsleep_us(3000); + + memset(buf,0,sizeof(buf)); + memset(cmd_buf,0,sizeof(cmd_buf)); + buf[0] = SET_PLL_DIVIDER2; + buf[0] |= COMMAND_FOR_ALL; + buf[1] = 0; //addr + buf[2] = reg_data_pll2 >> 8; + buf[3] = reg_data_pll2& 0x0ff; + buf[3] |= CRC5(buf, 4*8 - 5); + cmd_buf[0] = buf[0]<<24 | buf[1]<<16 | buf[2]<<8 | buf[3]; + + set_BC_command_buffer(cmd_buf); + ret = get_BC_write_command(); + value = BC_COMMAND_BUFFER_READY | BC_COMMAND_EN_CHAIN_ID| (i << 16) | (ret & 0x1f); + set_BC_write_command(value); + + dev->freq[i] = frequency; + + cgsleep_us(5000); + } + else // vil + { + memset(buf,0,9); + memset(cmd_buf,0,3*sizeof(int)); + if(mode) + buf[0] = VIL_COMMAND_TYPE | VIL_ALL | SET_CONFIG; + else + buf[0] = VIL_COMMAND_TYPE | SET_CONFIG; + buf[1] = 0x09; + buf[2] = addr; + buf[3] = PLL_PARAMETER; + buf[4] = (reg_data_vil >> 24) & 0xff; + buf[5] = (reg_data_vil >> 16) & 0xff; + buf[6] = (reg_data_vil >> 8) & 0xff; + buf[7] = (reg_data_vil >> 0) & 0xff; + buf[8] = CRC5(buf, 8*8); + + cmd_buf[0] = buf[0]<<24 | buf[1]<<16 | buf[2]<<8 | buf[3]; + cmd_buf[1] = buf[4]<<24 | buf[5]<<16 | buf[6]<<8 | buf[7];; + cmd_buf[2] = buf[8]<<24; + + set_BC_command_buffer(cmd_buf); + ret = get_BC_write_command(); + value = BC_COMMAND_BUFFER_READY | BC_COMMAND_EN_CHAIN_ID| (i << 16) | (ret & 0x1f); + set_BC_write_command(value); + + dev->freq[i] = frequency; + cgsleep_us(10000); + } +} + + +void clear_nonce_fifo() +{ + unsigned char i; + unsigned int buf[2] = {0}; + + for(i=0; i<3; i++) //loop 3 times for making sure read out all nonce/register data + { + while(get_nonce_number_in_fifo() & MAX_NONCE_NUMBER_IN_FIFO) + { + get_return_nonce(buf); + } + } +} + +void clear_register_value_buf() +{ + pthread_mutex_lock(®_mutex); + reg_value_buf.p_wr = 0; + reg_value_buf.p_rd = 0; + reg_value_buf.reg_value_num = 0; + //memset(reg_value_buf.reg_buffer, 0, sizeof(struct reg_content)*MAX_NONCE_NUMBER_IN_FIFO); + pthread_mutex_unlock(®_mutex); +} + +void read_asic_register(unsigned char chain, unsigned char mode, unsigned char chip_addr, unsigned char reg_addr) +{ + unsigned char buf[5] = {0,0,0,0,0}; + unsigned char buf_vil[12] = {0,0,0,0,0,0,0,0,0,0,0,0}; + unsigned int cmd_buf[3] = {0,0,0}; + unsigned int ret, value; + + if(!opt_multi_version) // fil mode + { + buf[0] = GET_STATUS; + buf[1] = chip_addr; + buf[2] = reg_addr; + if (mode) //all + buf[0] |= COMMAND_FOR_ALL; + buf[3] = CRC5(buf, 4*8 - 5); + applog(LOG_DEBUG,"%s: buf[0]=0x%x, buf[1]=0x%x, buf[2]=0x%x, buf[3]=0x%x\n", __FUNCTION__, buf[0], buf[1], buf[2], buf[3]); + + cmd_buf[0] = buf[0]<<24 | buf[1]<<16 | buf[2]<<8 | buf[3]; + set_BC_command_buffer(cmd_buf); + + ret = get_BC_write_command(); + value = BC_COMMAND_BUFFER_READY | BC_COMMAND_EN_CHAIN_ID | (chain << 16) | (ret & 0x1f); + set_BC_write_command(value); + } + else // vil mode + { + buf[0] = VIL_COMMAND_TYPE | GET_STATUS; + if(mode) + buf[0] |= VIL_ALL; + buf[1] = 0x05; + buf[2] = chip_addr; + buf[3] = reg_addr; + buf[4] = CRC5(buf, 4*8); + applog(LOG_DEBUG,"%s:VIL buf[0]=0x%x, buf[1]=0x%x, buf[2]=0x%x, buf[3]=0x%x, buf[4]=0x%x", __FUNCTION__, buf[0], buf[1], buf[2], buf[3], buf[4]); + + cmd_buf[0] = buf[0]<<24 | buf[1]<<16 | buf[2]<<8 | buf[3]; + cmd_buf[1] = buf[4]<<24; + + while (1) + { + if (((ret = get_BC_write_command()) & 0x80000000) == 0) + break; + cgsleep_ms(1); + } + set_BC_command_buffer(cmd_buf); + + ret = get_BC_write_command(); + value = BC_COMMAND_BUFFER_READY | BC_COMMAND_EN_CHAIN_ID | (chain << 16) | (ret & 0x1f); + set_BC_write_command(value); + } +} + +void read_temp(unsigned char device,unsigned reg,unsigned char data,unsigned char write,unsigned char chip_addr,int chain) +{ + unsigned char buf[9] = {0,0,0,0,0,0,0,0,0}; + unsigned int cmd_buf[3] = {0,0,0}; + unsigned int ret, value,i; + i = chain; + if(!opt_multi_version) + { + printf("fil mode do not support temp reading"); + } + else + { + buf[0] = VIL_COMMAND_TYPE | SET_CONFIG; + buf[1] = 0x09; + buf[2] = chip_addr; + buf[3] = GENERAL_I2C_COMMAND; + buf[4] = 0x01; + buf[5] = device | write; + buf[6] = reg; + buf[7] = data; + buf[8] = CRC5(buf, 8*8); + cmd_buf[0] = buf[0]<<24 | buf[1]<<16 | buf[2]<<8 | buf[3]; + cmd_buf[1] = buf[4]<<24 | buf[5]<<16 | buf[6]<<8 | buf[7]; + cmd_buf[2] = buf[8]<<24; + while (1) + { + ret = get_BC_write_command(); + if ((ret & 0x80000000) == 0) + break; + cgsleep_ms(1); + } + set_BC_command_buffer(cmd_buf); + value = BC_COMMAND_BUFFER_READY | BC_COMMAND_EN_CHAIN_ID| (i << 16) | (ret & 0x1f); + set_BC_write_command(value); + } + +} + +static void suffix_string_c5(uint64_t val, char *buf, size_t bufsiz, int sigdigits,bool display) +{ + const double dkilo = 1000.0; + const uint64_t kilo = 1000ull; + const uint64_t mega = 1000000ull; + const uint64_t giga = 1000000000ull; + const uint64_t tera = 1000000000000ull; + const uint64_t peta = 1000000000000000ull; + const uint64_t exa = 1000000000000000000ull; + char suffix[2] = ""; + bool decimal = true; + double dval; + /* + if (val >= exa) + { + val /= peta; + dval = (double)val / dkilo; + strcpy(suffix, "E"); + } + else if (val >= peta) + { + val /= tera; + dval = (double)val / dkilo; + strcpy(suffix, "P"); + } + else if (val >= tera) + { + val /= giga; + dval = (double)val / dkilo; + strcpy(suffix, "T"); + } + else */if (val >= giga) + { + val /= mega; + dval = (double)val / dkilo; + strcpy(suffix, "G"); + } + else if (val >= mega) + { + val /= kilo; + dval = (double)val / dkilo; + strcpy(suffix, "M"); + } + else if (val >= kilo) + { + dval = (double)val / dkilo; + strcpy(suffix, "K"); + } + else + { + dval = val; + decimal = false; + } + + if (!sigdigits) + { + if (decimal) + snprintf(buf, bufsiz, "%.3g%s", dval, suffix); + else + snprintf(buf, bufsiz, "%d%s", (unsigned int)dval, suffix); + } + else + { + /* Always show sigdigits + 1, padded on right with zeroes + * followed by suffix */ + int ndigits = sigdigits - 1 - (dval > 0.0 ? floor(log10(dval)) : 0); + if(display) + snprintf(buf, bufsiz, "%*.*f%s", sigdigits + 1, ndigits, dval, suffix); + else + snprintf(buf, bufsiz, "%*.*f", sigdigits + 1, ndigits, dval); + + } +} + + +void check_asic_reg(unsigned int reg) +{ + + unsigned char i, j, not_reg_data_time=0; + int nonce_number = 0; + unsigned int buf[2] = {0}; + unsigned int reg_value_num=0; + unsigned int temp_nonce = 0; + unsigned char reg_buf[5] = {0,0,0,0,0}; + int read_num = 0; + uint64_t tmp_rate = 0; +rerun_all: + clear_register_value_buf(); + tmp_rate = 0; + for(i=0; i < BITMAIN_MAX_CHAIN_NUM; i++) + { + read_num = 0; + if(dev->chain_exist[i] == 1) + { + tmp_rate = 0; + applog(LOG_DEBUG,"%s: check chain J%d \n", __FUNCTION__, i+1); + read_asic_register(i, 1, 0, reg); + if (reg ==CHIP_ADDRESS) + dev->chain_asic_num[i] = 0; + + while(not_reg_data_time < 3) //if there is no register value for 3 times, we can think all asic return their address + { + cgsleep_ms(300); + + pthread_mutex_lock(®_mutex); + + reg_value_num = reg_value_buf.reg_value_num; + //applog(LOG_DEBUG,"%s: reg_value_num = %d\n", __FUNCTION__, reg_value_num); + pthread_mutex_unlock(®_mutex); + if((reg_value_num >= MAX_NONCE_NUMBER_IN_FIFO || reg_value_buf.p_rd >= MAX_NONCE_NUMBER_IN_FIFO) && not_reg_data_time <3) + { + not_reg_data_time ++; + goto rerun_all; + } + if(not_reg_data_time == 3) + { + return; + } + + //applog(LOG_DEBUG,"%s: reg_value_buf.reg_value_num = 0x%x\n", __FUNCTION__, reg_value_num); + + if(reg_value_num > 0) + { + not_reg_data_time = 0; + + applog(LOG_DEBUG,"%s: reg_value_buf.reg_value_num = %d\n", __FUNCTION__, reg_value_num); + + for(j = 0; j < reg_value_num; j++) + { + pthread_mutex_lock(®_mutex); + + //applog(LOG_DEBUG,"%\n"); + if(reg_value_buf.reg_buffer[reg_value_buf.p_rd].chain_number != i) + { + applog(LOG_DEBUG,"%s: the return data is from chain%d, but it should be from chain%d\n", __FUNCTION__, reg_value_buf.reg_buffer[reg_value_buf.p_rd].chain_number, i); + pthread_mutex_unlock(®_mutex); + continue; + } + //applog(LOG_DEBUG,"@\n"); + + reg_buf[3] = (unsigned char)(reg_value_buf.reg_buffer[reg_value_buf.p_rd].reg_value & 0xff); + reg_buf[2] = (unsigned char)((reg_value_buf.reg_buffer[reg_value_buf.p_rd].reg_value >> 8) & 0xff); + reg_buf[1] = (unsigned char)((reg_value_buf.reg_buffer[reg_value_buf.p_rd].reg_value >> 16)& 0xff); + reg_buf[0] = (unsigned char)((reg_value_buf.reg_buffer[reg_value_buf.p_rd].reg_value >> 24)& 0xff); + + applog(LOG_DEBUG,"%s: reg_value = 0x%x\n", __FUNCTION__, reg_value_buf.reg_buffer[reg_value_buf.p_rd].reg_value); + + reg_value_buf.p_rd++; + reg_value_buf.reg_value_num--; + if(reg_value_buf.p_rd >= MAX_NONCE_NUMBER_IN_FIFO) + { + reg_value_buf.p_rd = 0; + } + //applog(LOG_DEBUG,"%s: reg_value_buf.reg_value_num = %d\n", __FUNCTION__, reg_value_buf.reg_value_num); + pthread_mutex_unlock(®_mutex); + + if(reg == CHIP_ADDRESS) + { + dev->chain_asic_num[i]++; + } + + if(reg == PLL_PARAMETER) + { + applog(LOG_DEBUG,"%s: the asic freq is 0x%x\n", __FUNCTION__, reg_value_buf.reg_buffer[reg_value_buf.p_rd].reg_value); + } + + if(reg == 0x08) + { + int i; + uint64_t temp_hash_rate = 0; + uint8_t rate_buf[10]; + uint8_t displayed_rate[16]; + for(i = 0; i < 4; i++) + { + sprintf(rate_buf + 2*i,"%02x",reg_buf[i]); + } + applog(LOG_DEBUG,"%s: hashrate is %s\n", __FUNCTION__, rate_buf); + temp_hash_rate = strtol(rate_buf,NULL,16); + temp_hash_rate = (temp_hash_rate << 24); + tmp_rate += temp_hash_rate; + read_num ++; + } + } + if(reg == CHIP_ADDRESS) + { + if (dev->chain_asic_num[i] == CHAIN_ASIC_NUM) + break; + } + } + else + { + cgsleep_ms(100); + not_reg_data_time++; + applog(LOG_DEBUG,"%s: no asic address register come back for %d time.\n", __FUNCTION__, not_reg_data_time); + } + } + + not_reg_data_time = 0; + + if(reg == CHIP_ADDRESS) + { + if(dev->chain_asic_num[i] > dev->max_asic_num_in_one_chain) + { + dev->max_asic_num_in_one_chain = dev->chain_asic_num[i]; + } + applog(LOG_DEBUG,"%s: chain J%d has %d ASIC\n", __FUNCTION__, i+1, dev->chain_asic_num[i]); + } + if(read_num == CHAIN_ASIC_NUM) + { + rate[i] = tmp_rate; + suffix_string_c5(rate[i], (char * )displayed_rate[i], sizeof(displayed_rate[i]), 6,false); + rate_error[i] = 0; + applog(LOG_DEBUG,"%s: chain %d hashrate is %s\n", __FUNCTION__, i, displayed_rate[i]); + } + if(read_num == 0 || status_error ) + { + rate_error[i]++; + if(rate_error[i] > 3 || status_error) + { + rate[i] = 0; + suffix_string_c5(rate[i], (char * )displayed_rate[i], sizeof(displayed_rate[i]), 6,false); + } + } + //set_nonce_fifo_interrupt(get_nonce_fifo_interrupt() & ~(FLUSH_NONCE3_FIFO)); + clear_register_value_buf(); + } + } +} + +#define RETRY_NUM 5 +unsigned int check_asic_reg_with_addr(unsigned int reg,unsigned int chip_addr,unsigned int chain, int check_num) +{ + unsigned char i, j, not_reg_data_time=0; + int nonce_number = 0; + unsigned int reg_value_num=0; + unsigned int reg_buf = 0; + i = chain; +rerun: + clear_register_value_buf(); + read_asic_register(i, 0, chip_addr, reg); + cgsleep_ms(80); + + while(not_reg_data_time < RETRY_NUM) //if there is no register value for 3 times, we can think all asic return their address + { + pthread_mutex_lock(®_mutex); + reg_value_num = reg_value_buf.reg_value_num; + //applog(LOG_NOTICE,"%s: p_wr = %d reg_value_num = %d\n", __FUNCTION__,reg_value_buf.p_wr,reg_value_buf.reg_value_num); + pthread_mutex_unlock(®_mutex); + applog(LOG_DEBUG,"%s: reg_value_num %d", __FUNCTION__, reg_value_num); + if((reg_value_num >= MAX_NONCE_NUMBER_IN_FIFO || reg_value_buf.p_rd >= MAX_NONCE_NUMBER_IN_FIFO ||reg_value_num ==0 ) && not_reg_data_time = RETRY_NUM) + { + return 0; + } + + pthread_mutex_lock(®_mutex); + for(i = 0; i < reg_value_num; i++) + { + reg_buf = reg_value_buf.reg_buffer[reg_value_buf.p_rd].reg_value; + applog(LOG_DEBUG,"%s: chip %x reg %x reg_buff %x", __FUNCTION__, chip_addr,reg,reg_buf); + reg_value_buf.p_rd++; + reg_value_buf.reg_value_num--; + if(reg_value_buf.p_rd < MAX_NONCE_NUMBER_IN_FIFO) + { + reg_value_buf.p_rd = 0; + } + if(reg == GENERAL_I2C_COMMAND) + { + if((reg_buf & 0xc0000000) == 0x0) + { + pthread_mutex_unlock(®_mutex); + clear_register_value_buf(); + return reg_buf; + } + else + { + pthread_mutex_unlock(®_mutex); + clear_register_value_buf(); + return 0; + } + } + } + pthread_mutex_unlock(®_mutex); + } + //set_nonce_fifo_interrupt(get_nonce_fifo_interrupt() & ~(FLUSH_NONCE3_FIFO)); + clear_register_value_buf(); + return 0; +} + + +unsigned int wait_iic_ok(unsigned int chip_addr,unsigned int chain,bool update) +{ + int fail_time = 0; + unsigned int ret = 0; + while(fail_time < 2) + { + ret = check_asic_reg_with_addr(GENERAL_I2C_COMMAND,chip_addr,chain,1); + if (ret != 0) + { + return ret; + } + else + { + fail_time++; + cgsleep_ms(1); + } + } + return 0; +} + +unsigned int check_reg_temp(unsigned char device,unsigned reg,unsigned char data,unsigned char write,unsigned char chip_addr,int chain) +{ + int fail_time =0; + unsigned int ret; + if(!write) + { + do + { + wait_iic_ok(chip_addr,chain,0); + read_temp(device, reg, data, write,chip_addr,chain); + cgsleep_ms(1); + ret = wait_iic_ok(chip_addr,chain,1); + cgsleep_ms(1); + fail_time++; + } + while (((ret & 0xff00) >>8 != reg || (ret & 0xff) == 0xff || (ret & 0xff) == 0x7f ) && fail_time < 2); + } + else + { + do + { + wait_iic_ok(chip_addr,chain,0); + read_temp(device, reg, data, write,chip_addr,chain); + wait_iic_ok(chip_addr,chain,1); + cgsleep_ms(1); + wait_iic_ok(chip_addr,chain,0); + read_temp(device, reg, 0, 0,chip_addr,chain); + ret = wait_iic_ok(chip_addr,chain,1); + cgsleep_ms(1); + fail_time++; + } + while (((ret & 0xff00) >>8 != reg && (ret & 0xff) != data )&& fail_time < 2); + } + + if (fail_time == 2) + return 0; + else + return ret; +} + + +int8_t calibration_sensor_offset(unsigned char device,unsigned char chip_addr,int chain,unsigned chip_num) +{ + int8_t offset,middle,local; + unsigned int ret = 0; + ret = check_reg_temp(device, 0x11, 0xba, 1, chip_addr, chain); + ret = check_reg_temp(device, 0x0, 0x0, 0, chip_addr, chain); + local = ret & 0xff; + ret = check_reg_temp(device, 0x1, 0x0, 0, chip_addr, chain); + middle = ret & 0xff; + offset = -70 + (local - middle); + ret = check_reg_temp(device, 0x11, offset, 1, chip_addr, chain); +} +void read_temp_func() +{ + int i; + unsigned int ret = 0; + int16_t temp_top = 0; + + while(1) + { + temp_top = 0; + for(i=0; i < BITMAIN_MAX_CHAIN_NUM; i++) + { + if(dev->chain_exist[i] == 1) + { + pthread_mutex_lock(®_read_mutex); + ret = check_reg_temp(0x98, 0x00, 0x0, 0x0, HAVE_TEMP, i); + if (ret != 0) + { + dev->chain_asic_temp[i][2][0] = (ret & 0xff); + if (dev->chain_asic_temp[i][2][0] > temp_top) + { + temp_top = dev->chain_asic_temp[i][2][0]; + } + } + + ret = check_reg_temp(0x98, 0x01, 0x0, 0x0, HAVE_TEMP, i); + if (ret != 0) + { + dev->chain_asic_temp[i][2][1] = (ret & 0xff); + } + } + } + dev->temp_top1 = temp_top; + sleep(1); + } +} + + +void chain_inactive(unsigned char chain) +{ + unsigned char buf[5] = {0,0,0,0,5}; + unsigned int cmd_buf[3] = {0,0,0}; + unsigned int ret, value; + + if(!opt_multi_version) // fil mode + { + buf[0] = CHAIN_INACTIVE | COMMAND_FOR_ALL; + buf[1] = 0; + buf[2] = 0; + buf[3] = CRC5(buf, 4*8 - 5); + applog(LOG_DEBUG,"%s: buf[0]=0x%x, buf[1]=0x%x, buf[2]=0x%x, buf[3]=0x%x\n", __FUNCTION__, buf[0], buf[1], buf[2], buf[3]); + + cmd_buf[0] = buf[0]<<24 | buf[1]<<16 | buf[2]<<8 | buf[3]; + set_BC_command_buffer(cmd_buf); + + ret = get_BC_write_command(); + value = BC_COMMAND_BUFFER_READY | BC_COMMAND_EN_CHAIN_ID | (chain << 16) | (ret & 0x1f); + set_BC_write_command(value); + } + else // vil mode + { + buf[0] = VIL_COMMAND_TYPE | VIL_ALL | CHAIN_INACTIVE; + buf[1] = 0x05; + buf[2] = 0; + buf[3] = 0; + buf[4] = CRC5(buf, 4*8); + applog(LOG_DEBUG,"%s: buf[0]=0x%x, buf[1]=0x%x, buf[2]=0x%x, buf[3]=0x%x, buf[4]=0x%x\n", __FUNCTION__, buf[0], buf[1], buf[2], buf[3], buf[4]); + + cmd_buf[0] = buf[0]<<24 | buf[1]<<16 | buf[2]<<8 | buf[3]; + cmd_buf[1] = buf[4]<<24; + while (1) + { + ret = get_BC_write_command(); + if ((ret & 0x80000000) == 0) + break; + cgsleep_ms(1); + } + set_BC_command_buffer(cmd_buf); + value = BC_COMMAND_BUFFER_READY | BC_COMMAND_EN_CHAIN_ID | (chain << 16) | (ret & 0x1f); + set_BC_write_command(value); + } +} + +void set_address(unsigned char chain, unsigned char mode, unsigned char address) +{ + unsigned char buf[4] = {0,0,0,0}; + unsigned int cmd_buf[3] = {0,0,0}; + unsigned int ret, value; + + if(!opt_multi_version) // fil mode + { + buf[0] = SET_ADDRESS; + buf[1] = address; + buf[2] = 0; + if (mode) //all + buf[0] |= COMMAND_FOR_ALL; + buf[3] = CRC5(buf, 4*8 - 5); + applog(LOG_DEBUG,"%s: buf[0]=0x%x, buf[1]=0x%x, buf[2]=0x%x, buf[3]=0x%x\n", __FUNCTION__, buf[0], buf[1], buf[2], buf[3]); + + cmd_buf[0] = buf[0]<<24 | buf[1]<<16 | buf[2]<<8 | buf[3]; + set_BC_command_buffer(cmd_buf); + + ret = get_BC_write_command(); + value = BC_COMMAND_BUFFER_READY | BC_COMMAND_EN_CHAIN_ID | (chain << 16) | (ret & 0x1f); + set_BC_write_command(value); + } + else // vil mode + { + buf[0] = VIL_COMMAND_TYPE | SET_ADDRESS; + buf[1] = 0x05; + buf[2] = address; + buf[3] = 0; + buf[4] = CRC5(buf, 4*8); + //applog(LOG_DEBUG,"%s: buf[0]=0x%x, buf[1]=0x%x, buf[2]=0x%x, buf[3]=0x%x, buf[4]=0x%x\n", __FUNCTION__, buf[0], buf[1], buf[2], buf[3], buf[4]); + + cmd_buf[0] = buf[0]<<24 | buf[1]<<16 | buf[2]<<8 | buf[3]; + cmd_buf[1] = buf[4]<<24; + while (1) + { + ret = get_BC_write_command(); + if ((ret & 0x80000000) == 0) + break; + cgsleep_ms(1); + } + set_BC_command_buffer(cmd_buf); + value = BC_COMMAND_BUFFER_READY | BC_COMMAND_EN_CHAIN_ID | (chain << 16) | (ret & 0x1f); + set_BC_write_command(value); + } +} + +int calculate_asic_number(unsigned int actual_asic_number) +{ + int i = 0; + if(actual_asic_number == 1) + { + i = 1; + } + else if(actual_asic_number == 2) + { + i = 2; + } + else if((actual_asic_number > 2) && (actual_asic_number <= 4)) + { + i = 4; + } + else if((actual_asic_number > 4) && (actual_asic_number <= 8)) + { + i = 8; + } + else if((actual_asic_number > 8) && (actual_asic_number <= 16)) + { + i = 16; + } + else if((actual_asic_number > 16) && (actual_asic_number <= 32)) + { + i = 32; + } + else if((actual_asic_number > 32) && (actual_asic_number <= 64)) + { + i = 64; + } + else if((actual_asic_number > 64) && (actual_asic_number <= 128)) + { + i = 128; + } + else + { + applog(LOG_DEBUG,"actual_asic_number = %d, but it is error\n", actual_asic_number); + return -1; + } + return i; +} + +int calculate_core_number(unsigned int actual_core_number) +{ + int i = 0; + if(actual_core_number == 1) + { + i = 1; + } + else if(actual_core_number == 2) + { + i = 2; + } + else if((actual_core_number > 2) && (actual_core_number <= 4)) + { + i = 4; + } + else if((actual_core_number > 4) && (actual_core_number <= 8)) + { + i = 8; + } + else if((actual_core_number > 8) && (actual_core_number <= 16)) + { + i = 16; + } + else if((actual_core_number > 16) && (actual_core_number <= 32)) + { + i = 32; + } + else if((actual_core_number > 32) && (actual_core_number <= 64)) + { + i = 64; + } + else if((actual_core_number > 64) && (actual_core_number <= 128)) + { + i = 128; + } + else + { + applog(LOG_DEBUG,"actual_core_number = %d, but it is error\n", actual_core_number); + return -1; + } + return i; +} + +void software_set_address() +{ + int temp_asic_number = 0; + unsigned int i, j; + unsigned char chip_addr = 0; + unsigned char check_bit = 0; + + applog(LOG_DEBUG,"--- %s\n", __FUNCTION__); + + temp_asic_number = calculate_asic_number(dev->max_asic_num_in_one_chain); + if(temp_asic_number <= 0) + { + dev->addrInterval = 0x1; + return; + } + + dev->addrInterval = 0x100 / temp_asic_number; + check_bit = dev->addrInterval - 1; + while(check_bit) + { + check_bit = check_bit >> 1; + dev->check_bit++; + } + + for(i=0; ichain_exist[i] == 1 && dev->chain_asic_num[i] == CHAIN_ASIC_NUM) + { + applog(LOG_DEBUG,"%s: chain %d has %d ASIC, and addrInterval is %d\n", __FUNCTION__, i, dev->chain_asic_num[i], dev->addrInterval); + + chip_addr = 0; + chain_inactive(i); + cgsleep_ms(30); + chain_inactive(i); + cgsleep_ms(30); + chain_inactive(i); + cgsleep_ms(30); + + for(j = 0; j < 0x100/dev->addrInterval; j++) + { + set_address(i, 0, chip_addr); + chip_addr += dev->addrInterval; + cgsleep_ms(30); + } + } + } +} + +void set_asic_ticket_mask(unsigned int ticket_mask) +{ + unsigned char buf[4] = {0,0,0,0}; + unsigned int cmd_buf[3] = {0,0,0}; + unsigned int ret, value,i; + unsigned int tm; + + tm = Swap32(ticket_mask); + + for(i=0; ichain_exist[i] == 1) + { + //first step: send new bauddiv to ASIC, but FPGA doesn't change its bauddiv, it uses old bauddiv to send BC command to ASIC + if(!opt_multi_version) // fil mode + { + buf[0] = SET_BAUD_OPS; + buf[1] = 0x10; + buf[2] = ticket_mask & 0x1f; + buf[0] |= COMMAND_FOR_ALL; + buf[3] = CRC5(buf, 4*8 - 5); + applog(LOG_DEBUG,"%s: buf[0]=0x%x, buf[1]=0x%x, buf[2]=0x%x, buf[3]=0x%x\n", __FUNCTION__, buf[0], buf[1], buf[2], buf[3]); + + cmd_buf[0] = buf[0]<<24 | buf[1]<<16 | buf[2]<<8 | buf[3]; + set_BC_command_buffer(cmd_buf); + + ret = get_BC_write_command(); + value = BC_COMMAND_BUFFER_READY | BC_COMMAND_EN_CHAIN_ID | (i << 16) | (ret & 0x1f); + set_BC_write_command(value); + } + else // vil mode + { + buf[0] = VIL_COMMAND_TYPE | VIL_ALL | SET_CONFIG; + buf[1] = 0x09; + buf[2] = 0; + buf[3] = TICKET_MASK; + buf[4] = tm & 0xff; + buf[5] = (tm >> 8) & 0xff; + buf[6] = (tm >> 16) & 0xff; + buf[7] = (tm >> 24) & 0xff; + buf[8] = CRC5(buf, 8*8); + + cmd_buf[0] = buf[0]<<24 | buf[1]<<16 | buf[2]<<8 | buf[3]; + cmd_buf[1] = buf[4]<<24 | buf[5]<<16 | buf[6]<<8 | buf[7]; + cmd_buf[2] = buf[8]<<24; + + set_BC_command_buffer(cmd_buf); + ret = get_BC_write_command(); + value = BC_COMMAND_BUFFER_READY | BC_COMMAND_EN_CHAIN_ID| (i << 16) | (ret & 0x1f); + set_BC_write_command(value); + } + } + } +} + + +#if 1 + +void set_baud(unsigned char bauddiv,int no_use) +{ + unsigned char buf[4] = {0,0,0,0}; + unsigned int cmd_buf[3] = {0,0,0}; + unsigned int ret, value,i; + + + if(dev->baud == bauddiv) + { + applog(LOG_DEBUG,"%s: the setting bauddiv(%d) is the same as before\n", __FUNCTION__, bauddiv); + return; + } + + for(i=0; ichain_exist[i] == 1) + { + //first step: send new bauddiv to ASIC, but FPGA doesn't change its bauddiv, it uses old bauddiv to send BC command to ASIC + if(!opt_multi_version) // fil mode + { + buf[0] = SET_BAUD_OPS; + buf[1] = 0x10; + buf[2] = bauddiv & 0x1f; + buf[0] |= COMMAND_FOR_ALL; + buf[3] = CRC5(buf, 4*8 - 5); + applog(LOG_DEBUG,"%s: buf[0]=0x%x, buf[1]=0x%x, buf[2]=0x%x, buf[3]=0x%x\n", __FUNCTION__, buf[0], buf[1], buf[2], buf[3]); + + cmd_buf[0] = buf[0]<<24 | buf[1]<<16 | buf[2]<<8 | buf[3]; + set_BC_command_buffer(cmd_buf); + + ret = get_BC_write_command(); + value = BC_COMMAND_BUFFER_READY | BC_COMMAND_EN_CHAIN_ID | (i << 16) | (ret & 0x1f); + set_BC_write_command(value); + } + else // vil mode + { + buf[0] = VIL_COMMAND_TYPE | VIL_ALL | SET_CONFIG; + buf[1] = 0x09; + buf[2] = 0; + buf[3] = MISC_CONTROL; + buf[4] = 0; + buf[5] = INV_CLKO; + buf[6] = bauddiv & 0x1f; + buf[7] = 0; + buf[8] = CRC5(buf, 8*8); + + cmd_buf[0] = buf[0]<<24 | buf[1]<<16 | buf[2]<<8 | buf[3]; + cmd_buf[1] = buf[4]<<24 | buf[5]<<16 | buf[6]<<8 | buf[7]; + cmd_buf[2] = buf[8]<<24; + applog(LOG_DEBUG,"%s: cmd_buf[0]=0x%x, cmd_buf[1]=0x%x, cmd_buf[2]=0x%x\n", __FUNCTION__, cmd_buf[0], cmd_buf[1], cmd_buf[2]); + + set_BC_command_buffer(cmd_buf); + ret = get_BC_write_command(); + value = BC_COMMAND_BUFFER_READY | BC_COMMAND_EN_CHAIN_ID| (i << 16) | (ret & 0x1f); + set_BC_write_command(value); + } + } + } + + // second step: change FPGA's bauddiv + cgsleep_us(50000); + ret = get_BC_write_command(); + value = (ret & 0xffffffe0) | (bauddiv & 0x1f); + set_BC_write_command(value); + dev->baud = bauddiv; +} +#endif + +void set_baud_with_addr(unsigned char bauddiv,unsigned int mode,unsigned int chip_addr,int chain,int iic,int open_core,int bottom_or_mid) +{ + unsigned char buf[9] = {0,0,0,0,0,0,0,0,0}; + unsigned int cmd_buf[3] = {0,0,0}; + unsigned int ret, value,i; + i = chain; + + //first step: send new bauddiv to ASIC, but FPGA doesn't change its bauddiv, it uses old bauddiv to send BC command to ASIC + if(!opt_multi_version) // fil mode + { + buf[0] = SET_BAUD_OPS; + buf[1] = 0x10; + buf[2] = bauddiv & 0x1f; + buf[0] |= COMMAND_FOR_ALL; + buf[3] = CRC5(buf, 4*8 - 5); + applog(LOG_DEBUG,"%s: buf[0]=0x%x, buf[1]=0x%x, buf[2]=0x%x, buf[3]=0x%x\n", __FUNCTION__, buf[0], buf[1], buf[2], buf[3]); + + cmd_buf[0] = buf[0]<<24 | buf[1]<<16 | buf[2]<<8 | buf[3]; + set_BC_command_buffer(cmd_buf); + + ret = get_BC_write_command(); + value = BC_COMMAND_BUFFER_READY | BC_COMMAND_EN_CHAIN_ID | (i << 16) | (ret & 0x1f); + set_BC_write_command(value); + } + else // vil mode + { + buf[0] = VIL_COMMAND_TYPE | SET_CONFIG; + if(mode) + buf[0] = VIL_COMMAND_TYPE | SET_CONFIG |VIL_ALL; + buf[1] = 0x09; + buf[2] = chip_addr; + buf[3] = MISC_CONTROL; + buf[4] = 0x40; + if(bottom_or_mid) + buf[5] = 0x20; + else + buf[5] = 0x21; + + if(iic) + { + buf[6] = (bauddiv & 0x1f) | 0x40; + buf[7] = 0x60; + } + else + { + buf[6] = (bauddiv & 0x1f); + buf[7] = 0x00; + } + if(open_core) + buf[6] = buf[6]| GATEBCLK; + buf[8] = 0; + buf[8] = CRC5(buf, 8*8); + + cmd_buf[0] = buf[0]<<24 | buf[1]<<16 | buf[2]<<8 | buf[3]; + cmd_buf[1] = buf[4]<<24 | buf[5]<<16 | buf[6]<<8 | buf[7]; + cmd_buf[2] = buf[8]<<24; + + while (1) + { + if (((ret = get_BC_write_command()) & 0x80000000) == 0) + break; + cgsleep_ms(1); + } + set_BC_command_buffer(cmd_buf); + value = BC_COMMAND_BUFFER_READY | BC_COMMAND_EN_CHAIN_ID| (i << 16) | (ret & 0x1f); + set_BC_write_command(value); + } +} + + +void init_uart_baud() +{ + unsigned int rBaudrate = 0, baud = 0; + unsigned char bauddiv = 0; + int i =0; + + rBaudrate = 1000000 * 5/3 / dev->timeout * (64*8); //64*8 need send bit, ratio=2/3 + baud = 25000000/rBaudrate/8 - 1; + baud = 1; + + if(baud > MAX_BAUD_DIVIDER) + { + bauddiv = MAX_BAUD_DIVIDER; + } + else + { + bauddiv = baud; + } + + applog(LOG_DEBUG,"%s: bauddiv = %d\n", __FUNCTION__, bauddiv); + + set_baud(bauddiv,1); +} + +void set_led(bool stop) +{ + static bool blink = true; + char cmd[100]; + blink = !blink; + if(stop) + { + sprintf(cmd,"echo %d > %s", 0,GREEN_LED_DEV); + system(cmd); + sprintf(cmd,"echo %d > %s", (blink)?1:0,RED_LED_DEV); + system(cmd); + } + else + { + sprintf(cmd,"echo %d > %s", 0,RED_LED_DEV); + system(cmd); + sprintf(cmd,"echo %d > %s", (blink)?1:0,GREEN_LED_DEV); + system(cmd); + } + +} + +void pic_heart_beat_func() +{ + int i; + while(1) + { + for(i=0; ichain_exist[i]) + { + pthread_mutex_lock(&iic_mutex); + pic_heart_beat_each_chain(i); + pthread_mutex_unlock(&iic_mutex); + cgsleep_ms(10); + } + } + sleep(HEART_BEAT_TIME_GAP); + } +} + +void change_pic_voltage_old() +{ + int i; + sleep(300); + for(i=0; ichain_exist[i]) + { + while(1) + { + if(tmp_vol > chain_voltage[i]) + break; + tmp_vol += 5; + if(tmp_vol > chain_voltage[i]) + tmp_vol = chain_voltage[i]; + pthread_mutex_lock(&iic_mutex); + set_pic_voltage(i,tmp_vol); + pthread_mutex_unlock(&iic_mutex); + pthread_mutex_lock(&iic_mutex); + get_pic_voltage(i); + pthread_mutex_unlock(&iic_mutex); + if(tmp_vol == chain_voltage[i]) + break; + cgsleep_ms(100); + } + } + } +} + + +void check_system_work() +{ + struct timeval tv_start = {0, 0}, tv_end,tv_send; + int i = 0, j = 0; + cgtime(&tv_end); + copy_time(&tv_start, &tv_end); + copy_time(&tv_send_job,&tv_send); + bool stop = false; + int asic_num = 0, error_asic = 0, avg_num = 0; + while(1) + { + struct timeval diff; + cgtime(&tv_end); + cgtime(&tv_send); + timersub(&tv_end, &tv_start, &diff); + + if (diff.tv_sec > 60) + { + asic_num = 0, error_asic = 0, avg_num = 0; + for(i=0; ichain_exist[i]) + { + asic_num += dev->chain_asic_num[i]; + for(j=0; jchain_asic_num[i]; j++) + { + avg_num += dev->chain_asic_nonce[i][j]; + applog(LOG_DEBUG,"%s: chain %d asic %d asic_nonce_num %d", __FUNCTION__, i,j,dev->chain_asic_nonce[i][j]); + } + } + } + if (asic_num != 0) + { + applog(LOG_DEBUG,"%s: avg_num %d asic_num %d", __FUNCTION__, avg_num,asic_num); + avg_num = avg_num / asic_num / 8; + } + else + { + avg_num = 1; + } + for(i=0; ichain_exist[i]) + { + int offset = 0; + + for(j=0; jchain_asic_num[i]; j++) + { + if(j%8 == 0) + { + dev->chain_asic_status_string[i][j+offset] = ' '; + offset++; + } + + if(dev->chain_asic_nonce[i][j]>avg_num) + { + dev->chain_asic_status_string[i][j+offset] = 'o'; + } + else + { + dev->chain_asic_status_string[i][j+offset] = 'x'; + error_asic++; + } + dev->chain_asic_nonce[i][j] = 0; + } + dev->chain_asic_status_string[i][j+offset] = '\0'; + } + } + copy_time(&tv_start, &tv_end); + } + + check_fan(); + set_PWM_according_to_temperature(); + timersub(&tv_send, &tv_send_job, &diff); + if(diff.tv_sec > 120 || dev->temp_top1 > MAX_TEMP + || dev->fan_num < MIN_FAN_NUM || dev->fan_speed_top1 < (MAX_FAN_SPEED * dev->fan_pwm / 150)) + { + stop = true; + if(dev->temp_top1 > MAX_TEMP + || dev->fan_num < MIN_FAN_NUM || dev->fan_speed_top1 < (MAX_FAN_SPEED * dev->fan_pwm / 150)) + { + status_error = true; + once_error = true; + for(i=0; i < BITMAIN_MAX_CHAIN_NUM; i++) + { + if(dev->chain_exist[i] == 1) + { + pthread_mutex_lock(&iic_mutex); + disable_pic_dac(i); + pthread_mutex_unlock(&iic_mutex); + } + } + } + set_dhash_acc_control((unsigned int)get_dhash_acc_control() & ~RUN_BIT); + } + else + { + stop = false; + if (!once_error) + status_error = false; + } + if(stop_mining) + status_error = true; + /* + if(error_asic > asic_num/5 || asic_num == 0) + { + stop = true; + } + */ + set_led(stop); + + cgsleep_ms(1000); + } +} + + +void open_core() +{ + unsigned int i = 0, j = 0, k, m, work_id = 0, ret = 0, value = 0, work_fifo_ready = 0, loop=0; + unsigned char gateblk[4] = {0,0,0,0}; + unsigned int cmd_buf[3] = {0,0,0}, buf[TW_WRITE_COMMAND_LEN/sizeof(unsigned int)]= {0}; + unsigned int buf_vil_tw[TW_WRITE_COMMAND_LEN_VIL/sizeof(unsigned int)]= {0}; + unsigned char data[TW_WRITE_COMMAND_LEN] = {0xff}; + unsigned char buf_vil[9] = {0,0,0,0,0,0,0,0,0}; + struct vil_work work_vil; + struct vil_work_1387 work_vil_1387; + + loop = 114; + + + if(!opt_multi_version) // fil mode + { + set_dhash_acc_control(get_dhash_acc_control() & (~OPERATION_MODE)); + set_hash_counting_number(0); + gateblk[0] = SET_BAUD_OPS; + gateblk[1] = 0;//0x10; //16-23 + gateblk[2] = dev->baud | 0x80; //8-15 gateblk=1 + gateblk[0] |= 0x80; + //gateblk[3] = CRC5(gateblk, 4*8 - 5); + gateblk[3] = 0x80; // MMEN=1 + gateblk[3] = 0x80 | (0x1f & CRC5(gateblk, 4*8 - 5)); + applog(LOG_DEBUG,"%s: gateblk[0]=0x%x, gateblk[1]=0x%x, gateblk[2]=0x%x, gateblk[3]=0x%x\n", __FUNCTION__, gateblk[0], gateblk[1], gateblk[2], gateblk[3]); + cmd_buf[0] = gateblk[0]<<24 | gateblk[1]<<16 | gateblk[2]<<8 | gateblk[3]; + + memset(data, 0x00, TW_WRITE_COMMAND_LEN); + data[TW_WRITE_COMMAND_LEN - 1] = 0xff; + data[TW_WRITE_COMMAND_LEN - 12] = 0xff; + + for(i = 0; i < BITMAIN_MAX_CHAIN_NUM; i++) + { + if(dev->chain_exist[i] == 1) + { + set_BC_command_buffer(cmd_buf); + ret = get_BC_write_command(); + value = BC_COMMAND_BUFFER_READY | BC_COMMAND_EN_CHAIN_ID | (i << 16) | (ret & 0x1f); + set_BC_write_command(value); + cgsleep_us(10000); + + for(m=0; mbaud & 0x1f) | GATEBCLK; // enable gateblk + buf_vil[7] = MMEN; // MMEN=1 + + buf_vil[8] = CRC5(buf_vil, 8*8); + + cmd_buf[0] = buf_vil[0]<<24 | buf_vil[1]<<16 | buf_vil[2]<<8 | buf_vil[3]; + cmd_buf[1] = buf_vil[4]<<24 | buf_vil[5]<<16 | buf_vil[6]<<8 | buf_vil[7]; + cmd_buf[2] = buf_vil[8]<<24; + + // prepare special work for openning core + memset(buf_vil_tw, 0x00, TW_WRITE_COMMAND_LEN_VIL/sizeof(unsigned int)); + memset(&work_vil_1387, 0xff, sizeof(struct vil_work_1387)); + + for(i = 0; i < BITMAIN_MAX_CHAIN_NUM; i++) + { + if(dev->chain_exist[i] == 1) + { + work_vil_1387.work_type = NORMAL_BLOCK_MARKER; + work_vil_1387.chain_id = 0x80 | i; + work_vil_1387.reserved1[0]= 0; + work_vil_1387.reserved1[1]= 0; + work_vil_1387.work_count = 0; + work_vil_1387.data[0] = 0xff; + work_vil_1387.data[11] = 0xff; + set_BC_command_buffer(cmd_buf); + ret = get_BC_write_command(); + value = BC_COMMAND_BUFFER_READY | BC_COMMAND_EN_CHAIN_ID | (i << 16) | (ret & 0x1f); + set_BC_write_command(value); + cgsleep_us(10000); + + for(m=0; mbaud,0,0x60,i,1,0,MID_OR_BOT); + cgsleep_ms(10); + set_baud_with_addr(dev->baud,0,0xa8,i,1,0,MID_OR_BOT); + cgsleep_ms(10); + */ + set_baud_with_addr(dev->baud,0,HAVE_TEMP,i,1,0,MID_OR_BOT); + cgsleep_ms(10); +#endif + } + } + set_dhash_acc_control(get_dhash_acc_control()| VIL_MODE | VIL_MIDSTATE_NUMBER(opt_multi_version)); + } +} + +#if 0 +void open_core() +{ + unsigned int i = 0, j = 0, m, work_id = 0, ret = 0, value = 0, work_fifo_ready = 0; + unsigned char gateblk[4] = {0,0,0,0}; + unsigned int cmd_buf[3] = {0,0,0}, buf[TW_WRITE_COMMAND_LEN/sizeof(unsigned int)]= {0}; + unsigned int buf_vil_tw[TW_WRITE_COMMAND_LEN_VIL/sizeof(unsigned int)]= {0}; + unsigned char data[TW_WRITE_COMMAND_LEN] = {0xff}; + unsigned char buf_vil[9] = {0}; + struct vil_work work_vil; + + if(!opt_multi_version) // fil mode + { + set_dhash_acc_control(get_dhash_acc_control() & (~OPERATION_MODE)); + set_hash_counting_number(0); + gateblk[0] = SET_BAUD_OPS; + gateblk[1] = 0;//0x10; //16-23 + gateblk[2] = dev->baud | 0x80; //8-15 gateblk=1 + gateblk[0] |= 0x80; + gateblk[3] = CRC5(gateblk, 4*8 - 5); + applog(LOG_DEBUG,"%s: gateblk[0]=0x%x, gateblk[1]=0x%x, gateblk[2]=0x%x, gateblk[3]=0x%x\n", __FUNCTION__, gateblk[0], gateblk[1], gateblk[2], gateblk[3]); + cmd_buf[0] = gateblk[0]<<24 | gateblk[1]<<16 | gateblk[2]<<8 | gateblk[3]; + + memset(data, 0x00, TW_WRITE_COMMAND_LEN); + data[TW_WRITE_COMMAND_LEN - 1] = 0xff; + data[TW_WRITE_COMMAND_LEN - 12] = 0xff; + + for(i = 0; i < BITMAIN_MAX_CHAIN_NUM; i++) + { + if(dev->chain_exist[i] == 1) + { + set_BC_command_buffer(cmd_buf); + ret = get_BC_write_command(); + value = BC_COMMAND_BUFFER_READY | BC_COMMAND_EN_CHAIN_ID | (i << 16) | (ret & 0x1f); + set_BC_write_command(value); + cgsleep_ms(10); + + for(m=0; mbaud & 0x1f) | GATEBCLK; + buf_vil[7] = 0; + buf_vil[8] = CRC5(buf_vil, 8*8); + + cmd_buf[0] = buf_vil[0]<<24 | buf_vil[1]<<16 | buf_vil[2]<<8 | buf_vil[3]; + cmd_buf[1] = buf_vil[4]<<24 | buf_vil[5]<<16 | buf_vil[6]<<8 | buf_vil[7]; + cmd_buf[2] = buf_vil[8]<<24; + + // prepare special work for openning core + memset(&work_vil, 0, sizeof(struct vil_work)); + work_vil.type = 0x01 << 5; + work_vil.length = sizeof(struct vil_work); + work_vil.wc_base = 0; + work_vil.mid_num = 1; + //work_vil.sno = 0; + work_vil.data2[0] = 0xff; + work_vil.data2[11] = 0xff; + + memset(data, 0x00, 4); + memset(buf_vil_tw, 0x00, TW_WRITE_COMMAND_LEN_VIL); + + for(i = 0; i < BITMAIN_MAX_CHAIN_NUM; i++) + { + if(dev->chain_exist[i] == 1) + { + set_BC_command_buffer(cmd_buf); + ret = get_BC_write_command(); + value = BC_COMMAND_BUFFER_READY | BC_COMMAND_EN_CHAIN_ID | (i << 16) | (ret & 0x1f); + set_BC_write_command(value); + cgsleep_ms(10); + + for(m=0; m= MAX_NONCE_NUMBER_IN_FIFO || reg_value_buf.p_wr >= MAX_NONCE_NUMBER_IN_FIFO) + { + clear_register_value_buf(); + continue; + } + pthread_mutex_lock(®_mutex); + + reg_value_buf.reg_buffer[reg_value_buf.p_wr].reg_value = buf[1]; + reg_value_buf.reg_buffer[reg_value_buf.p_wr].crc = (buf[0] >> 24) & 0x1f; + reg_value_buf.reg_buffer[reg_value_buf.p_wr].chain_number = CHAIN_NUMBER(buf[0]); + + if(reg_value_buf.p_wr < MAX_NONCE_NUMBER_IN_FIFO ) + { + reg_value_buf.p_wr++; + } + else + { + reg_value_buf.p_wr = 0; + } + + if(reg_value_buf.reg_value_num < MAX_NONCE_NUMBER_IN_FIFO) + { + reg_value_buf.reg_value_num++; + } + else + { + reg_value_buf.reg_value_num = MAX_NONCE_NUMBER_IN_FIFO; + } + //applog(LOG_NOTICE,"%s: p_wr = %d reg_value_num = %d\n", __FUNCTION__,reg_value_buf.p_wr,reg_value_buf.reg_value_num); + pthread_mutex_unlock(®_mutex); + } + } + } + } +} + + +//interface between bmminer and axi driver +int bitmain_c5_init(struct init_config config) +{ + char ret=0,j; + uint16_t crc = 0; + struct init_config config_parameter; + int i=0,x = 0,y = 0; + int hardware_version; + unsigned int data = 0; + + memcpy(&config_parameter, &config, sizeof(struct init_config)); + + if(config_parameter.token_type != INIT_CONFIG_TYPE) + { + applog(LOG_DEBUG,"%s: config_parameter.token_type != 0x%x, it is 0x%x\n", __FUNCTION__, INIT_CONFIG_TYPE, config_parameter.token_type); + return -1; + } + + crc = CRC16((uint8_t*)(&config_parameter), sizeof(struct init_config) - sizeof(uint16_t)); + if(crc != config_parameter.crc) + { + applog(LOG_DEBUG,"%s: config_parameter.crc = 0x%x, but we calculate it as 0x%x\n", __FUNCTION__, config_parameter.crc, crc); + return -2; + } + + //malloc nonce_read_out +#if 0 + nonce_read_out = malloc(sizeof(struct nonce_buf)); + if(!nonce_read_out) + { + applog(LOG_DEBUG,"%s: malloc nonce_read_out failed\n", __FUNCTION__); + return -3; + } + else + { + memset(nonce_read_out, 0, sizeof(struct nonce_buf)); + mutex_init(&nonce_read_out.spinlock); + } + + //malloc register value buffer + reg_value_buf = malloc(sizeof(struct reg_buf)); + if(!reg_value_buf) + { + applog(LOG_DEBUG,"%s: malloc reg_value_buf failed\n", __FUNCTION__); + return -4; + } + else + { + memset(reg_value_buf, 0, sizeof(struct reg_buf)); + mutex_init(®_value_buf.spinlock); + } +#endif + read_nonce_reg_id = calloc(1,sizeof(struct thr_info)); + if(thr_info_create(read_nonce_reg_id, NULL, get_nonce_and_register, read_nonce_reg_id)) + { + applog(LOG_DEBUG,"%s: create thread for get nonce and register from FPGA failed\n", __FUNCTION__); + return -5; + } + + pthread_detach(read_nonce_reg_id->pth); + + //init axi + bitmain_axi_init(); + + //reset FPGA & HASH board + if(config_parameter.reset) + { + set_QN_write_data_command(RESET_HASH_BOARD | RESET_ALL | RESET_FPGA | RESET_TIME(15)); + while(get_QN_write_data_command() & RESET_HASH_BOARD) + { + cgsleep_ms(30); + } + } + set_nonce2_and_job_id_store_address(PHY_MEM_NONCE2_JOBID_ADDRESS); + set_job_start_address(PHY_MEM_JOB_START_ADDRESS_1); + //check chain + check_chain(); + + char * buf_hex = NULL; + int board_num = 0; + for(i=0; i < BITMAIN_MAX_CHAIN_NUM; i++) + { + if(dev->chain_exist[i] == 1) + { + pthread_mutex_lock(&iic_mutex); + get_hash_board_id_number(i,hash_board_id[i]); + buf_hex = bin2hex(hash_board_id[i],12); + sprintf(hash_board_id_string + (board_num*id_string_len),"{\"ID\":\"%s\"},",buf_hex); + board_num++; + free(buf_hex); + buf_hex = NULL; + pthread_mutex_unlock(&iic_mutex); + } + } + hash_board_id_string[board_num*id_string_len - 1] = '\0'; + + for(i=0; i < BITMAIN_MAX_CHAIN_NUM; i++) + { + if(dev->chain_exist[i] == 1) + { + pthread_mutex_lock(&iic_mutex); + reset_iic_pic(i); + cgsleep_ms(500); + jump_to_app_from_loader(i); + pthread_mutex_unlock(&iic_mutex); + } + } + +#if 0 + de_voltage = opt_bitmain_c5_voltage; + cgsleep_ms(100); + for(i=0; i < BITMAIN_MAX_CHAIN_NUM; i++) + { + if(dev->chain_exist[i] == 1) + { + pthread_mutex_lock(&iic_mutex); + chain_voltage[i] = get_pic_voltage(i); + pthread_mutex_unlock(&iic_mutex); + } + } + cgsleep_ms(100); + for(i=0; i < BITMAIN_MAX_CHAIN_NUM; i++) + { + if(dev->chain_exist[i] == 1) + { + pthread_mutex_lock(&iic_mutex); + //if(de_voltage < chain_voltage[i]) + set_pic_voltage(i,de_voltage); + pthread_mutex_unlock(&iic_mutex); + pthread_mutex_lock(&iic_mutex); + get_pic_voltage(i); + pthread_mutex_unlock(&iic_mutex); + } + } + cgsleep_ms(100); +#endif +#if 0 + change_voltage_to_old = calloc(1,sizeof(struct thr_info)); + if(thr_info_create(change_voltage_to_old, NULL, change_pic_voltage_old, change_voltage_to_old)) + { + applog(LOG_DEBUG,"%s: create thread error for pic_heart_beat_func\n", __FUNCTION__); + return -6; + } + pthread_detach(change_voltage_to_old->pth); +#endif + pic_heart_beat = calloc(1,sizeof(struct thr_info)); + if(thr_info_create(pic_heart_beat, NULL, pic_heart_beat_func, pic_heart_beat)) + { + applog(LOG_DEBUG,"%s: create thread error for pic_heart_beat_func\n", __FUNCTION__); + return -6; + } + pthread_detach(pic_heart_beat->pth); + + for(i=0; i < BITMAIN_MAX_CHAIN_NUM; i++) + { + if(dev->chain_exist[i] == 1) + { + pthread_mutex_lock(&iic_mutex); + enable_pic_dac(i); + pthread_mutex_unlock(&iic_mutex); + } + } + + cgsleep_ms(100); + + if(config_parameter.reset) + { + set_QN_write_data_command(RESET_HASH_BOARD | RESET_ALL | RESET_TIME(15)); + while(get_QN_write_data_command() & RESET_HASH_BOARD) + { + cgsleep_ms(30); + } + } + + if(opt_multi_version) + set_dhash_acc_control(get_dhash_acc_control() & (~OPERATION_MODE) | VIL_MODE | VIL_MIDSTATE_NUMBER(opt_multi_version) & (~NEW_BLOCK) & (~RUN_BIT)); + + cgsleep_ms(10); + //check ASIC number for every chain + check_asic_reg(CHIP_ADDRESS); + cgsleep_ms(10); + //set core number + dev->corenum = BM1387_CORE_NUM; + + software_set_address(); + cgsleep_ms(10); + + check_asic_reg(CHIP_ADDRESS); + cgsleep_ms(10); + + set_asic_ticket_mask(63); + cgsleep_ms(10); + + if(config_parameter.frequency_eft) + { + dev->frequency = config_parameter.frequency; + set_frequency(dev->frequency); + sprintf(dev->frequency_t,"%u",dev->frequency); + } + + cgsleep_ms(10); + + //check who control fan + dev->fan_eft = config_parameter.fan_eft; + dev->fan_pwm= config_parameter.fan_pwm_percent; + applog(LOG_DEBUG,"%s: fan_eft : %d fan_pwm : %d\n", __FUNCTION__,dev->fan_eft,dev->fan_pwm); + if(config_parameter.fan_eft) + { + if((config_parameter.fan_pwm_percent >= 0) && (config_parameter.fan_pwm_percent <= 100)) + { + set_PWM(config_parameter.fan_pwm_percent); + } + else + { + set_PWM_according_to_temperature(); + } + } + else + { + set_PWM_according_to_temperature(); + } + + //calculate real timeout + if(config_parameter.timeout_eft) + { + if(config_parameter.timeout_data_integer == 0 && config_parameter.timeout_data_fractions == 0) //driver calculate out timeout value + { + dev->timeout = 0x1000000/calculate_core_number(dev->corenum)*dev->addrInterval/(dev->frequency)*90/100; + applog(LOG_DEBUG,"dev->timeout = %d\n", dev->timeout); + } + else + { + dev->timeout = config_parameter.timeout_data_integer * 1000 + config_parameter.timeout_data_fractions; + } + + if(dev->timeout > MAX_TIMEOUT_VALUE) + { + dev->timeout = MAX_TIMEOUT_VALUE; + } + } + + //set baud + init_uart_baud(); + cgsleep_ms(10); +#if USE_IIC + if(access("/config/temp_sensor", 0) == -1) + { + system("touch /config/temp_sensor"); + for(i=0; ichain_exist[i] == 1) + { + set_baud_with_addr(dev->baud,0,HAVE_TEMP,i,1,open_core,MID_OR_BOT); + } + } + + cgsleep_ms(5); + + for(i=0; i < BITMAIN_MAX_CHAIN_NUM; i++) + { + if(dev->chain_exist[i] == 1 && dev->chain_asic_num[i] == CHAIN_ASIC_NUM) + { + calibration_sensor_offset(0x98,HAVE_TEMP,i,3); + cgsleep_ms(10); + } + } + } +#endif + + +#if 0 + for(i=0; i < BITMAIN_MAX_CHAIN_NUM; i++) + { + if(dev->chain_exist[i] == 1 && dev->chain_asic_num[i] == CHAIN_ASIC_NUM) + { + set_frequency_with_addr(dev->frequency + ADD_FREQ1,0,0x54,i); + //set_frequency_with_addr(dev->frequency + ADD_FREQ1,0,0x58,i); + //set_frequency_with_addr(dev->frequency + ADD_FREQ1,0,0x5c,i); + set_frequency_with_addr(dev->frequency + ADD_FREQ,0,0x60,i); + //set_frequency_with_addr(dev->frequency + ADD_FREQ,0,0x64,i); + //set_frequency_with_addr(dev->frequency + ADD_FREQ,0,0x68,i); + set_frequency_with_addr(dev->frequency + ADD_FREQ,0,0x6c,i); + //set_frequency_with_addr(dev->frequency + ADD_FREQ,0,0x70,i); + //set_frequency_with_addr(dev->frequency + ADD_FREQ,0,0x74,i); + set_frequency_with_addr(dev->frequency + ADD_FREQ1,0,0x78,i); + //set_frequency_with_addr(dev->frequency + ADD_FREQ1,0,0x7c,i); + //set_frequency_with_addr(dev->frequency + ADD_FREQ1,0,0x80,i); + } + } +#endif + //set big timeout value for open core + //set_time_out_control((MAX_TIMEOUT_VALUE - 100) | TIME_OUT_VALID); + set_time_out_control(0xc350 | TIME_OUT_VALID); + + open_core(); + + //set real timeout back + if(opt_multi_version) + set_time_out_control(((dev->timeout * opt_multi_version) & MAX_TIMEOUT_VALUE) | TIME_OUT_VALID); + else + set_time_out_control(((dev->timeout) & MAX_TIMEOUT_VALUE) | TIME_OUT_VALID); + check_system_work_id = calloc(1,sizeof(struct thr_info)); + if(thr_info_create(check_system_work_id, NULL, check_system_work, check_system_work_id)) + { + applog(LOG_DEBUG,"%s: create thread for check system\n", __FUNCTION__); + return -6; + } + pthread_detach(check_system_work_id->pth); + +#if 1 + read_hash_rate = calloc(1,sizeof(struct thr_info)); + if(thr_info_create(read_hash_rate, NULL, get_hash_rate, read_hash_rate)) + { + applog(LOG_DEBUG,"%s: create thread for get nonce and register from FPGA failed\n", __FUNCTION__); + return -5; + } + + pthread_detach(read_hash_rate->pth); +#endif + +#if 1 + read_temp_id = calloc(1,sizeof(struct thr_info)); + if(thr_info_create(read_temp_id, NULL, read_temp_func, read_temp_id)) + { + applog(LOG_DEBUG,"%s: create thread for read temp\n", __FUNCTION__); + return -7; + } + pthread_detach(read_temp_id->pth); + +#endif + + + for(x=0; xchain_exist[x]) + { + int offset = 0; + for(y=0; ychain_asic_num[x]; y++) + { + if(y%8 == 0) + { + dev->chain_asic_status_string[x][y+offset] = ' '; + offset++; + } + dev->chain_asic_status_string[x][y+offset] = 'o'; + dev->chain_asic_nonce[x][y] = 0; + } + dev->chain_asic_status_string[x][y+offset] = '\0'; + } + } + + hardware_version = get_hardware_version(); + pcb_version = (hardware_version >> 16) & 0x0000ffff; + fpga_version = hardware_version & 0x000000ff; + sprintf(g_miner_version, "%d.%d.%d.%d", fpga_version, pcb_version, C5_VERSION, 0); + + return 0; +} + +int parse_job_to_c5(unsigned char **buf,struct pool *pool,uint32_t id) +{ + uint16_t crc = 0; + uint32_t buf_len = 0; + uint64_t nonce2 = 0; + unsigned char * tmp_buf; + int i; + static uint64_t pool_send_nu = 0; + struct part_of_job part_job; + char *buf_hex = NULL; + + part_job.token_type = SEND_JOB_TYPE; + part_job.version = 0x00; + part_job.pool_nu = pool_send_nu; + part_job.new_block = pool->swork.clean ?1:0; + part_job.asic_diff_valid = 1; + part_job.asic_diff = 15; + part_job.job_id = id; + + hex2bin(&part_job.bbversion, pool->bbversion, 4); + hex2bin(part_job.prev_hash, pool->prev_hash, 32); + hex2bin(&part_job.nbit, pool->nbit, 4); + hex2bin(&part_job.ntime, pool->ntime, 4); + part_job.coinbase_len = pool->coinbase_len; + part_job.nonce2_offset = pool->nonce2_offset; + part_job.nonce2_bytes_num = pool->n2size; + + nonce2 = htole64(pool->nonce2); + memcpy(&(part_job.nonce2_start_value), pool->coinbase + pool->nonce2_offset,8); + memcpy(&(part_job.nonce2_start_value), &nonce2,pool->n2size); + + part_job.merkles_num = pool->merkles; + buf_len = sizeof(struct part_of_job) + pool->coinbase_len + pool->merkles * 32 + 2; + + tmp_buf = (unsigned char *)malloc(buf_len); + if (unlikely(!tmp_buf)) + quit(1, "Failed to malloc tmp_buf"); + part_job.length = buf_len -8; + + memset(tmp_buf,0,buf_len); + memcpy(tmp_buf,&part_job,sizeof(struct part_of_job)); + memcpy(tmp_buf + sizeof(struct part_of_job), pool->coinbase, pool->coinbase_len); + /* + buf_hex = bin2hex(pool->coinbase,pool->coinbase_len); + printf("coinbase:%s offset:%d n2size:%d nonce2%lld\n",buf_hex,pool->nonce2_offset,pool->n2size,pool->nonce2); + free(buf_hex); + */ + for (i = 0; i < pool->merkles; i++) + { + memcpy(tmp_buf + sizeof(struct part_of_job) + pool->coinbase_len + i * 32, pool->swork.merkle_bin[i], 32); + } + + crc = CRC16((uint8_t *)tmp_buf, buf_len-2); + memcpy(tmp_buf + (buf_len - 2), &crc, 2); + + pool_send_nu++; + *buf = (unsigned char *)malloc(buf_len); + if (unlikely(!tmp_buf)) + quit(1, "Failed to malloc buf"); + memcpy(*buf,tmp_buf,buf_len); + free(tmp_buf); + return buf_len; +} + +static void show_status(int if_quit) +{ + char * buf_hex = NULL; + unsigned int *l_job_start_address = NULL; + unsigned int buf[2] = {0}; + int i = 0; + get_work_nonce2(buf); + set_dhash_acc_control((unsigned int)get_dhash_acc_control() & ~RUN_BIT); + while((unsigned int)get_dhash_acc_control() & RUN_BIT) + { + cgsleep_ms(1); + applog(LOG_DEBUG,"%s: run bit is 1 after set it to 0", __FUNCTION__); + } + + buf_hex = bin2hex((unsigned char *)dev->current_job_start_address,c_coinbase_padding); + + free(buf_hex); + for(i=0; icurrent_job_start_address + c_coinbase_padding+ i*MERKLE_BIN_LEN,32); + free(buf_hex); + } + if(dev->current_job_start_address == job_start_address_1) + { + l_job_start_address = job_start_address_2; + } + else if(dev->current_job_start_address == job_start_address_2) + { + l_job_start_address = job_start_address_1; + } + buf_hex = bin2hex((unsigned char *)l_job_start_address,l_coinbase_padding); + free(buf_hex); + for(i=0; icoinbase,pool->coinbase_len); + printf("%s: nonce2 0x%x\n", __FUNCTION__, nonce2); + printf("%s: coinbase : %s\n", __FUNCTION__, buf_hex); + free(buf_hex); + for(i=0; imerkles; i++) + { + buf_hex = bin2hex(pool->swork.merkle_bin[i],32); + printf("%s: merkle_bin %d : %s\n", __FUNCTION__, i, buf_hex); + free(buf_hex); + } +} + + +int send_job(unsigned char *buf) +{ + unsigned int len = 0, i=0, j=0, coinbase_padding_len = 0; + unsigned short int crc = 0, job_length = 0; + unsigned char *temp_buf = NULL, *coinbase_padding = NULL, *merkles_bin = NULL; + unsigned char buf1[PREV_HASH_LEN] = {0}; + unsigned int buf2[PREV_HASH_LEN] = {0}; + int times = 0; + struct part_of_job *part_job = NULL; + + applog(LOG_DEBUG,"--- %s\n", __FUNCTION__); + + if(*(buf + 0) != SEND_JOB_TYPE) + { + applog(LOG_DEBUG,"%s: SEND_JOB_TYPE is wrong : 0x%x\n", __FUNCTION__, *(buf + 0)); + return -1; + } + + len = *((unsigned int *)buf + 4/sizeof(int)); + applog(LOG_DEBUG,"%s: len = 0x%x\n", __FUNCTION__, len); + + temp_buf = malloc(len + 8*sizeof(unsigned char)); + if(!temp_buf) + { + applog(LOG_DEBUG,"%s: malloc buffer failed.\n", __FUNCTION__); + return -2; + } + else + { + memset(temp_buf, 0, len + 8*sizeof(unsigned char)); + memcpy(temp_buf, buf, len + 8*sizeof(unsigned char)); + part_job = (struct part_of_job *)temp_buf; + } + + //write new job data into dev->current_job_start_address + if(dev->current_job_start_address == job_start_address_1) + { + dev->current_job_start_address = job_start_address_2; + } + else if(dev->current_job_start_address == job_start_address_2) + { + dev->current_job_start_address = job_start_address_1; + } + else + { + applog(LOG_DEBUG,"%s: dev->current_job_start_address = 0x%x, but job_start_address_1 = 0x%x, job_start_address_2 = 0x%x\n", __FUNCTION__, dev->current_job_start_address, job_start_address_1, job_start_address_2); + return -3; + } + + + if((part_job->coinbase_len % 64) > 55) + { + coinbase_padding_len = (part_job->coinbase_len/64 + 2) * 64; + } + else + { + coinbase_padding_len = (part_job->coinbase_len/64 + 1) * 64; + } + + coinbase_padding = malloc(coinbase_padding_len); + if(!coinbase_padding) + { + applog(LOG_DEBUG,"%s: malloc coinbase_padding failed.\n", __FUNCTION__); + return -4; + } + else + { + applog(LOG_DEBUG,"%s: coinbase_padding = 0x%x", __FUNCTION__, (unsigned int)coinbase_padding); + } + + if(part_job->merkles_num) + { + merkles_bin = malloc(part_job->merkles_num * MERKLE_BIN_LEN); + if(!merkles_bin) + { + applog(LOG_DEBUG,"%s: malloc merkles_bin failed.\n", __FUNCTION__); + return -5; + } + else + { + applog(LOG_DEBUG,"%s: merkles_bin = 0x%x", __FUNCTION__, (unsigned int)merkles_bin); + } + } + + //applog(LOG_DEBUG,"%s: copy coinbase into memory ...\n", __FUNCTION__); + memset(coinbase_padding, 0, coinbase_padding_len); + memcpy(coinbase_padding, buf + sizeof(struct part_of_job), part_job->coinbase_len); + *(coinbase_padding + part_job->coinbase_len) = 0x80; + *((unsigned int *)coinbase_padding + (coinbase_padding_len - 4)/sizeof(int)) = Swap32((unsigned int)((unsigned long long int)(part_job->coinbase_len * sizeof(char) * 8) & 0x00000000ffffffff)); // 8 means 8 bits + *((unsigned int *)coinbase_padding + (coinbase_padding_len - 8)/sizeof(int)) = Swap32((unsigned int)(((unsigned long long int)(part_job->coinbase_len * sizeof(char) * 8) & 0xffffffff00000000) >> 32)); // 8 means 8 bits + + l_coinbase_padding = c_coinbase_padding; + c_coinbase_padding = coinbase_padding_len; + for(i=0; icurrent_job_start_address + i) = *(coinbase_padding + i); + //applog(LOG_DEBUG,"%s: coinbase_padding_in_ddr[%d] = 0x%x", __FUNCTION__, i, *(((unsigned char *)dev->current_job_start_address + i))); + } + + /* check coinbase & padding in ddr */ + for(i=0; icurrent_job_start_address + i) != *(coinbase_padding + i)) + { + applog(LOG_DEBUG,"%s: coinbase_padding_in_ddr[%d] = 0x%x, but *(coinbase_padding + %d) = 0x%x", __FUNCTION__, i, *(((unsigned char *)dev->current_job_start_address + i)), i, *(coinbase_padding + i)); + } + } + l_merkles_num = c_merkles_num; + c_merkles_num = part_job->merkles_num; + if(part_job->merkles_num) + { + applog(LOG_DEBUG,"%s: copy merkle bin into memory ...\n", __FUNCTION__); + memset(merkles_bin, 0, part_job->merkles_num * MERKLE_BIN_LEN); + memcpy(merkles_bin, buf + sizeof(struct part_of_job) + part_job->coinbase_len , part_job->merkles_num * MERKLE_BIN_LEN); + + for(i=0; i<(part_job->merkles_num * MERKLE_BIN_LEN); i++) + { + *((unsigned char *)dev->current_job_start_address + coinbase_padding_len + i) = *(merkles_bin + i); + //applog(LOG_DEBUG,"%s: merkles_in_ddr[%d] = 0x%x", __FUNCTION__, i, *(((unsigned char *)dev->current_job_start_address + coinbase_padding_len + i))); + } + + for(i=0; i<(part_job->merkles_num * MERKLE_BIN_LEN); i++) + { + if(*((unsigned char *)dev->current_job_start_address + coinbase_padding_len + i) != *(merkles_bin + i)) + { + applog(LOG_DEBUG,"%s: merkles_in_ddr[%d] = 0x%x, but *(merkles_bin + %d) =0x%x", __FUNCTION__, i, *(((unsigned char *)dev->current_job_start_address + coinbase_padding_len + i)), i, *(merkles_bin + i)); + } + } + } + + + + set_dhash_acc_control((unsigned int)get_dhash_acc_control() & ~RUN_BIT); + while((unsigned int)get_dhash_acc_control() & RUN_BIT) + { + cgsleep_ms(1); + applog(LOG_DEBUG,"%s: run bit is 1 after set it to 0\n", __FUNCTION__); + times++; + } + cgsleep_ms(1); + + + //write new job data into dev->current_job_start_address + if(dev->current_job_start_address == job_start_address_1) + { + set_job_start_address(PHY_MEM_JOB_START_ADDRESS_1); + //applog(LOG_DEBUG,"%s: dev->current_job_start_address = 0x%x\n", __FUNCTION__, (unsigned int)job_start_address_2); + } + else if(dev->current_job_start_address == job_start_address_2) + { + set_job_start_address(PHY_MEM_JOB_START_ADDRESS_2); + //applog(LOG_DEBUG,"%s: dev->current_job_start_address = 0x%x\n", __FUNCTION__, (unsigned int)job_start_address_1); + } + + if(part_job->asic_diff_valid) + { + set_ticket_mask((unsigned int)(part_job->asic_diff & 0x000000ff)); + dev->diff = part_job->asic_diff & 0xff; + } + + set_job_id(part_job->job_id); + + set_block_header_version(part_job->bbversion); + + memset(buf2, 0, PREV_HASH_LEN*sizeof(unsigned int)); + for(i=0; i<(PREV_HASH_LEN/sizeof(unsigned int)); i++) + { + buf2[i] = ((part_job->prev_hash[4*i + 3]) << 24) | ((part_job->prev_hash[4*i + 2]) << 16) | ((part_job->prev_hash[4*i + 1]) << 8) | (part_job->prev_hash[4*i + 0]); + } + set_pre_header_hash(buf2); + + set_time_stamp(part_job->ntime); + + set_target_bits(part_job->nbit); + + j = (part_job->nonce2_offset << 16) | ((unsigned char)(part_job->nonce2_bytes_num & 0x00ff)) << 8 | (unsigned char)((coinbase_padding_len/64) & 0x000000ff); + set_coinbase_length_and_nonce2_length(j); + + //memset(buf2, 0, PREV_HASH_LEN*sizeof(unsigned int)); + buf2[0] = 0; + buf2[1] = 0; + buf2[0] = ((unsigned long long )(part_job->nonce2_start_value)) & 0xffffffff; + buf2[1] = ((unsigned long long )(part_job->nonce2_start_value) >> 32) & 0xffffffff; + set_work_nonce2(buf2); + + set_merkle_bin_number(part_job->merkles_num); + + job_length = coinbase_padding_len + part_job->merkles_num*MERKLE_BIN_LEN; + set_job_length((unsigned int)job_length & 0x0000ffff); + + cgsleep_ms(1); + + + if(!gBegin_get_nonce) + { + set_nonce_fifo_interrupt(get_nonce_fifo_interrupt() | FLUSH_NONCE3_FIFO); + gBegin_get_nonce = true; + } +#if 1 + //start FPGA generating works + if(part_job->new_block) + { + if(!opt_multi_version) + { + set_dhash_acc_control((unsigned int)get_dhash_acc_control() | NEW_BLOCK ); + set_dhash_acc_control((unsigned int)get_dhash_acc_control() | RUN_BIT | OPERATION_MODE); + } + else + { + set_dhash_acc_control((unsigned int)get_dhash_acc_control() | NEW_BLOCK ); + set_dhash_acc_control((unsigned int)get_dhash_acc_control() | RUN_BIT | OPERATION_MODE |VIL_MODE); + } + } + else + { + if(!opt_multi_version) + set_dhash_acc_control((unsigned int)get_dhash_acc_control() | RUN_BIT| OPERATION_MODE ); + else + set_dhash_acc_control((unsigned int)get_dhash_acc_control() | RUN_BIT| OPERATION_MODE |VIL_MODE); + } +#endif + + free(temp_buf); + free((unsigned char *)coinbase_padding); + if(part_job->merkles_num) + { + free((unsigned char *)merkles_bin); + } + + applog(LOG_DEBUG,"--- %s end\n", __FUNCTION__); + cgtime(&tv_send_job); + return 0; +} + +static void copy_pool_stratum(struct pool *pool_stratum, struct pool *pool) +{ + int i; + int merkles = pool->merkles; + size_t coinbase_len = pool->coinbase_len; + + if (!pool->swork.job_id) + return; + + cg_wlock(&pool_stratum->data_lock); + free(pool_stratum->swork.job_id); + free(pool_stratum->nonce1); + free(pool_stratum->coinbase); + + align_len(&coinbase_len); + pool_stratum->coinbase = calloc(coinbase_len, 1); + if (unlikely(!pool_stratum->coinbase)) + quit(1, "Failed to calloc pool_stratum coinbase in c5"); + memcpy(pool_stratum->coinbase, pool->coinbase, coinbase_len); + + + for (i = 0; i < pool_stratum->merkles; i++) + free(pool_stratum->swork.merkle_bin[i]); + if (merkles) + { + pool_stratum->swork.merkle_bin = realloc(pool_stratum->swork.merkle_bin, + sizeof(char *) * merkles + 1); + for (i = 0; i < merkles; i++) + { + pool_stratum->swork.merkle_bin[i] = malloc(32); + if (unlikely(!pool_stratum->swork.merkle_bin[i])) + quit(1, "Failed to malloc pool_stratum swork merkle_bin"); + memcpy(pool_stratum->swork.merkle_bin[i], pool->swork.merkle_bin[i], 32); + } + } + pool_stratum->pool_no = pool->pool_no; + pool_stratum->sdiff = pool->sdiff; + pool_stratum->coinbase_len = pool->coinbase_len; + pool_stratum->nonce2_offset = pool->nonce2_offset; + pool_stratum->n2size = pool->n2size; + pool_stratum->merkles = pool->merkles; + + pool_stratum->swork.job_id = strdup(pool->swork.job_id); + pool_stratum->nonce1 = strdup(pool->nonce1); + + memcpy(pool_stratum->ntime, pool->ntime, sizeof(pool_stratum->ntime)); + memcpy(pool_stratum->header_bin, pool->header_bin, sizeof(pool_stratum->header_bin)); + cg_wunlock(&pool_stratum->data_lock); +} + + +static void noblock_socket(int fd) +{ + int flags = fcntl(fd, F_GETFL, 0); + fcntl(fd, F_SETFL, O_NONBLOCK | flags); +} + +static void block_socket(int fd) +{ + int flags = fcntl(fd, F_GETFL, 0); + fcntl(fd, F_SETFL, flags & ~O_NONBLOCK); +} + +static bool sock_connecting(void) +{ + return errno == EINPROGRESS; +} + +static int get_mac(char * device,char **mac) +{ + struct ifreq ifreq; + int sock = 0; + + sock = socket(AF_INET,SOCK_STREAM,0); + if(sock < 0) + { + perror("error sock"); + return 2; + } + strcpy(ifreq.ifr_name,device); + if(ioctl(sock,SIOCGIFHWADDR,&ifreq) < 0) + { + perror("error ioctl"); + close(sock); + return 3; + } + int i = 0; + for(i = 0; i < 6; i++) + { + sprintf(*mac+3*i, "%02X:", (unsigned char)ifreq.ifr_hwaddr.sa_data[i]); + } + (*mac)[strlen(*mac) - 1] = 0; + close(sock); + return 0; +} + +static bool setup_send_mac_socket(char * s) +{ + struct addrinfo *servinfo, hints, *p; + int sockd; + int send_bytes,recv_bytes; + char rec[1024]; + int flags; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + if (getaddrinfo(AUTH_URL, PORT, &hints, &servinfo) != 0) + { + return false; + } + for (p = servinfo; p != NULL; p = p->ai_next) + { + sockd = socket(p->ai_family, p->ai_socktype, p->ai_protocol); + if (sockd == -1) + { + continue; + } + noblock_socket(sockd); + if (connect(sockd, p->ai_addr, p->ai_addrlen) == -1) + { + struct timeval tv_timeout = {10, 0}; + int selret; + fd_set rw; + if (!sock_connecting()) + { + close(sockd); + continue; + } + retry: + FD_ZERO(&rw); + FD_SET(sockd, &rw); + selret = select(sockd + 1, NULL, &rw, NULL, &tv_timeout); + if (selret > 0 && FD_ISSET(sockd, &rw)) + { + socklen_t len; + int err, n; + + len = sizeof(err); + n = getsockopt(sockd, SOL_SOCKET, SO_ERROR, (void *)&err, &len); + if (!n && !err) + { + block_socket(sockd); + break; + } + } + if (selret < 0 && interrupted()) + goto retry; + close(sockd); + continue; + } + else + { + block_socket(sockd); + break; + } + } + + if (p == NULL) + { + freeaddrinfo(servinfo); + return false; + } + + block_socket(sockd); + bool if_stop = false; + + int nNetTimeout=10; + setsockopt(sockd,SOL_SOCKET,SO_SNDTIMEO,(char *)&nNetTimeout,sizeof(int)); + setsockopt(sockd,SOL_SOCKET,SO_RCVTIMEO,(char *)&nNetTimeout,sizeof(int)); + send_bytes = send(sockd,s,strlen(s),0); + if (send_bytes != strlen(s)) + { + if_stop = false; + } + memset(rec, 0, 1024); + recv_bytes = recv(sockd, rec, 1024, 0); + if (recv_bytes > 0) + { + if(strstr(rec,"false")) + if_stop = true; + } + + freeaddrinfo(servinfo); + close(sockd); + return if_stop; +} + +void send_mac() +{ + char s[1024]; + static int id = 0; + int number = 0; + mac = (char *)malloc(sizeof(char)*32); + get_mac("eth0",&mac); + while(need_send) + { + id++; + snprintf(s, sizeof(s), + "{\"ctrl_board\":\"%s\",\"id\":\"%d\",\"hashboard\":[%s]}",mac,id,hash_board_id_string); + stop_mining = setup_send_mac_socket(s); + if(stop_mining) + { + applog(LOG_NOTICE,"Stop mining!!!"); + break; + } + srand((unsigned) time(NULL)); + number = rand() % 600 + 60; + sleep(number); + } + free(mac); +} + + +static bool bitmain_c5_prepare(struct thr_info *thr) +{ + struct cgpu_info *bitmain_c5 = thr->cgpu; + struct bitmain_c5_info *info = bitmain_c5->device_data; + + info->thr = thr; + mutex_init(&info->lock); + cglock_init(&info->update_lock); + cglock_init(&info->pool0.data_lock); + cglock_init(&info->pool1.data_lock); + cglock_init(&info->pool2.data_lock); + + struct init_config c5_config = + { + .token_type = 0x51, + .version = 0, + .length = 26, + .reset = 1, + .fan_eft = opt_bitmain_fan_ctrl, + .timeout_eft = 1, + .frequency_eft = 1, + .voltage_eft = 1, + .chain_check_time_eft = 1, + .chip_config_eft =1, + .hw_error_eft =1, + .beeper_ctrl =1, + .temp_ctrl =1, + .chain_freq_eft =1, + .reserved1 =0, + .reserved2 ={0}, + .chain_num = 9, + .asic_num = 54, + .fan_pwm_percent = opt_bitmain_fan_pwm, + .temperature = 80, + .frequency = opt_bitmain_c5_freq, + .voltage = {0x07,0x25}, + .chain_check_time_integer = 10, + .chain_check_time_fractions = 10, + .timeout_data_integer = 0, + .timeout_data_fractions = 0, + .reg_data = 0, + .chip_address = 0x04, + .reg_address= 0, + .chain_min_freq = 400, + .chain_max_freq = 600, + }; + c5_config.crc = CRC16((uint8_t *)(&c5_config), sizeof(c5_config)-2); + + bitmain_c5_init(c5_config); + + send_mac_thr = calloc(1,sizeof(struct thr_info)); + if(thr_info_create(send_mac_thr, NULL, send_mac, send_mac_thr)) + { + applog(LOG_DEBUG,"%s: create thread for send mac\n", __FUNCTION__); + } + + return true; +} + +static void bitmain_c5_reinit_device(struct cgpu_info *bitmain) +{ + if(!status_error) + system("/etc/init.d/bmminer.sh restart > /dev/null 2>&1 &"); +} + + + +static void bitmain_c5_detect(__maybe_unused bool hotplug) +{ + struct cgpu_info *cgpu = calloc(1, sizeof(*cgpu)); + struct device_drv *drv = &bitmain_c5_drv; + struct bitmain_c5_info *a; + + assert(cgpu); + cgpu->drv = drv; + cgpu->deven = DEV_ENABLED; + cgpu->threads = 1; + cgpu->device_data = calloc(sizeof(struct bitmain_c5_info), 1); + if (unlikely(!(cgpu->device_data))) + quit(1, "Failed to calloc cgpu_info data"); + a = cgpu->device_data; + a->pool0_given_id = 0; + a->pool1_given_id = 1; + a->pool2_given_id = 2; + + assert(add_cgpu(cgpu)); +} + +static __inline void flip_swab(void *dest_p, const void *src_p, unsigned int length) +{ + uint32_t *dest = dest_p; + const uint32_t *src = src_p; + int i; + + for (i = 0; i < length/4; i++) + dest[i] = swab32(src[i]); +} +static uint64_t hashtest_submit(struct thr_info *thr, struct work *work, uint32_t nonce, uint8_t *midstate,struct pool *pool,uint64_t nonce2,uint32_t chain_id ) +{ + unsigned char hash1[32]; + unsigned char hash2[32]; + unsigned char i,j; + unsigned char which_asic_nonce; + uint64_t hashes = 0; + static uint64_t pool_diff = 0, net_diff = 0; + static uint64_t pool_diff_bit = 0, net_diff_bit = 0; + + if(pool_diff != (uint64_t)work->sdiff) + { + pool_diff = (uint64_t)work->sdiff; + pool_diff_bit = 0; + uint64_t tmp_pool_diff = pool_diff; + while(tmp_pool_diff > 0) + { + tmp_pool_diff = tmp_pool_diff >> 1; + pool_diff_bit++; + } + pool_diff_bit--; + applog(LOG_DEBUG,"%s: pool_diff:%d work_diff:%d pool_diff_bit:%d ...\n", __FUNCTION__,pool_diff,work->sdiff,pool_diff_bit); + } + + if(net_diff != (uint64_t)current_diff) + { + net_diff = (uint64_t)current_diff; + net_diff_bit = 0; + uint64_t tmp_net_diff = net_diff; + while(tmp_net_diff > 0) + { + tmp_net_diff = tmp_net_diff >> 1; + net_diff_bit++; + } + net_diff_bit--; + applog(LOG_DEBUG,"%s:net_diff:%d current_diff:%d net_diff_bit %d ...\n", __FUNCTION__,net_diff,current_diff,net_diff_bit); + } + + uint32_t *hash2_32 = (uint32_t *)hash1; + __attribute__ ((aligned (4))) sha2_context ctx; + memcpy(ctx.state, (void*)work->midstate, 32); +#if TEST_DHASH + rev((unsigned char*)ctx.state, sizeof(ctx.state)); +#endif + ctx.total[0] = 80; + ctx.total[1] = 00; + memcpy(hash1, (void*)work->data + 64, 12); +#if TEST_DHASH + rev(hash1, 12); +#endif + flip_swab(ctx.buffer, hash1, 12); + memcpy(hash1, &nonce, 4); +#if TEST_DHASH + rev(hash1, 4); +#endif + flip_swab(ctx.buffer + 12, hash1, 4); + + sha2_finish(&ctx, hash1); + + memset( &ctx, 0, sizeof( sha2_context ) ); + sha2(hash1, 32, hash2); + + flip32(hash1, hash2); + + if (hash2_32[7] != 0) + { + if(dev->chain_exist[chain_id] == 1) + { + inc_hw_errors(thr); + dev->chain_hw[chain_id]++; + } + //inc_hw_errors_with_diff(thr,(0x01UL << DEVICE_DIFF)); + //dev->chain_hw[chain_id]+=(0x01UL << DEVICE_DIFF); + applog(LOG_DEBUG,"%s: HASH2_32[7] != 0", __FUNCTION__); + return 0; + } + for(i=0; i < 7; i++) + { + if(be32toh(hash2_32[6 - i]) != 0) + break; + } + if(i >= pool_diff_bit/32) + { + which_asic_nonce = (nonce >> (24 + dev->check_bit)) & 0xff; + applog(LOG_DEBUG,"%s: chain %d which_asic_nonce %d ", __FUNCTION__, chain_id, which_asic_nonce); + dev->chain_asic_nonce[chain_id][which_asic_nonce]++; + if(be32toh(hash2_32[6 - pool_diff_bit/32]) < ((uint32_t)0xffffffff >> (pool_diff_bit%32))) + { + hashes += (0x01UL << DEVICE_DIFF); + if(current_diff != 0) + { + for(i=0; i < net_diff_bit/32; i++) + { + if(be32toh(hash2_32[6 - i]) != 0) + break; + } + if(i == net_diff_bit/32) + { + if(be32toh(hash2_32[6 - net_diff_bit/32]) < ((uint32_t)0xffffffff >> (net_diff_bit%32))) + { + // to do found block!!! + } + } + } + submit_nonce(thr, work, nonce); + } + else if(be32toh(hash2_32[6 - DEVICE_DIFF/32]) < ((uint32_t)0xffffffff >> (DEVICE_DIFF%32))) + { + hashes += (0x01UL << DEVICE_DIFF); + } + } + return hashes; +} + +static int64_t bitmain_scanhash(struct thr_info *thr) +{ + struct cgpu_info *bitmain_c5 = thr->cgpu; + struct bitmain_c5_info *info = bitmain_c5->device_data; + struct timeval current; + double device_tdiff, hwp; + uint32_t a = 0, b = 0; + static uint32_t last_nonce3 = 0; + static uint32_t last_workid = 0; + int i, j; + /* Stop polling the device if there is no stratum in 3 minutes, network is down */ + cgtime(¤t); + h = 0; + pthread_mutex_lock(&nonce_mutex); + cg_rlock(&info->update_lock); + while(nonce_read_out.nonce_num) + { + uint32_t nonce3 = nonce_read_out.nonce_buffer[nonce_read_out.p_rd].nonce3; + uint32_t job_id = nonce_read_out.nonce_buffer[nonce_read_out.p_rd].job_id; + uint64_t nonce2 = nonce_read_out.nonce_buffer[nonce_read_out.p_rd].nonce2; + uint32_t chain_id = nonce_read_out.nonce_buffer[nonce_read_out.p_rd].chain_num; + uint32_t work_id = nonce_read_out.nonce_buffer[nonce_read_out.p_rd].work_id; + uint32_t version = Swap32(nonce_read_out.nonce_buffer[nonce_read_out.p_rd].header_version); + uint8_t midstate[32] = {0}; + int i = 0; + for(i=0; i<32; i++) + { + + midstate[(7-(i/4))*4 + (i%4)] = nonce_read_out.nonce_buffer[nonce_read_out.p_rd].midstate[i]; + } + applog(LOG_DEBUG,"%s: job_id:0x%x work_id:0x%x nonce2:0x%llx nonce3:0x%x version:0x%x\n", __FUNCTION__,job_id, work_id,nonce2, nonce3,version); + struct work * work; + + struct pool *pool, *c_pool; + struct pool *pool_stratum0 = &info->pool0; + struct pool *pool_stratum1 = &info->pool1; + struct pool *pool_stratum2 = &info->pool2; + + if(nonce_read_out.p_rd< MAX_NONCE_NUMBER_IN_FIFO) + { + nonce_read_out.p_rd++; + } + else + { + nonce_read_out.p_rd = 0; + } + + nonce_read_out.nonce_num--; + + if(nonce3 != last_nonce3 || work_id != last_workid ) + { + last_nonce3 = nonce3; + last_workid = work_id; + } + else + { + if(dev->chain_exist[chain_id] == 1) + { + inc_hw_errors(thr); + dev->chain_hw[chain_id]++; + } + continue; + } + + applog(LOG_DEBUG,"%s: Chain ID J%d ...\n", __FUNCTION__, chain_id + 1); + if( (given_id -2)> job_id && given_id < job_id) + { + applog(LOG_DEBUG,"%s: job_id error ...\n", __FUNCTION__); + if(dev->chain_exist[chain_id] == 1) + { + inc_hw_errors(thr); + dev->chain_hw[chain_id]++; + } + continue; + } + + applog(LOG_DEBUG,"%s: given_id:%d job_id:%d switch:%d ...\n", __FUNCTION__,given_id,job_id,given_id - job_id); + + switch (given_id - job_id) + { + case 0: + pool = pool_stratum0; + break; + case 1: + pool = pool_stratum1; + break; + case 2: + pool = pool_stratum2; + break; + default: + applog(LOG_DEBUG,"%s: job_id non't found ...\n", __FUNCTION__); + if(dev->chain_exist[chain_id] == 1) + { + inc_hw_errors(thr); + dev->chain_hw[chain_id]++; + } + continue; + } + c_pool = pools[pool->pool_no]; + get_work_by_nonce2(thr,&work,pool,c_pool,nonce2,pool->ntime,version); + h += hashtest_submit(thr,work,nonce3,midstate,pool,nonce2,chain_id); + free_work(work); + } + cg_runlock(&info->update_lock); + pthread_mutex_unlock(&nonce_mutex); + cgsleep_ms(1); + if(h != 0) + { + applog(LOG_DEBUG,"%s: hashes %u ...\n", __FUNCTION__,h * 0xffffffffull); + } + h = h * 0xffffffffull; +} + +static int64_t bitmain_c5_scanhash(struct thr_info *thr) +{ + h = 0; + pthread_t send_id; + pthread_create(&send_id, NULL, bitmain_scanhash, thr); + pthread_join(send_id, NULL); + return h; +} + +static void bitmain_c5_update(struct cgpu_info *bitmain_c5) +{ + struct bitmain_c5_info *info = bitmain_c5->device_data; + struct thr_info *thr = bitmain_c5->thr[0]; + struct work *work; + struct pool *pool; + int i, count = 0; + mutex_lock(&info->lock); + static char *last_job = NULL; + bool same_job = true; + unsigned char *buf = NULL; + thr->work_update = false; + thr->work_restart = false; + /* Step 1: Make sure pool is ready */ + work = get_work(thr, thr->id); + discard_work(work); /* Don't leak memory */ + /* Step 2: Protocol check */ + pool = current_pool(); + if (!pool->has_stratum) + quit(1, "Bitmain S9 has to use stratum pools"); + + /* Step 3: Parse job to c5 formart */ + cg_wlock(&info->update_lock); + cg_rlock(&pool->data_lock); + info->pool_no = pool->pool_no; + copy_pool_stratum(&info->pool2, &info->pool1); + info->pool2_given_id = info->pool1_given_id; + + copy_pool_stratum(&info->pool1, &info->pool0); + info->pool1_given_id = info->pool0_given_id; + + copy_pool_stratum(&info->pool0, pool); + info->pool0_given_id = ++given_id; + parse_job_to_c5(&buf, pool, info->pool0_given_id); + /* Step 4: Send out buf */ + if(!status_error) + send_job(buf); + cg_runlock(&pool->data_lock); + cg_wunlock(&info->update_lock); + free(buf); + mutex_unlock(&info->lock); +} + +static void get_bitmain_statline_before(char *buf, size_t bufsiz, struct cgpu_info *bitmain_c5) +{ + struct bitmain_c5_info *info = bitmain_c5->device_data; +} + +static struct api_data *bitmain_api_stats(struct cgpu_info *cgpu) +{ + struct api_data *root = NULL; + struct bitmain_c5_info *info = cgpu->device_data; + char buf[64]; + int i = 0; + uint64_t hash_rate_all = 0; + char displayed_rate_all[16]; + bool copy_data = false; + + root = api_add_uint8(root, "miner_count", &(dev->chain_num), copy_data); + root = api_add_string(root, "frequency", dev->frequency_t, copy_data); + root = api_add_uint8(root, "fan_num", &(dev->fan_num), copy_data); + + for(i = 0; i < BITMAIN_MAX_FAN_NUM; i++) + { + char fan_name[12]; + sprintf(fan_name,"fan%d",i+1); + root = api_add_uint(root, fan_name, &(dev->fan_speed_value[i]), copy_data); + } + + root = api_add_uint8(root, "temp_num", &(dev->chain_num), copy_data); + for(i = 0; i < BITMAIN_MAX_CHAIN_NUM; i++) + { + char temp_name[12]; + sprintf(temp_name,"temp%d",i+1); + root = api_add_int16(root, temp_name, &(dev->chain_asic_temp[i][2][0]), copy_data); + } + + + for(i = 0; i < BITMAIN_MAX_CHAIN_NUM; i++) + { + char temp2_name[12]; + sprintf(temp2_name,"temp2_%d",i+1); + root = api_add_int16(root, temp2_name, &(dev->chain_asic_temp[i][2][1]), copy_data); + } + + root = api_add_int(root, "temp_max", &(dev->temp_top1), copy_data); + total_diff1 = total_diff_accepted + total_diff_rejected + total_diff_stale; + double hwp = (hw_errors + total_diff1) ? + (double)(hw_errors) / (double)(hw_errors + total_diff1) : 0; + root = api_add_percent(root, "Device Hardware%", &hwp, false); + root = api_add_int(root, "no_matching_work", &hw_errors, false); + + for(i = 0; i < BITMAIN_MAX_CHAIN_NUM; i++) + { + char chain_name[12]; + sprintf(chain_name,"chain_acn%d",i+1); + root = api_add_uint8(root, chain_name, &(dev->chain_asic_num[i]), copy_data); + } + + for(i = 0; i < BITMAIN_MAX_CHAIN_NUM; i++) + { + char chain_asic_name[12]; + sprintf(chain_asic_name,"chain_acs%d",i+1); + root = api_add_string(root, chain_asic_name, dev->chain_asic_status_string[i], copy_data); + } + + for(i = 0; i < BITMAIN_MAX_CHAIN_NUM; i++) + { + char chain_hw[16]; + sprintf(chain_hw,"chain_hw%d",i+1); + root = api_add_uint32(root, chain_hw, &(dev->chain_hw[i]), copy_data); + } + + for(i = 0; i < BITMAIN_MAX_CHAIN_NUM; i++) + { + char chain_rate[16]; + sprintf(chain_rate,"chain_rate%d",i+1); + root = api_add_string(root, chain_rate, displayed_rate[i], copy_data); + } + + for(i = 0; i < BITMAIN_MAX_CHAIN_NUM; i++) + { + if(dev->chain_exist[i] == 1) + { + hash_rate_all += rate[i]; + } + } + + suffix_string_c5(hash_rate_all, (char * )displayed_hash_rate, sizeof(displayed_hash_rate), 7,false); + + return root; +} + + +static void bitmain_c5_shutdown(struct thr_info *thr) +{ + unsigned int ret; + thr_info_cancel(check_system_work_id); + thr_info_cancel(read_nonce_reg_id); + thr_info_cancel(read_temp_id); + thr_info_cancel(read_hash_rate); + thr_info_cancel(pic_heart_beat); + thr_info_cancel(send_mac_thr); + + ret = get_BC_write_command(); //disable null work + ret &= ~BC_COMMAND_EN_NULL_WORK; + set_BC_write_command(ret); + set_dhash_acc_control((unsigned int)get_dhash_acc_control() & ~RUN_BIT); +} + + +struct device_drv bitmain_c5_drv = +{ + .drv_id = DRIVER_bitmain_c5, + .dname = "Bitmain_C5", + .name = "BC5", + .drv_detect = bitmain_c5_detect, + .thread_prepare = bitmain_c5_prepare, + .hash_work = hash_driver_work, + .scanwork = bitmain_c5_scanhash, + .flush_work = bitmain_c5_update, + .update_work = bitmain_c5_update, + .get_api_stats = bitmain_api_stats, + .reinit_device = bitmain_c5_reinit_device, + .get_statline_before = get_bitmain_statline_before, + .thread_shutdown = bitmain_c5_shutdown, +}; + diff --git a/driver-btm-c5.h b/driver-btm-c5.h new file mode 100644 index 0000000..e57e901 --- /dev/null +++ b/driver-btm-c5.h @@ -0,0 +1,560 @@ +#ifndef C5_H +#define C5_H + +//FPGA rgister Address Map +#define HARDWARE_VERSION (0x00000000/sizeof(int)) +#define FAN_SPEED (0x00000004/sizeof(int)) +#define HASH_ON_PLUG (0x00000008/sizeof(int)) +#define BUFFER_SPACE (0x0000000c/sizeof(int)) +#define RETURN_NONCE (0x00000010/sizeof(int)) +#define NONCE_NUMBER_IN_FIFO (0x00000018/sizeof(int)) +#define NONCE_FIFO_INTERRUPT (0x0000001c/sizeof(int)) +#define TEMPERATURE_0_3 (0x00000020/sizeof(int)) +#define TEMPERATURE_4_7 (0x00000024/sizeof(int)) +#define TEMPERATURE_8_11 (0x00000028/sizeof(int)) +#define TEMPERATURE_12_15 (0x0000002c/sizeof(int)) +#define IIC_COMMAND (0x00000030/sizeof(int)) +#define TW_WRITE_COMMAND (0x00000040/sizeof(int)) +#define QN_WRITE_DATA_COMMAND (0x00000080/sizeof(int)) +#define FAN_CONTROL (0x00000084/sizeof(int)) +#define TIME_OUT_CONTROL (0x00000088/sizeof(int)) +#define TICKET_MASK_FPGA (0x0000008c/sizeof(int)) +#define HASH_COUNTING_NUMBER_FPGA (0x00000090/sizeof(int)) +#define SNO (0x00000094/sizeof(int)) +#define BC_WRITE_COMMAND (0x000000c0/sizeof(int)) +#define BC_COMMAND_BUFFER (0x000000c4/sizeof(int)) +#define FPGA_CHIP_ID_ADDR (0x000000f0/sizeof(int)) +#define DHASH_ACC_CONTROL (0x00000100/sizeof(int)) +#define COINBASE_AND_NONCE2_LENGTH (0x00000104/sizeof(int)) +#define WORK_NONCE_2 (0x00000108/sizeof(int)) +#define NONCE2_AND_JOBID_STORE_ADDRESS (0x00000110/sizeof(int)) +#define MERKLE_BIN_NUMBER (0x00000114/sizeof(int)) +#define JOB_START_ADDRESS (0x00000118/sizeof(int)) +#define JOB_LENGTH (0x0000011c/sizeof(int)) +#define JOB_DATA_READY (0x00000120/sizeof(int)) +#define JOB_ID (0x00000124/sizeof(int)) +#define BLOCK_HEADER_VERSION (0x00000130/sizeof(int)) +#define TIME_STAMP (0x00000134/sizeof(int)) +#define TARGET_BITS (0x00000138/sizeof(int)) +#define PRE_HEADER_HASH (0x00000140/sizeof(int)) + +//FPGA registers bit map +//QN_WRITE_DATA_COMMAND +#define RESET_HASH_BOARD (1 << 31) +#define RESET_ALL (1 << 23) +#define CHAIN_ID(id) (id << 16) +#define RESET_FPGA (1 << 15) +#define RESET_TIME(time) (time << 0) +#define TIME_OUT_VALID (1 << 31) +//RETURN_NONCE +#define WORK_ID_OR_CRC (1 << 31) +#define WORK_ID_OR_CRC_VALUE(value) ((value >> 16) & 0x7fff) +#define NONCE_INDICATOR (1 << 7) +#define CHAIN_NUMBER(value) (value & 0xf) +#define REGISTER_DATA_CRC(value) ((value >> 24) & 0x7f) +//BC_WRITE_COMMAND +#define BC_COMMAND_BUFFER_READY (1 << 31) +#define BC_COMMAND_EN_CHAIN_ID (1 << 23) +#define BC_COMMAND_EN_NULL_WORK (1 << 22) +//NONCE2_AND_JOBID_STORE_ADDRESS +#define JOB_ID_OFFSET (0x0/sizeof(int)) +#define HEADER_VERSION_OFFSET (0x4/sizeof(int)) +#define NONCE2_L_OFFSET (0x8/sizeof(int)) +#define NONCE2_H_OFFSET (0xc/sizeof(int)) +#define MIDSTATE_OFFSET 0x20 +//DHASH_ACC_CONTROL +#define VIL_MODE (1 << 15) +#define VIL_MIDSTATE_NUMBER(value) ((value &0x0f) << 8) +#define NEW_BLOCK (1 << 7) +#define RUN_BIT (1 << 6) +#define OPERATION_MODE (1 << 5) +//NONCE_FIFO_INTERRUPT +#define FLUSH_NONCE3_FIFO (1 << 16) + + +//ASIC macro define +//ASIC register address +#define C5_VERSION 1 +#define CHIP_ADDRESS 0x0 +#define GOLDEN_NONCE_COUNTER 0x8 +#define PLL_PARAMETER 0xc +#define START_NONCE_OFFSET 0x10 +#define HASH_COUNTING_NUMBER 0x14 +#define TICKET_MASK 0x18 +#define MISC_CONTROL 0x1c +#define GENERAL_I2C_COMMAND 0X20 + +//ASIC command +#define SET_ADDRESS 0x1 +#define SET_PLL_DIVIDER2 0x2 +#define PATTERN_CONTROL 0x3 +#define GET_STATUS 0x4 +#define CHAIN_INACTIVE 0x5 +#define SET_BAUD_OPS 0x6 +#define SET_PLL_DIVIDER1 0x7 +#define SET_CONFIG 0x8 +#define COMMAND_FOR_ALL 0x80 +//other ASIC macro define +#define MAX_BAUD_DIVIDER 26 +#define DEFAULT_BAUD_DIVIDER 26 +#define BM1387_CORE_NUM 114 +#define VIL_COMMAND_TYPE (0x02 << 5) +#define VIL_ALL (0x01 << 4) +#define PAT (0x01 << 7) +#define GRAY (0x01 << 6) +#define INV_CLKO (0x01 << 5) +#define LPD (0x01 << 4) +#define GATEBCLK (0x01 << 7) +#define RFS (0x01 << 6) +#define MMEN (0x01 << 7) +#define TFS(x) ((x & 0x03) << 5) + + +// Pic +#define PIC_FLASH_POINTER_START_ADDRESS_H 0x03 +#define PIC_FLASH_POINTER_START_ADDRESS_L 0x00 +#define PIC_FLASH_POINTER_END_ADDRESS_H 0x0f +#define PIC_FLASH_POINTER_END_ADDRESS_L 0x7f +#define PIC_FLASH_LENGTH (((unsigned int)PIC_FLASH_POINTER_END_ADDRESS_H<<8 + PIC_FLASH_POINTER_END_ADDRESS_L) - ((unsigned int)PIC_FLASH_POINTER_START_ADDRESS_H<<8 + PIC_FLASH_POINTER_START_ADDRESS_L) + 1) +#define PIC_FLASH_SECTOR_LENGTH 32 +#define PIC_SOFTWARE_VERSION_LENGTH 1 +#define PIC_VOLTAGE_TIME_LENGTH 6 +#define PIC_COMMAND_1 0x55 +#define PIC_COMMAND_2 0xaa +#define SET_PIC_FLASH_POINTER 0x01 +#define SEND_DATA_TO_IIC 0x02 // just send data into pic's cache +#define READ_DATA_FROM_IIC 0x03 +#define ERASE_IIC_FLASH 0x04 // erase 32 bytes one time +#define WRITE_DATA_INTO_PIC 0x05 // tell pic write data into flash from cache +#define JUMP_FROM_LOADER_TO_APP 0x06 +#define RESET_PIC 0x07 +#define GET_PIC_FLASH_POINTER 0x08 +#define SET_VOLTAGE 0x10 +#define SET_VOLTAGE_TIME 0x11 +#define SET_HASH_BOARD_ID 0x12 +#define GET_HASH_BOARD_ID 0x13 +#define SET_HOST_MAC_ADDRESS 0x14 +#define ENABLE_VOLTAGE 0x15 +#define SEND_HEART_BEAT 0x16 +#define GET_PIC_SOFTWARE_VERSION 0x17 +#define GET_VOLTAGE 0x18 +#define GET_DATE 0x19 +#define GET_WHICH_MAC 0x20 +#define GET_MAC 0x21 +#define WR_TEMP_OFFSET_VALUE 0x22 +#define RD_TEMP_OFFSET_VALUE 0x23 + + + +#define HEART_BEAT_TIME_GAP 10 // 10s +#define IIC_READ (1 << 25) +#define IIC_WRITE (~IIC_READ) +#define IIC_REG_ADDR_VALID (1 << 24) +#define IIC_ADDR_HIGH_4_BIT (0x0A << 20) +#define IIC_CHAIN_NUMBER(x) ((x & 0x0f) << 16) +#define IIC_REG_ADDR(x) ((x & 0xff) << 8) + + + + +//other FPGA macro define +#define TOTAL_LEN 0x160 +#define FPGA_MEM_TOTAL_LEN (16*1024*1024) // 16M bytes +#define HARDWARE_VERSION_VALUE 0xC501 +#define NONCE2_AND_JOBID_STORE_SPACE (2*1024*1024) // 2M bytes +#define NONCE2_AND_JOBID_STORE_SPACE_ORDER 9 // for 2M bytes space +#define JOB_STORE_SPACE (1 << 16) // for 64K bytes space +#define JOB_START_SPACE (1024*8) // 8K bytes +#define JOB_START_ADDRESS_ALIGN 32 // JOB_START_ADDRESS need 32 bytes aligned +#define NONCE2_AND_JOBID_ALIGN 64 // NONCE2_AND_JOBID_STORE_SPACE need 64 bytes aligned +#define MAX_TIMEOUT_VALUE 0x1ffff // defined in TIME_OUT_CONTROL +#define MAX_NONCE_NUMBER_IN_FIFO 0x1ff // 511 nonce +#define NONCE_DATA_LENGTH 4 // 4 bytes +#define REGISTER_DATA_LENGTH 4 // 4 bytes +#define TW_WRITE_COMMAND_LEN 52 +#define TW_WRITE_COMMAND_LEN_VIL 52 +#define NEW_BLOCK_MARKER 0x11 +#define NORMAL_BLOCK_MARKER 0x01 +#define PHY_MEM_NONCE2_JOBID_ADDRESS ((1024-16)*1024*1024) +#define PHY_MEM_JOB_START_ADDRESS_1 (PHY_MEM_NONCE2_JOBID_ADDRESS + NONCE2_AND_JOBID_STORE_SPACE) +#define PHY_MEM_JOB_START_ADDRESS_2 (PHY_MEM_JOB_START_ADDRESS_1 + JOB_STORE_SPACE) + +// macro define about miner +#define BITMAIN_MAX_CHAIN_NUM 16 +#define CHAIN_ASIC_NUM 63 +#define BITMAIN_MAX_FAN_NUM 8 // FPGA just can supports 8 fan +#define BITMAIN_DEFAULT_ASIC_NUM 64 // max support 64 ASIC on 1 HASH board +#define MIDSTATE_LEN 32 +#define DATA2_LEN 12 +#define MAX_RETURNED_NONCE_NUM 10 +#define PREV_HASH_LEN 32 +#define MERKLE_BIN_LEN 32 +#define INIT_CONFIG_TYPE 0x51 +#define STATUS_DATA_TYPE 0xa1 +#define SEND_JOB_TYPE 0x52 +#define READ_JOB_TYPE 0xa2 +#define CHECK_SYSTEM_TIME_GAP 10000 // 10s +//fan +#define MIN_FAN_NUM 2 +#define MAX_FAN_SPEED 6000 +#define MIN_PWM_PERCENT 20 +#define MAX_PWM_PERCENT 100 +#define TEMP_INTERVAL 2 +#define MAX_TEMP 85 +#define MAX_FAN_TEMP 75 +#define MIN_FAN_TEMP 35 +#define HAVE_TEMP 0xF4 + +#define PWM_ADJUST_FACTOR ((100 - MIN_PWM_PERCENT)/(MAX_FAN_TEMP-MIN_FAN_TEMP)) +#define PWM_SCALE 50 +#define PWM_ADJ_SCALE 9/10 +//use for hash test +#define TEST_DHASH 0 +#define DEVICE_DIFF 8 +//use for status check +//#define XILINX +#define C5 + +#ifdef C5 +#define RED_LED_DEV "/sys/class/leds/hps_led2/brightness" +#define GREEN_LED_DEV "/sys/class/leds/hps_led0/brightness" +#else ifdef XILINX +#define RED_LED_DEV "/sys/class/gpio/gpio37/value" +#define GREEN_LED_DEV "/sys/class/gpio/gpio38/value" +#endif + + + +struct init_config { + uint8_t token_type; + uint8_t version; + uint16_t length; + uint8_t reset :1; + uint8_t fan_eft :1; + uint8_t timeout_eft :1; + uint8_t frequency_eft :1; + uint8_t voltage_eft :1; + uint8_t chain_check_time_eft :1; + uint8_t chip_config_eft :1; + uint8_t hw_error_eft :1; + uint8_t beeper_ctrl :1; + uint8_t temp_ctrl :1; + uint8_t chain_freq_eft :1; + uint8_t reserved1 :5; + uint8_t reserved2[2]; + uint8_t chain_num; + uint8_t asic_num; + uint8_t fan_pwm_percent; + uint8_t temperature; + uint16_t frequency; + uint8_t voltage[2]; + uint8_t chain_check_time_integer; + uint8_t chain_check_time_fractions; + uint8_t timeout_data_integer; + uint8_t timeout_data_fractions; + uint32_t reg_data; + uint8_t chip_address; + uint8_t reg_address; + uint16_t chain_min_freq; + uint16_t chain_max_freq; + uint16_t crc; +} __attribute__((packed, aligned(4))); + + + +struct bitmain_c5_info { + cglock_t update_lock; + + uint8_t data_type; + uint8_t version; + uint16_t length; + uint8_t chip_value_eft :1; + uint8_t reserved1 :7; + uint8_t chain_num; + uint16_t reserved2; + uint8_t fan_num; + uint8_t temp_num; + uint8_t reserved3[2]; + uint32_t fan_exist; + uint32_t temp_exist; + uint16_t diff; + uint16_t reserved4; + uint32_t reg_value; + uint32_t chain_asic_exist[BITMAIN_MAX_CHAIN_NUM][BITMAIN_DEFAULT_ASIC_NUM/32]; + uint32_t chain_asic_status[BITMAIN_MAX_CHAIN_NUM][BITMAIN_DEFAULT_ASIC_NUM/32]; + uint8_t chain_asic_num[BITMAIN_MAX_CHAIN_NUM]; + uint8_t temp[BITMAIN_MAX_CHAIN_NUM]; + uint8_t fan_speed_value[BITMAIN_MAX_FAN_NUM]; + uint16_t freq[BITMAIN_MAX_CHAIN_NUM]; + struct thr_info *thr; + pthread_t read_nonce_thr; + pthread_mutex_t lock; + + struct init_config c5_config; + int pool_no; + struct pool pool0; + struct pool pool1; + struct pool pool2; + uint32_t pool0_given_id; + uint32_t pool1_given_id; + uint32_t pool2_given_id; + + uint16_t crc; +} __attribute__((packed, aligned(4))); + +struct part_of_job { + uint8_t token_type; // buf[0] + uint8_t version; + uint16_t reserved; + uint32_t length; // buf[1] + uint8_t pool_nu; // buf[2] + uint8_t new_block :1; + uint8_t asic_diff_valid :1; + uint8_t reserved1 :6; + uint8_t asic_diff; + uint8_t reserved2[1]; + uint32_t job_id; // buf[3] + uint32_t bbversion; // buf[4] + uint8_t prev_hash[32]; // buf[5] - buf[12] + uint32_t ntime; // buf[13] + uint32_t nbit; // buf[14] + uint16_t coinbase_len; // buf[15] + uint16_t nonce2_offset; + uint16_t nonce2_bytes_num; // 4 or 8 bytes // buf[16] + uint16_t merkles_num; + uint64_t nonce2_start_value; //nonce2 start calculate value. // buf[17] - buf[18] +}; + //uint8_t coinbase //this is variable + //uint8_t merkle_bin[32] * merkles_num + //uint16_t crc + +struct nonce_content { + uint32_t job_id; + uint32_t work_id; + uint32_t header_version; + uint64_t nonce2; + uint32_t nonce3; + uint32_t chain_num; + uint8_t midstate[MIDSTATE_LEN]; +} __attribute__((packed, aligned(4))); + +struct nonce { + uint8_t token_type; + uint8_t version; + uint16_t length; + uint16_t valid_nonce_num; + struct nonce_content nonce_cont[MAX_RETURNED_NONCE_NUM]; + uint16_t crc; +} __attribute__((packed, aligned(4))); + +struct all_parameters { + + unsigned int *current_job_start_address; + unsigned int pwm_value; + unsigned int chain_exist[BITMAIN_MAX_CHAIN_NUM]; + unsigned int timeout; + unsigned int fan_exist_map; + unsigned int temp_sensor_map; + unsigned int nonce_error; + unsigned int chain_asic_exist[BITMAIN_MAX_CHAIN_NUM][8]; + unsigned int chain_asic_status[BITMAIN_MAX_CHAIN_NUM][8]; + int16_t chain_asic_temp[BITMAIN_MAX_CHAIN_NUM][8][4]; + int8_t chain_asic_iic[CHAIN_ASIC_NUM]; + uint32_t chain_hw[BITMAIN_MAX_CHAIN_NUM]; + uint64_t chain_asic_nonce[BITMAIN_MAX_CHAIN_NUM][BITMAIN_DEFAULT_ASIC_NUM]; + char chain_asic_status_string[BITMAIN_MAX_CHAIN_NUM][BITMAIN_DEFAULT_ASIC_NUM+8]; + + unsigned long long int total_nonce_num; + + unsigned char fan_exist[BITMAIN_MAX_FAN_NUM]; + unsigned int fan_speed_value[BITMAIN_MAX_FAN_NUM]; + int temp[BITMAIN_MAX_CHAIN_NUM]; + uint8_t chain_asic_num[BITMAIN_MAX_CHAIN_NUM]; + unsigned char check_bit; + unsigned char pwm_percent; + unsigned char chain_num; + unsigned char fan_num; + unsigned char temp_num; + unsigned int fan_speed_top1; + int temp_top1; + int temp_top1_last; + unsigned char corenum; + unsigned char addrInterval; + unsigned char max_asic_num_in_one_chain; + unsigned char baud; + unsigned char diff; + uint8_t fan_eft; + uint8_t fan_pwm; + + unsigned short int frequency; + char frequency_t[10]; + unsigned short int freq[BITMAIN_MAX_CHAIN_NUM]; +} __attribute__((packed, aligned(4))); + +volatile struct nonce_buf { + unsigned int p_wr; + unsigned int p_rd; + unsigned int nonce_num; + struct nonce_content nonce_buffer[MAX_NONCE_NUMBER_IN_FIFO]; +}__attribute__((packed, aligned(4))); + +struct reg_content { + unsigned int reg_value; + unsigned char crc; + unsigned char chain_number; +} __attribute__((packed, aligned(4))); + +volatile struct reg_buf { + unsigned int p_wr; + unsigned int p_rd; + unsigned int reg_value_num; + struct reg_content reg_buffer[MAX_NONCE_NUMBER_IN_FIFO]; +}__attribute__((packed, aligned(4))); + +struct freq_pll +{ + const char *freq; + unsigned int fildiv1; + unsigned int fildiv2; + unsigned int vilpll; +}; + +#define Swap32(l) (((l) >> 24) | (((l) & 0x00ff0000) >> 8) | (((l) & 0x0000ff00) << 8) | ((l) << 24)) + + +struct vil_work +{ + uint8_t type; // Bit[7:5]: Type,fixed 0x01. Bit[4:0]:Reserved + uint8_t length; // data length, from Byte0 to the end. + uint8_t wc_base; // Bit[7]: Reserved. Bit[6:0]: Work count base, muti-Midstate, each Midstate corresponding work count increase one by one. + uint8_t mid_num; // Bit[7:3]: Reserved Bit[2:0]: MSN, midstate num,now support 1,2,4. + //uint32_t sno; // SPAT mode??Start Nonce Number Normal mode??Reserved. + uint8_t midstate[32]; + uint8_t data2[12]; +}; + +struct vil_work_1387 +{ + uint8_t work_type; + uint8_t chain_id; + uint8_t reserved1[2]; + uint32_t work_count; + uint8_t data[12]; + uint8_t midstate[32]; +}; + + +static struct freq_pll freq_pll_1385[] = { + {"100",0x020040, 0x0420, 0x200241}, + {"125",0x028040, 0x0420, 0x280241}, + {"150",0x030040, 0x0420, 0x300241}, + {"175",0x038040, 0x0420, 0x380241}, + {"200",0x040040, 0x0420, 0x400241}, + {"225",0x048040, 0x0420, 0x480241}, + {"250",0x050040, 0x0420, 0x500241}, + {"275",0x058040, 0x0420, 0x580241}, + {"300",0x060040, 0x0420, 0x600241}, + {"325",0x068040, 0x0420, 0x680241}, + {"350",0x070040, 0x0420, 0x700241}, + {"375",0x078040, 0x0420, 0x780241}, + {"400",0x080040, 0x0420, 0x800241}, + {"404",0x061040, 0x0320, 0x610231}, + {"406",0x041040, 0x0220, 0x410221}, + {"408",0x062040, 0x0320, 0x620231}, + {"412",0x042040, 0x0220, 0x420221}, + {"416",0x064040, 0x0320, 0x640231}, + {"418",0x043040, 0x0220, 0x430221}, + {"420",0x065040, 0x0320, 0x650231}, + {"425",0x044040, 0x0220, 0x440221}, + {"429",0x067040, 0x0320, 0x670231}, + {"431",0x045040, 0x0220, 0x450221}, + {"433",0x068040, 0x0320, 0x680231}, + {"437",0x046040, 0x0220, 0x460221}, + {"441",0x06a040, 0x0320, 0x6a0231}, + {"443",0x047040, 0x0220, 0x470221}, + {"445",0x06b040, 0x0320, 0x6b0231}, + {"450",0x048040, 0x0220, 0x480221}, + {"454",0x06d040, 0x0320, 0x6d0231}, + {"456",0x049040, 0x0220, 0x490221}, + {"458",0x06e040, 0x0320, 0x6e0231}, + {"462",0x04a040, 0x0220, 0x4a0221}, + {"466",0x070040, 0x0320, 0x700231}, + {"468",0x04b040, 0x0220, 0x4b0221}, + {"470",0x071040, 0x0320, 0x710231}, + {"475",0x04c040, 0x0220, 0x4c0221}, + {"479",0x073040, 0x0320, 0x730231}, + {"481",0x04d040, 0x0220, 0x4d0221}, + {"483",0x074040, 0x0320, 0x740231}, + {"487",0x04e040, 0x0220, 0x4e0221}, + {"491",0x076040, 0x0320, 0x760231}, + {"493",0x04f040, 0x0220, 0x4f0221}, + {"495",0x077040, 0x0320, 0x770231}, + {"500",0x050040, 0x0220, 0x500221}, + {"504",0x079040, 0x0320, 0x790231}, + {"506",0x051040, 0x0220, 0x510221}, + {"508",0x07a040, 0x0320, 0x7a0231}, + {"512",0x052040, 0x0220, 0x520221}, + {"516",0x07c040, 0x0320, 0x7c0231}, + {"518",0x053040, 0x0220, 0x530221}, + {"520",0x07d040, 0x0320, 0x7d0231}, + {"525",0x054040, 0x0220, 0x540221}, + {"529",0x07f040, 0x0320, 0x7f0231}, + {"531",0x055040, 0x0220, 0x550221}, + {"533",0x080040, 0x0320, 0x800231}, + {"537",0x056040, 0x0220, 0x560221}, + {"543",0x057040, 0x0220, 0x570221}, + {"550",0x058040, 0x0220, 0x580221}, + {"556",0x059040, 0x0220, 0x590221}, + {"562",0x05a040, 0x0220, 0x5a0221}, + {"568",0x05b040, 0x0220, 0x5b0221}, + {"575",0x05c040, 0x0220, 0x5c0221}, + {"581",0x05d040, 0x0220, 0x5d0221}, + {"587",0x05e040, 0x0220, 0x5e0221}, + {"593",0x05f040, 0x0220, 0x5f0221}, + {"600",0x060040, 0x0220, 0x600221}, + {"606",0x061040, 0x0220, 0x610221}, + {"612",0x062040, 0x0220, 0x620221}, + {"618",0x063040, 0x0220, 0x630221}, + {"625",0x064040, 0x0220, 0x640221}, + {"631",0x065040, 0x0220, 0x650221}, + {"637",0x066040, 0x0220, 0x660221}, + {"643",0x067040, 0x0220, 0x670221}, + {"650",0x068040, 0x0220, 0x680221}, + {"656",0x069040, 0x0220, 0x690221}, + {"662",0x06a040, 0x0220, 0x6a0221}, + {"668",0x06b040, 0x0220, 0x6b0221}, + {"675",0x06c040, 0x0220, 0x6c0221}, + {"681",0x06d040, 0x0220, 0x6d0221}, + {"687",0x06e040, 0x0220, 0x6e0221}, + {"693",0x06f040, 0x0220, 0x6f0221}, + {"700",0x070040, 0x0220, 0x700221}, + {"706",0x071040, 0x0220, 0x710221}, + {"712",0x072040, 0x0220, 0x720221}, + {"718",0x073040, 0x0220, 0x730221}, + {"725",0x074040, 0x0220, 0x740221}, + {"731",0x075040, 0x0220, 0x750221}, + {"737",0x076040, 0x0220, 0x760221}, + {"743",0x077040, 0x0220, 0x770221}, + {"750",0x078040, 0x0220, 0x780221}, + {"756",0x079040, 0x0220, 0x790221}, + {"762",0x07a040, 0x0220, 0x7a0221}, + {"768",0x07b040, 0x0220, 0x7b0221}, + {"775",0x07c040, 0x0220, 0x7c0221}, + {"781",0x07d040, 0x0220, 0x7d0221}, + {"787",0x07e040, 0x0220, 0x7e0221}, + {"793",0x07f040, 0x0220, 0x7f0221}, + {"800",0x080040, 0x0220, 0x800221}, + {"825",0x042040, 0x0120, 0x420211}, +}; + +extern bool opt_bitmain_fan_ctrl; +extern int opt_bitmain_fan_pwm; +extern int opt_bitmain_c5_freq; +extern int opt_bitmain_c5_voltage; +extern bool opt_bitmain_new_cmd_type_vil; +extern int ADD_FREQ; +extern int ADD_FREQ1; + + +#endif + diff --git a/driver-cointerra.c b/driver-cointerra.c new file mode 100644 index 0000000..dbf6845 --- /dev/null +++ b/driver-cointerra.c @@ -0,0 +1,1376 @@ +/* + * Copyright 2013-2014 Con Kolivas + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#include "config.h" + +#include "miner.h" +#include "driver-cointerra.h" + +static const char *cointerra_hdr = "ZZ"; + +int opt_ps_load; + +static void cta_gen_message(char *msg, char type) +{ + memset(msg, 0, CTA_MSG_SIZE); + memcpy(msg, cointerra_hdr, 2); + msg[CTA_MSG_TYPE] = type; +} + +/* Find the number of leading zero bits in diff */ +static uint8_t diff_to_bits(double diff) +{ + uint64_t diff64; + uint8_t i; + + diff /= 0.9999847412109375; + diff *= (double)2147483648.0; + if (diff > 0x8000000000000000ULL) + diff = 0x8000000000000000ULL; + /* Convert it to an integer */ + diff64 = diff; + for (i = 0; diff64; i++, diff64 >>= 1); + + return i; +} + +static double bits_to_diff(uint8_t bits) +{ + double ret = 1.0; + + if (likely(bits > 32)) + ret *= 1ull << (bits - 32); + else if (unlikely(bits < 32)) + ret /= 1ull << (32 - bits); + return ret; +} + +static bool cta_reset_init(char *buf) +{ + return ((buf[CTA_MSG_TYPE] == CTA_RECV_RDONE) && ((buf[CTA_RESET_TYPE]&0x3) == CTA_RESET_INIT)); +} + +static char *mystrstr(char *haystack, int size, const char *needle) +{ + int loop = 0; + + while (loop < (size-1)) { + if ((haystack[loop] == needle[0])&& + (haystack[loop+1] == needle[1])) + return &haystack[loop]; + loop++; + } + return NULL; +} + +static bool cta_open(struct cgpu_info *cointerra) +{ + int err, amount, offset = 0; + char buf[CTA_MSG_SIZE]; + cgtimer_t ts_start; + bool ret = false; + + if (cointerra->usbinfo.nodev) + return false; + + applog(LOG_INFO, "CTA_OPEN"); + + cta_gen_message(buf, CTA_SEND_RESET); + // set the initial difficulty + buf[CTA_RESET_TYPE] = CTA_RESET_INIT | CTA_RESET_DIFF; + buf[CTA_RESET_DIFF] = diff_to_bits(CTA_INIT_DIFF); + buf[CTA_RESET_LOAD] = opt_cta_load ? opt_cta_load : 255; + buf[CTA_RESET_PSLOAD] = opt_ps_load; + + if (cointerra->usbinfo.nodev) + return ret; + + err = usb_write(cointerra, buf, CTA_MSG_SIZE, &amount, C_CTA_WRITE); + if (err) { + applog(LOG_INFO, "Write error %d, wrote %d of %d", err, amount, CTA_MSG_SIZE); + return ret; + } + + cgtimer_time(&ts_start); + + /* Read from the device for up to 2 seconds discarding any data that + * doesn't match a reset complete acknowledgement. */ + while (42) { + cgtimer_t ts_now, ts_diff; + char *msg; + + cgtimer_time(&ts_now); + cgtimer_sub(&ts_now, &ts_start, &ts_diff); + if (cgtimer_to_ms(&ts_diff) > 2000) { + applog(LOG_DEBUG, "%s %d: Timed out waiting for response to reset init", + cointerra->drv->name, cointerra->device_id); + break; + } + + if (cointerra->usbinfo.nodev) + break; + + err = usb_read(cointerra, buf + offset, CTA_MSG_SIZE - offset, &amount, C_CTA_READ); + if (err && err != LIBUSB_ERROR_TIMEOUT) { + applog(LOG_INFO, "%s %d: Read error %d, read %d", cointerra->drv->name, + cointerra->device_id, err, amount); + break; + } + if (!amount) + continue; + + msg = mystrstr(buf, amount, cointerra_hdr); + if (!msg) { + /* Keep the last byte in case it's the first byte of + * the 2 byte header. */ + offset = 1; + memmove(buf, buf + amount - 1, offset); + continue; + } + + if (msg > buf) { + /* length of message = offset for next usb_read after moving */ + offset = CTA_MSG_SIZE - (msg - buf); + memmove(buf, msg, offset); + continue; + } + + /* We have a full sized message starting with the header now */ + if (cta_reset_init(buf)) { + /* We can't store any other data returned with this + * reset since we have not allocated any memory for + * a cointerra_info structure yet. */ + applog(LOG_INFO, "%s %d: Successful reset init received", + cointerra->drv->name, cointerra->device_id); + ret = true; + break; + } + } + + return ret; +} + +static void cta_clear_work(struct cgpu_info *cgpu) +{ + struct work *work, *tmp; + + wr_lock(&cgpu->qlock); + HASH_ITER(hh, cgpu->queued_work, work, tmp) { + __work_completed(cgpu, work); + free_work(work); + } + wr_unlock(&cgpu->qlock); +} + +static void cta_close(struct cgpu_info *cointerra) +{ + struct cointerra_info *info = cointerra->device_data; + + /* Wait for read thread to die */ + pthread_join(info->read_thr, NULL); + + /* Open does the same reset init followed by response as is required to + * close the device. */ + if (!cta_open(cointerra)) { + applog(LOG_INFO, "%s %d: Reset on close failed", cointerra->drv->name, + cointerra->device_id); + } + + mutex_destroy(&info->lock); + mutex_destroy(&info->sendlock); + /* Don't free info here to avoid trying to access dereferenced members + * once a device is unplugged. */ + cta_clear_work(cointerra); +} + +static struct cgpu_info *cta_detect_one(struct libusb_device *dev, struct usb_find_devices *found) +{ + struct cgpu_info *cointerra = usb_alloc_cgpu(&cointerra_drv, 1); + int tries = 0; + + if (!usb_init(cointerra, dev, found)) + goto fail; + applog(LOG_INFO, "%s %d: Found at %s", cointerra->drv->name, + cointerra->device_id, cointerra->device_path); + + while (!cta_open(cointerra) && !cointerra->usbinfo.nodev) { + if (tries++ > 3) + goto failed_open; + applog(LOG_INFO, "%s %d: Failed to open %d times, retrying", cointerra->drv->name, + cointerra->device_id, tries); + } + + if (!add_cgpu(cointerra)) + goto fail_close; + + update_usb_stats(cointerra); + applog(LOG_INFO, "%s %d: Successfully set up %s", cointerra->drv->name, + cointerra->device_id, cointerra->device_path); + return cointerra; + +fail_close: + cta_close(cointerra); +failed_open: + applog(LOG_INFO, "%s %d: Failed to initialise %s", cointerra->drv->name, + cointerra->device_id, cointerra->device_path); +fail: + usb_free_cgpu(cointerra); + return NULL; +} + +static void cta_detect(bool __maybe_unused hotplug) +{ + usb_detect(&cointerra_drv, cta_detect_one); +} + +/* This function will remove a work item from the hashtable if it matches the + * id in work->subid and return a pointer to the work but it will not free the + * work. It may return NULL if it cannot find matching work. */ +static struct work *take_work_by_id(struct cgpu_info *cgpu, uint16_t id) +{ + struct work *work, *tmp, *ret = NULL; + + wr_lock(&cgpu->qlock); + HASH_ITER(hh, cgpu->queued_work, work, tmp) { + if (work->subid == id) { + ret = work; + break; + } + } + if (ret) + __work_completed(cgpu, ret); + wr_unlock(&cgpu->qlock); + + return ret; +} + +/* This function will look up a work item in the hashtable if it matches the + * id in work->subid and return a cloned work item if it matches. It may return + * NULL if it cannot find matching work. */ +static struct work *clone_work_by_id(struct cgpu_info *cgpu, uint16_t id) +{ + struct work *work, *tmp, *ret = NULL; + + rd_lock(&cgpu->qlock); + HASH_ITER(hh, cgpu->queued_work, work, tmp) { + if (work->subid == id) { + ret = work; + break; + } + } + if (ret) + ret = copy_work(ret); + rd_unlock(&cgpu->qlock); + + return ret; +} + +static bool cta_send_msg(struct cgpu_info *cointerra, char *buf); + +static uint16_t hu16_from_msg(char *buf, int msg) +{ + return le16toh(*(uint16_t *)&buf[msg]); +} + +static uint32_t hu32_from_msg(char *buf, int msg) +{ + return le32toh(*(uint32_t *)&buf[msg]); +} + +static uint64_t hu64_from_msg(char *buf, int msg) +{ + return le64toh(*(uint64_t *)&buf[msg]); +} + +static uint8_t u8_from_msg(char *buf, int msg) +{ + return *(uint8_t *)&buf[msg]; +} + +static void msg_from_hu16(char *buf, int msg, uint16_t val) +{ + *(uint16_t *)&buf[msg] = htole16(val); +} + +static void cta_parse_reqwork(struct cgpu_info *cointerra, struct cointerra_info *info, + char *buf) +{ + uint16_t retwork; + + retwork = hu16_from_msg(buf, CTA_REQWORK_REQUESTS); + applog(LOG_DEBUG, "%s %d: Request work message for %u items received", + cointerra->drv->name, cointerra->device_id, retwork); + + mutex_lock(&info->lock); + info->requested = retwork; + /* Wake up the main scanwork loop since we need more + * work. */ + pthread_cond_signal(&info->wake_cond); + mutex_unlock(&info->lock); +} + +static void cta_parse_recvmatch(struct thr_info *thr, struct cgpu_info *cointerra, + struct cointerra_info *info, char *buf) +{ + uint32_t timestamp_offset, mcu_tag; + uint16_t retwork; + struct work *work; + + /* No endian switch needs doing here since it's sent and returned as + * the same 4 bytes */ + retwork = *(uint16_t *)(&buf[CTA_DRIVER_TAG]); + mcu_tag = hu32_from_msg(buf, CTA_MCU_TAG); + applog(LOG_DEBUG, "%s %d: Match message for id 0x%04x MCU id 0x%08x received", + cointerra->drv->name, cointerra->device_id, retwork, mcu_tag); + + work = clone_work_by_id(cointerra, retwork); + if (likely(work)) { + uint8_t wdiffbits = u8_from_msg(buf, CTA_WORK_DIFFBITS); + uint32_t nonce = hu32_from_msg(buf, CTA_MATCH_NONCE); + unsigned char rhash[32]; + char outhash[16]; + double wdiff; + bool ret; + + timestamp_offset = hu32_from_msg(buf, CTA_MATCH_NOFFSET); + if (timestamp_offset) { + struct work *base_work = work; + + work = copy_work_noffset(base_work, timestamp_offset); + free_work(base_work); + } + + /* Test against the difficulty we asked for along with the work */ + wdiff = bits_to_diff(wdiffbits); + ret = test_nonce_diff(work, nonce, wdiff); + + if (opt_debug) { + /* Debugging, remove me */ + swab256(rhash, work->hash); + __bin2hex(outhash, rhash, 8); + applog(LOG_WARNING, "submit work %s 0x%04x 0x%08x %d 0x%08x", + outhash, retwork, mcu_tag, timestamp_offset, nonce); + } + + if (likely(ret)) { + uint8_t asic, core, pipe, coreno; + int pipeno, bitchar, bitbit; + uint64_t hashes; + + asic = u8_from_msg(buf, CTA_MCU_ASIC); + core = u8_from_msg(buf, CTA_MCU_CORE); + pipe = u8_from_msg(buf, CTA_MCU_PIPE); + pipeno = asic * 512 + core * 128 + pipe; + coreno = asic * 4 + core; + if (unlikely(asic > 1 || core > 3 || pipe > 127 || pipeno > 1023)) { + applog(LOG_WARNING, "%s %d: MCU invalid pipe asic %d core %d pipe %d", + cointerra->drv->name, cointerra->device_id, asic, core, pipe); + coreno = 0; + } else { + info->last_pipe_nonce[pipeno] = time(NULL); + bitchar = pipeno / 8; + bitbit = pipeno % 8; + info->pipe_bitmap[bitchar] |= 0x80 >> bitbit; + } + + applog(LOG_DEBUG, "%s %d: Submitting tested work job_id %s work_id %u", + cointerra->drv->name, cointerra->device_id, work->job_id, work->subid); + ret = submit_tested_work(thr, work); + + hashes = (uint64_t)wdiff * 0x100000000ull; + mutex_lock(&info->lock); + info->share_hashes += hashes; + info->tot_core_hashes[coreno] += hashes; + info->hashes += nonce; + mutex_unlock(&info->lock); + } else { + char sendbuf[CTA_MSG_SIZE]; + uint8_t asic, core, coreno; + asic = u8_from_msg(buf, CTA_MCU_ASIC); + core = u8_from_msg(buf, CTA_MCU_CORE); + coreno = asic * 4 + core; + inc_hw_errors(thr); + + applog(LOG_WARNING, "%s %d: Notify bad match work", + cointerra->drv->name, cointerra->device_id); + if (coreno < CTA_CORES) + info->fmatch_errors[coreno]++; + if (opt_debug) { + uint64_t sdiff = share_diff(work); + unsigned char midstate[32], wdata[12]; + char hexmidstate[68], hexwdata[28]; + uint16_t wid; + + memcpy(&wid, &info->work_id, 2); + flip32(midstate, work->midstate); + __bin2hex(hexmidstate, midstate, 32); + flip12(wdata, &work->data[64]); + __bin2hex(hexwdata, wdata, 12); + applog(LOG_DEBUG, "False match sent: work id %u midstate %s blkhdr %s", + wid, hexmidstate, hexwdata); + applog(LOG_DEBUG, "False match reports: work id 0x%04x MCU id 0x%08x work diff %.1f", + retwork, mcu_tag, wdiff); + applog(LOG_DEBUG, "False match tested: nonce 0x%08x noffset %d %s", + nonce, timestamp_offset, outhash); + applog(LOG_DEBUG, "False match devdiff set to %.1f share diff calc %"PRIu64, + work->device_diff, sdiff); + } + + /* Tell the device we got a false match */ + cta_gen_message(sendbuf, CTA_SEND_FMATCH); + memcpy(sendbuf + 3, buf + 3, CTA_MSG_SIZE - 3); + cta_send_msg(cointerra, sendbuf); + } + free_work(work); + } else { + applog(LOG_WARNING, "%s %d: Matching work id 0x%X %d not found", cointerra->drv->name, + cointerra->device_id, retwork, __LINE__); + inc_hw_errors(thr); + + mutex_lock(&info->lock); + info->no_matching_work++; + mutex_unlock(&info->lock); + } +} + +static void cta_parse_wdone(struct thr_info *thr, struct cgpu_info *cointerra, + struct cointerra_info *info, char *buf) +{ + uint16_t retwork = *(uint16_t *)(&buf[CTA_DRIVER_TAG]); + struct work *work = take_work_by_id(cointerra, retwork); + uint64_t hashes; + + if (likely(work)) { + free_work(work); + applog(LOG_DEBUG, "%s %d: Done work found id 0x%X %d", + cointerra->drv->name, cointerra->device_id, retwork, __LINE__); + } else { + applog(LOG_WARNING, "%s %d: Done work not found id 0x%X %d", + cointerra->drv->name, cointerra->device_id, retwork, __LINE__); + inc_hw_errors(thr); + } + + /* Removing hashes from work done message */ + hashes = hu64_from_msg(buf, CTA_WDONE_NONCES); + if (unlikely(hashes > (61 * 0x100000000ull))) { + applog(LOG_INFO, "%s Invalid hash returned %"PRIu64"x %"PRIu64"x %"PRIu64"X", + __func__, info->hashes, hashes, hashes); + hashes = 0; + } + + mutex_lock(&info->lock); + info->hashes += hashes; + mutex_unlock(&info->lock); +} + +static void u16array_from_msg(uint16_t *u16, int entries, int var, char *buf) +{ + int i, j; + + for (i = 0, j = 0; i < entries; i++, j += sizeof(uint16_t)) + u16[i] = hu16_from_msg(buf, var + j); +} + +static void cta_parse_statread(struct cgpu_info *cointerra, struct cointerra_info *info, + char *buf) +{ + float max_temp = 0; + int i; + + mutex_lock(&info->lock); + u16array_from_msg(info->coretemp, CTA_CORES, CTA_STAT_CORETEMPS, buf); + info->ambtemp_low = hu16_from_msg(buf, CTA_STAT_AMBTEMP_LOW); + info->ambtemp_avg = hu16_from_msg(buf, CTA_STAT_AMBTEMP_AVG); + info->ambtemp_high = hu16_from_msg(buf, CTA_STAT_AMBTEMP_HIGH); + u16array_from_msg(info->pump_tachs, CTA_PUMPS, CTA_STAT_PUMP_TACHS, buf); + u16array_from_msg(info->fan_tachs, CTA_FANS, CTA_STAT_FAN_TACHS, buf); + u16array_from_msg(info->corevolts, CTA_CORES, CTA_STAT_CORE_VOLTS, buf); + info->volts33 = hu16_from_msg(buf, CTA_STAT_VOLTS33); + info->volts12 = hu16_from_msg(buf, CTA_STAT_VOLTS12); + info->inactive = hu16_from_msg(buf, CTA_STAT_INACTIVE); + info->active = hu16_from_msg(buf, CTA_STAT_ACTIVE); + mutex_unlock(&info->lock); + + for (i = 0; i < CTA_CORES; i++) { + if (info->coretemp[i] > max_temp) + max_temp = info->coretemp[i]; + } + max_temp /= 100.0; + /* Store the max temperature in the cgpu struct as an exponentially + * changing value. */ + cointerra->temp = cointerra->temp * 0.63 + max_temp * 0.37; +} + +static void u8array_from_msg(uint8_t *u8, int entries, int var, char *buf) +{ + int i; + + for (i = 0; i < entries; i++) + u8[i] = u8_from_msg(buf, var + i); +} + +static void cta_parse_statset(struct cointerra_info *info, char *buf) +{ + mutex_lock(&info->lock); + u8array_from_msg(info->coreperf, CTA_CORES, CTA_STAT_PERFMODE, buf); + u8array_from_msg(info->fanspeed, CTA_FANS, CTA_STAT_FANSPEEDS, buf); + info->dies_active = u8_from_msg(buf, CTA_STAT_DIES_ACTIVE); + u8array_from_msg(info->pipes_enabled, CTA_CORES, CTA_STAT_PIPES_ENABLED, buf); + u16array_from_msg(info->corefreqs, CTA_CORES, CTA_STAT_CORE_FREQS, buf); + info->uptime = hu32_from_msg(buf,CTA_STAT_UPTIME); + mutex_unlock(&info->lock); +} + +static void cta_parse_irstat(struct cointerra_info *info, char *buf) +{ + uint8_t channel = u8_from_msg(buf,CTA_IRSTAT_CHANNEL); + + if (channel >= CTA_CORES) + return; + + mutex_lock(&info->lock); + info->irstat_vin[channel] = hu16_from_msg(buf,CTA_IRSTAT_VIN); + info->irstat_iin[channel] = hu16_from_msg(buf,CTA_IRSTAT_IIN); + info->irstat_vout[channel] = hu16_from_msg(buf,CTA_IRSTAT_VOUT); + info->irstat_iout[channel] = hu16_from_msg(buf,CTA_IRSTAT_IOUT); + info->irstat_temp1[channel] = hu16_from_msg(buf,CTA_IRSTAT_TEMP1); + info->irstat_temp2[channel] = hu16_from_msg(buf,CTA_IRSTAT_TEMP2); + info->irstat_pout[channel] = hu16_from_msg(buf,CTA_IRSTAT_POUT); + info->irstat_pin[channel] = hu16_from_msg(buf,CTA_IRSTAT_PIN); + info->irstat_efficiency[channel] = hu16_from_msg(buf,CTA_IRSTAT_EFF); + info->irstat_status[channel] = hu16_from_msg(buf,CTA_IRSTAT_STATUS); + mutex_unlock(&info->lock); +} + +static void cta_parse_info(struct cgpu_info *cointerra, struct cointerra_info *info, + char *buf) +{ + mutex_lock(&info->lock); + info->hwrev = hu64_from_msg(buf, CTA_INFO_HWREV); + info->serial = hu32_from_msg(buf, CTA_INFO_SERNO); + info->asics = u8_from_msg(buf, CTA_INFO_NUMASICS); + info->dies = u8_from_msg(buf, CTA_INFO_NUMDIES); + info->cores = hu16_from_msg(buf, CTA_INFO_NUMCORES); + info->board_number = u8_from_msg(buf, CTA_INFO_BOARDNUMBER); + info->fwrev[0] = u8_from_msg(buf, CTA_INFO_FWREV_MAJ); + info->fwrev[1] = u8_from_msg(buf, CTA_INFO_FWREV_MIN); + info->fwrev[2] = u8_from_msg(buf, CTA_INFO_FWREV_MIC); + info->fw_year = hu16_from_msg(buf, CTA_INFO_FWDATE_YEAR); + info->fw_month = u8_from_msg(buf, CTA_INFO_FWDATE_MONTH); + info->fw_day = u8_from_msg(buf, CTA_INFO_FWDATE_DAY); + info->init_diffbits = u8_from_msg(buf, CTA_INFO_INITDIFFBITS); + info->min_diffbits = u8_from_msg(buf, CTA_INFO_MINDIFFBITS); + info->max_diffbits = u8_from_msg(buf, CTA_INFO_MAXDIFFBITS); + mutex_unlock(&info->lock); + + if (!cointerra->unique_id) { + uint32_t b32 = htobe32(info->serial); + + cointerra->unique_id = bin2hex((unsigned char *)&b32, 4); + } +} + +static void cta_parse_rdone(struct cgpu_info *cointerra, struct cointerra_info *info, + char *buf) +{ + uint8_t reset_type, diffbits; + uint64_t wdone; + + reset_type = buf[CTA_RESET_TYPE]; + diffbits = buf[CTA_RESET_DIFF]; + wdone = hu64_from_msg(buf, CTA_WDONE_NONCES); + + if (wdone) { + applog(LOG_INFO, "%s %d: Reset done type %u message %u diffbits %"PRIu64" done received", + cointerra->drv->name, cointerra->device_id, reset_type, diffbits, wdone); + + mutex_lock(&info->lock); + info->hashes += wdone; + mutex_unlock(&info->lock); + } + + /* Note that the cgsem that is posted here must not be waited on while + * holding the info->lock to not get into a livelock since this + * function also grabs the lock first and it's always best to not sleep + * while holding a lock. */ + if (reset_type == CTA_RESET_NEW) { + cta_clear_work(cointerra); + /* Tell reset sender that the reset is complete + * and it may resume. */ + cgsem_post(&info->reset_sem); + } +} + +static void cta_zero_stats(struct cgpu_info *cointerra); + +static void cta_parse_debug(struct cointerra_info *info, char *buf) +{ + mutex_lock(&info->lock); + + info->tot_underruns = hu16_from_msg(buf, CTA_STAT_UNDERRUNS); + u16array_from_msg(info->tot_hw_errors, CTA_CORES, CTA_STAT_HW_ERRORS, buf); + info->tot_hashes = hu64_from_msg(buf, CTA_STAT_HASHES); + info->tot_flushed_hashes = hu64_from_msg(buf, CTA_STAT_FLUSHED_HASHES); + info->autovoltage = u8_from_msg(buf, CTA_STAT_AUTOVOLTAGE); + info->current_ps_percent = u8_from_msg(buf, CTA_STAT_POWER_PERCENT); + info->power_used = hu16_from_msg(buf,CTA_STAT_POWER_USED); + info->power_voltage = hu16_from_msg(buf,CTA_STAT_VOLTAGE); + info->ipower_used = hu16_from_msg(buf,CTA_STAT_IPOWER_USED); + info->ipower_voltage = hu16_from_msg(buf,CTA_STAT_IVOLTAGE); + info->power_temps[0] = hu16_from_msg(buf,CTA_STAT_PS_TEMP1); + info->power_temps[1] = hu16_from_msg(buf,CTA_STAT_PS_TEMP2); + + mutex_unlock(&info->lock); + + /* Autovoltage is positive only once at startup and eventually drops + * to zero. After that time we reset the stats since they're unreliable + * till then. */ + if (unlikely(!info->autovoltage_complete && !info->autovoltage)) { + struct cgpu_info *cointerra = info->thr->cgpu; + + info->autovoltage_complete = true; + cgtime(&cointerra->dev_start_tv); + cta_zero_stats(cointerra); + cointerra->total_mhashes = 0; + cointerra->accepted = 0; + cointerra->rejected = 0; + cointerra->hw_errors = 0; + cointerra->utility = 0.0; + cointerra->last_share_pool_time = 0; + cointerra->diff1 = 0; + cointerra->diff_accepted = 0; + cointerra->diff_rejected = 0; + cointerra->last_share_diff = 0; + } +} + +static int verify_checksum(char *buf) +{ + unsigned char checksum = 0; + unsigned char i; + + for (i = 0; i < 63; i++) + checksum += buf[i]; + + return (checksum == buf[63]); +} + +static void cta_parse_msg(struct thr_info *thr, struct cgpu_info *cointerra, + struct cointerra_info *info, char *buf) +{ + if ((buf[CTA_MSG_TYPE] != CTA_RECV_MATCH)&& + (buf[CTA_MSG_TYPE] != CTA_RECV_WDONE)) { + if (unlikely(verify_checksum(buf) == 0)) { + inc_hw_errors(thr); + applog(LOG_INFO, "%s %d: checksum bad",cointerra->drv->name,cointerra->device_id); + } + } + + switch (buf[CTA_MSG_TYPE]) { + default: + case CTA_RECV_UNUSED: + applog(LOG_INFO, "%s %d: Unidentified message type %u", + cointerra->drv->name, cointerra->device_id, buf[CTA_MSG_TYPE]); + break; + case CTA_RECV_REQWORK: + cta_parse_reqwork(cointerra, info, buf); + break; + case CTA_RECV_MATCH: + cta_parse_recvmatch(thr, cointerra, info, buf); + break; + case CTA_RECV_WDONE: + applog(LOG_DEBUG, "%s %d: Work done message received", + cointerra->drv->name, cointerra->device_id); + cta_parse_wdone(thr, cointerra, info, buf); + break; + case CTA_RECV_STATREAD: + applog(LOG_DEBUG, "%s %d: Status readings message received", + cointerra->drv->name, cointerra->device_id); + cta_parse_statread(cointerra, info, buf); + break; + case CTA_RECV_STATSET: + applog(LOG_DEBUG, "%s %d: Status settings message received", + cointerra->drv->name, cointerra->device_id); + cta_parse_statset(info, buf); + break; + case CTA_RECV_INFO: + applog(LOG_DEBUG, "%s %d: Info message received", + cointerra->drv->name, cointerra->device_id); + cta_parse_info(cointerra, info, buf); + break; + case CTA_RECV_MSG: + applog(LOG_NOTICE, "%s %d: MSG: %s", + cointerra->drv->name, cointerra->device_id, &buf[CTA_MSG_RECVD]); + break; + case CTA_RECV_RDONE: + cta_parse_rdone(cointerra, info, buf); + break; + case CTA_RECV_STATDEBUG: + cta_parse_debug(info, buf); + break; + case CTA_RECV_IRSTAT: + cta_parse_irstat(info, buf); + break; + } +} + +static void *cta_recv_thread(void *arg) +{ + struct thr_info *thr = (struct thr_info *)arg; + struct cgpu_info *cointerra = thr->cgpu; + struct cointerra_info *info = cointerra->device_data; + char threadname[24]; + int offset = 0; + + snprintf(threadname, 24, "cta_recv/%d", cointerra->device_id); + RenameThread(threadname); + + while (likely(!cointerra->shutdown)) { + char buf[CTA_READBUF_SIZE]; + int amount, err; + + if (unlikely(cointerra->usbinfo.nodev)) { + applog(LOG_DEBUG, "%s %d: Device disappeared, disabling recv thread", + cointerra->drv->name, cointerra->device_id); + break; + } + + err = usb_read(cointerra, buf + offset, CTA_MSG_SIZE, &amount, C_CTA_READ); + if (err && err != LIBUSB_ERROR_TIMEOUT) { + applog(LOG_ERR, "%s %d: Read error %d, read %d", cointerra->drv->name, + cointerra->device_id, err, amount); + break; + } + offset += amount; + + while (offset >= CTA_MSG_SIZE) { + char *msg = mystrstr(buf, offset, cointerra_hdr); + int begin; + + if (unlikely(!msg)) { + applog(LOG_WARNING, "%s %d: No message header found, discarding buffer", + cointerra->drv->name, cointerra->device_id); + inc_hw_errors(thr); + /* Save the last byte in case it's the fist + * byte of a header. */ + begin = CTA_MSG_SIZE - 1; + offset -= begin; + memmove(buf, buf + begin, offset); + continue; + } + + if (unlikely(msg != buf)) { + begin = msg - buf; + applog(LOG_WARNING, "%s %d: Reads out of sync, discarding %d bytes", + cointerra->drv->name, cointerra->device_id, begin); + inc_hw_errors(thr); + offset -= begin; + memmove(buf, msg, offset); + if (offset < CTA_MSG_SIZE) + break; + } + + /* We have enough buffer for a full message, parse now */ + cta_parse_msg(thr, cointerra, info, msg); + offset -= CTA_MSG_SIZE; + if (offset > 0) + memmove(buf, buf + CTA_MSG_SIZE, offset); + } + } + + return NULL; +} + +static bool cta_send_msg(struct cgpu_info *cointerra, char *buf) +{ + struct cointerra_info *info = cointerra->device_data; + int amount, err; + + if (unlikely(cointerra->usbinfo.nodev)) + return false; + + /* Serialise usb writes to prevent overlap in case multiple threads + * send messages */ + mutex_lock(&info->sendlock); + err = usb_write(cointerra, buf, CTA_MSG_SIZE, &amount, C_CTA_WRITE); + mutex_unlock(&info->sendlock); + + if (unlikely(err || amount != CTA_MSG_SIZE)) { + applog(LOG_ERR, "%s %d: Write error %d, wrote %d of %d", cointerra->drv->name, + cointerra->device_id, err, amount, CTA_MSG_SIZE); + return false; + } + return true; +} + +static bool cta_prepare(struct thr_info *thr) +{ + struct cgpu_info *cointerra = thr->cgpu; + struct cointerra_info *info = calloc(sizeof(struct cointerra_info), 1); + char buf[CTA_MSG_SIZE]; + + if (unlikely(cointerra->usbinfo.nodev)) + return false; + + if (unlikely(!info)) + quit(1, "Failed to calloc info in cta_detect_one"); + cointerra->device_data = info; + /* Nominally set a requested value when starting, preempting the need + * for a req-work message. */ + info->requested = CTA_MAX_QUEUE; + + info->thr = thr; + mutex_init(&info->lock); + mutex_init(&info->sendlock); + if (unlikely(pthread_cond_init(&info->wake_cond, NULL))) + quit(1, "Failed to create cta pthread cond"); + cgsem_init(&info->reset_sem); + if (pthread_create(&info->read_thr, NULL, cta_recv_thread, (void *)thr)) + quit(1, "Failed to create cta_recv_thread"); + + /* Request a single status setting message */ + cta_gen_message(buf, CTA_SEND_REQUEST); + msg_from_hu16(buf, CTA_REQ_MSGTYPE, CTA_RECV_STATSET); + msg_from_hu16(buf, CTA_REQ_INTERVAL, 0); + if (!cta_send_msg(cointerra, buf)) + return false; + + /* Request status debug messages every 60 seconds */ + cta_gen_message(buf, CTA_SEND_REQUEST); + msg_from_hu16(buf, CTA_REQ_MSGTYPE, CTA_RECV_STATDEBUG); + msg_from_hu16(buf, CTA_REQ_INTERVAL, 6000); + if (!cta_send_msg(cointerra, buf)) + return false; + + cgtime(&info->core_hash_start); + + return true; +} + +static void cta_send_reset(struct cgpu_info *cointerra, struct cointerra_info *info, + uint8_t reset_type, uint8_t diffbits); +static void cta_flush_work(struct cgpu_info *cointerra); + +/* *_fill and *_scanwork are serialised wrt to each other */ +static bool cta_fill(struct cgpu_info *cointerra) +{ + struct cointerra_info *info = cointerra->device_data; + bool ret = true; + char buf[CTA_MSG_SIZE]; + struct work *work = NULL; + unsigned short nroll_limit; + uint32_t swab[8]; + uint8_t diffbits; + + //applog(LOG_WARNING, "%s %d: cta_fill %d", cointerra->drv->name, cointerra->device_id,__LINE__); + + if (unlikely(info->thr->work_restart)) + cta_flush_work(cointerra); + + mutex_lock(&info->lock); + if (!info->requested) + goto out_unlock; + work = get_queued(cointerra); + if (unlikely(!work)) { + ret = false; + goto out_unlock; + } + if (--info->requested > 0) + ret = false; + + /* It does not matter what endian this uint16_t is since it will be + * the same value on sending to the MC as returning in match/done. This + * will automatically wrap as a uint16_t. It cannot be zero for the MCU + * though. */ + if (unlikely(++info->work_id == 0)) + info->work_id = 1; + work->subid = info->work_id; + + diffbits = diff_to_bits(work->device_diff); + + cta_gen_message(buf, CTA_SEND_WORK); + + memcpy(buf + CTA_DRIVER_TAG, &info->work_id, 2); + + flip32(swab, work->midstate); + memcpy(buf + CTA_WORK_MIDSTATE, swab, 32); + + flip12(swab, &work->data[64]); + memcpy(buf + CTA_WORK_DATA, swab, 12); + + nroll_limit = htole16(work->drv_rolllimit); + memcpy(buf + CTA_WORK_NROLL, &nroll_limit, 2); + + memcpy(buf + CTA_WORK_DIFFBITS, &diffbits, 1); + +out_unlock: + mutex_unlock(&info->lock); + + if (work) { + cgtime(&work->tv_work_start); + applog(LOG_DEBUG, "%s %d: Sending work job_id %s work_id %u", cointerra->drv->name, + cointerra->device_id, work->job_id, work->subid); + if (unlikely(!cta_send_msg(cointerra, buf))) { + work_completed(cointerra, work); + applog(LOG_INFO, "%s %d: Failed to send work", + cointerra->drv->name, cointerra->device_id); + /* The device will fail after this */ + } + } + + return ret; +} + +static void cta_send_reset(struct cgpu_info *cointerra, struct cointerra_info *info, + uint8_t reset_type, uint8_t diffbits) +{ + char buf[CTA_MSG_SIZE]; + int ret, retries = 0; + + /* Clear any accumulated messages in case we've gotten out of sync. */ + cgsem_reset(&info->reset_sem); +resend: + cta_gen_message(buf, CTA_SEND_RESET); + + buf[CTA_RESET_TYPE] = reset_type; + buf[CTA_RESET_LOAD] = opt_cta_load ? opt_cta_load : 255; + buf[CTA_RESET_PSLOAD] = opt_ps_load; + + applog(LOG_INFO, "%s %d: Sending Reset type %u with diffbits %u", cointerra->drv->name, + cointerra->device_id, reset_type, diffbits); + cta_send_msg(cointerra, buf); + + /* Wait for read thread to parse a reset message and signal us we may + * return to submitting other messages. Use a timeout in case we have + * a problem and the reset done message never returns. */ + if (reset_type == CTA_RESET_NEW) { + ret = cgsem_mswait(&info->reset_sem, CTA_RESET_TIMEOUT); + if (ret) { + if (++retries < 5) { + applog(LOG_INFO, "%s %d: Timed out waiting for reset done msg, retrying", + cointerra->drv->name, cointerra->device_id); + goto resend; + } + applog(LOG_WARNING, "%s %d: Timed out waiting for reset done msg", + cointerra->drv->name, cointerra->device_id); + } + /* Good place to flush any work we have */ + flush_queue(cointerra); + } +} + +static void cta_flush_work(struct cgpu_info *cointerra) +{ + struct cointerra_info *info = cointerra->device_data; + + applog(LOG_INFO, "%s %d: cta_flush_work %d", cointerra->drv->name, cointerra->device_id, + __LINE__); + cta_send_reset(cointerra, info, CTA_RESET_NEW, 0); + info->thr->work_restart = false; +} + +static void cta_update_work(struct cgpu_info *cointerra) +{ + struct cointerra_info *info = cointerra->device_data; + + applog(LOG_INFO, "%s %d: Update work", cointerra->drv->name, cointerra->device_id); + cta_send_reset(cointerra, info, CTA_RESET_UPDATE, 0); +} + +static void cta_zero_corehashes(struct cointerra_info *info) +{ + int i; + + for (i = 0; i < CTA_CORES; i++) + info->tot_core_hashes[i] = 0; + cgtime(&info->core_hash_start); +} + +/* Send per core hashrate calculations at regular intervals ~every 5 minutes */ +static void cta_send_corehashes(struct cgpu_info *cointerra, struct cointerra_info *info, + double corehash_time) +{ + uint16_t core_ghs[CTA_CORES]; + double k[CTA_CORES]; + char buf[CTA_MSG_SIZE]; + int i, offset; + + for (i = 0; i < CTA_CORES; i++) { + k[i] = (double)info->tot_core_hashes[i]; +#if 0 + k[i] /= ((double)32 * (double)0x100000000ull); + k[i] = sqrt(k[i]) + 1; + k[i] *= k[i]; + k[i] = k[i] * 32 * ((double)0x100000000ull ); +#endif + k[i] /= ((double)1000000000 * corehash_time); + core_ghs[i] = k[i]; + } + cta_gen_message(buf, CTA_SEND_COREHASHRATE); + offset = CTA_CORE_HASHRATES; + for (i = 0; i < CTA_CORES; i++) { + msg_from_hu16(buf, offset, core_ghs[i]); + offset += 2; // uint16_t + } + cta_send_msg(cointerra, buf); +} + +static int64_t cta_scanwork(struct thr_info *thr) +{ + struct cgpu_info *cointerra = thr->cgpu; + struct cointerra_info *info = cointerra->device_data; + double corehash_time; + struct timeval now; + uint32_t runtime; + int64_t hashes; + + applog(LOG_DEBUG, "%s %d: cta_scanwork %d", cointerra->drv->name, cointerra->device_id,__LINE__); + + if (unlikely(cointerra->usbinfo.nodev)) { + hashes = -1; + goto out; + } + + cgtime(&now); + + if (unlikely(thr->work_restart)) { + applog(LOG_INFO, "%s %d: Flush work line %d", + cointerra->drv->name, cointerra->device_id,__LINE__); + cta_flush_work(cointerra); + } else { + struct timespec abstime, tsdiff = {0, 500000000}; + time_t now_t; + int i; + + timeval_to_spec(&abstime, &now); + timeraddspec(&abstime, &tsdiff); + + /* Discard work that was started more than 5 minutes ago as + * a safety precaution backup in case the hardware failed to + * return a work done message for some work items. */ + age_queued_work(cointerra, 300.0); + + /* Each core should be 1.7MH so at max diff of 32 should + * average a share every ~80 seconds.Use this opportunity to + * unset the bits in any pipes that have not returned a valid + * nonce for over 30 full nonce ranges or 2400s. */ + now_t = time(NULL); + for (i = 0; i < 1024; i++) { + if (unlikely(now_t > info->last_pipe_nonce[i] + 2400)) { + int bitchar = i / 8, bitbit = i % 8; + + info->pipe_bitmap[bitchar] &= ~(0x80 >> bitbit); + } + } + + /* Sleep for up to 0.5 seconds, waking if we need work or + * have received a restart message. */ + mutex_lock(&info->lock); + pthread_cond_timedwait(&info->wake_cond, &info->lock, &abstime); + mutex_unlock(&info->lock); + + if (thr->work_restart) { + applog(LOG_INFO, "%s %d: Flush work line %d", + cointerra->drv->name, cointerra->device_id,__LINE__); + cta_flush_work(cointerra); + } + } + + corehash_time = tdiff(&now, &info->core_hash_start); + if (corehash_time > 300) { + cta_send_corehashes(cointerra, info, corehash_time); + cta_zero_corehashes(info); + } + + mutex_lock(&info->lock); + hashes = info->share_hashes; + info->tot_share_hashes += info->share_hashes; + info->tot_calc_hashes += info->hashes; + runtime = cgpu_runtime(thr->cgpu); + runtime /= 30; + info->old_hashes[runtime % 32] = info->tot_calc_hashes; + info->hashes = info->share_hashes = 0; + mutex_unlock(&info->lock); + + if (unlikely(cointerra->usbinfo.nodev)) + hashes = -1; +out: + return hashes; +} + +/* This is used for a work restart. We don't actually perform the work restart + * here but wake up the scanwork loop if it's waiting on the conditional so + * that it can test for the restart message. */ +static void cta_wake(struct cgpu_info *cointerra) +{ + struct cointerra_info *info = cointerra->device_data; + + mutex_lock(&info->lock); + pthread_cond_signal(&info->wake_cond); + mutex_unlock(&info->lock); +} + +static void cta_shutdown(struct thr_info *thr) +{ + struct cgpu_info *cointerra = thr->cgpu; + + cta_close(cointerra); +} + +static void cta_zero_stats(struct cgpu_info *cointerra) +{ + struct cointerra_info *info = cointerra->device_data; + int i; + + info->tot_calc_hashes = 0; + info->tot_reset_hashes = info->tot_hashes; + info->tot_share_hashes = 0; + cta_zero_corehashes(info); + + for (i = 0; i < 16 * 2; i++) + info->old_hashes[i] = 0; +} + +static int bits_set(char v) +{ + int c; + + for (c = 0; v; c++) + v &= v - 1; + return c; +} + +static struct api_data *cta_api_stats(struct cgpu_info *cgpu) +{ + struct api_data *root = NULL; + struct cointerra_info *info = cgpu->device_data; + double dev_runtime = cgpu_runtime(cgpu); + int i, asic, core, coreno = 0; + struct timeval now; + char bitmaphex[36]; + uint64_t ghs, val; + char buf[64]; + uint32_t runtime = cgpu_runtime(cgpu); + + /* Info data */ + root = api_add_uint16(root, "HW Revision", &info->hwrev, false); + root = api_add_uint32(root, "Serial", &info->serial, false); + root = api_add_uint8(root, "Asics", &info->asics, false); + root = api_add_uint8(root, "Dies", &info->dies, false); + root = api_add_uint16(root, "Cores", &info->cores, false); + root = api_add_uint8(root, "Board number", &info->board_number, false); + sprintf(buf, "%u.%u.%u", info->fwrev[0], info->fwrev[1], info->fwrev[2]); + root = api_add_string(root, "FW Revision", buf, true); + sprintf(buf, "%04u-%02u-%02u", info->fw_year, info->fw_month, info->fw_day); + root = api_add_string(root, "FW Date", buf, true); + root = api_add_uint8(root, "Init diffbits", &info->init_diffbits, false); + root = api_add_uint8(root, "Min diffbits", &info->min_diffbits, false); + root = api_add_uint8(root, "Max diffbits", &info->max_diffbits, false); + + /* Status readings */ + for (i = 0; i < CTA_CORES; i++) { + sprintf(buf, "CoreTemp%d", i); + root = api_add_int16(root, buf, &info->coretemp[i], false); + } + root = api_add_int16(root, "Ambient Low", &info->ambtemp_low, false); + root = api_add_int16(root, "Ambient Avg", &info->ambtemp_avg, false); + root = api_add_int16(root, "Ambient High", &info->ambtemp_high, false); + for (i = 0; i < CTA_PUMPS; i++) { + sprintf(buf, "PumpRPM%d", i); + root = api_add_uint16(root, buf, &info->pump_tachs[i], false); + } + for (i = 0; i < CTA_FANS; i++) { + sprintf(buf, "FanRPM%d", i); + root = api_add_uint16(root, buf, &info->fan_tachs[i], false); + } + for (i = 0; i < CTA_CORES; i++) { + sprintf(buf, "CoreFreqs%d", i); + root = api_add_uint16(root, buf, &info->corefreqs[i], false); + } + + for (i = 0; i < CTA_CORES; i++) { + sprintf(buf, "CoreVolts%d", i); + root = api_add_uint16(root, buf, &info->corevolts[i], false); + } + root = api_add_uint16(root, "Volts3.3", &info->volts33, false); + root = api_add_uint16(root, "Volts12", &info->volts12, false); + root = api_add_uint16(root, "Inactive", &info->inactive, false); + root = api_add_uint16(root, "Active", &info->active, false); + + /* Status settings */ + for (i = 0; i < CTA_CORES; i++) { + sprintf(buf, "CorePerfMode%d", i); + root = api_add_uint8(root, buf, &info->coreperf[i], false); + } + for (i = 0; i < CTA_FANS; i++) { + sprintf(buf, "FanSpeed%d", i); + root = api_add_uint8(root, buf, &info->fanspeed[i], false); + } + root = api_add_uint8(root, "DiesActive", &info->dies_active, false); + for (i = 0; i < CTA_CORES; i++) { + sprintf(buf, "PipesEnabled%d", i); + root = api_add_uint8(root, buf, &info->pipes_enabled[i], false); + } + + /* Status debug */ + root = api_add_int(root, "Underruns", &info->tot_underruns, false); + for (i = 0; i < CTA_CORES; i++) { + sprintf(buf, "HWErrors%d", i); + root = api_add_uint16(root, buf, &info->tot_hw_errors[i], false); + } + ghs = info->tot_calc_hashes / dev_runtime; + root = api_add_uint64(root, "Calc hashrate", &ghs, true); + ghs = (info->tot_hashes - info->tot_reset_hashes) / dev_runtime; + root = api_add_uint64(root, "Hashrate", &ghs, true); + //root = api_add_uint64(root, "bmminer 15m Hashrate", &cgpu->rolling15, true); + // get runtime in 30 second steps + runtime = runtime / 30; + // store the current hashes + info->old_hashes[runtime%32] = info->tot_calc_hashes; + // calc the 15 minute average hashrate + ghs = (info->old_hashes[(runtime+31)%32] - info->old_hashes[(runtime+1)%32])/(15*60); + root = api_add_uint64(root, "15m Hashrate", &ghs, true); + ghs = info->tot_share_hashes / dev_runtime; + root = api_add_uint64(root, "Share hashrate", &ghs, true); + root = api_add_uint64(root, "Total calc hashes", &info->tot_calc_hashes, false); + ghs = info->tot_hashes - info->tot_reset_hashes; + root = api_add_uint64(root, "Total hashes", &ghs, true); + root = api_add_uint64(root, "Total raw hashes", &info->tot_hashes, false); + root = api_add_uint64(root, "Total share hashes", &info->tot_share_hashes, false); + root = api_add_uint64(root, "Total flushed hashes", &info->tot_flushed_hashes, false); + val = cgpu->diff_accepted * 0x100000000ull; + root = api_add_uint64(root, "Accepted hashes", &val, true); + ghs = val / dev_runtime; + root = api_add_uint64(root, "Accepted hashrate", &ghs, true); + val = cgpu->diff_rejected * 0x100000000ull; + root = api_add_uint64(root, "Rejected hashes", &val, true); + ghs = val / dev_runtime; + root = api_add_uint64(root, "Rejected hashrate", &ghs, true); + + cgtime(&now); + dev_runtime = tdiff(&now, &info->core_hash_start); + if (dev_runtime < 1) + dev_runtime = 1; + for (i = 0; i < CTA_CORES; i++) { + sprintf(buf, "Core%d hashrate", i); + ghs = info->tot_core_hashes[i] / dev_runtime; + root = api_add_uint64(root, buf, &ghs, true); + } + root = api_add_uint32(root, "Uptime",&info->uptime,false); + for (asic = 0; asic < 2; asic++) { + for (core = 0; core < 4; core++) { + char bitmapcount[40], asiccore[12]; + int count = 0; + + sprintf(asiccore, "Asic%dCore%d", asic, core); + __bin2hex(bitmaphex, &info->pipe_bitmap[coreno], 16); + for (i = coreno; i < coreno + 16; i++) + count += bits_set(info->pipe_bitmap[i]); + snprintf(bitmapcount, 40, "%d:%s", count, bitmaphex); + root = api_add_string(root, asiccore, bitmapcount, true); + coreno += 16; + } + } + root = api_add_uint8(root, "AV", &info->autovoltage, false); + root = api_add_uint8(root, "Power Supply Percent", &info->current_ps_percent, false); + //if (info->power_used != 0) { + { + double value = info->power_used/100.0; + + value *= (info->power_voltage/100.0); + root = api_add_double(root, "Power Used", &value, true); + } + root = api_add_uint16(root, "IOUT", &info->power_used, false); + root = api_add_uint16(root, "VOUT", &info->power_voltage, false); + root = api_add_uint16(root, "IIN", &info->ipower_used, false); + root = api_add_uint16(root, "VIN", &info->ipower_voltage, false); + root = api_add_uint16(root, "PSTemp1", &info->power_temps[0], false); + root = api_add_uint16(root, "PSTemp2", &info->power_temps[1], false); + //} + + for (core = 0; core < CTA_CORES; core++) { + char name[20]; + char str[20]; + double value; + + sprintf(name,"IRVIN%d",core+1); + value = info->irstat_vin[core]/100.0; + root = api_add_double(root,name,&value,true); + sprintf(name,"IRIIN%d",core+1); + value = info->irstat_iin[core]/100.0; + root = api_add_double(root,name,&value,true); + sprintf(name,"IRVOUT%d",core+1); + value = info->irstat_vout[core]/100.0; + root = api_add_double(root,name,&value,true); + sprintf(name,"IRIOUT%d",core+1); + value = info->irstat_iout[core]/100.0; + root = api_add_double(root,name,&value,true); + sprintf(name,"IRTEMP1_%d",core+1); + value = info->irstat_temp1[core]/100.0; + root = api_add_double(root,name,&value,true); + sprintf(name,"IRTEMP2_%d",core+1); + value = info->irstat_temp2[core]/100.0; + root = api_add_double(root,name,&value,true); + sprintf(name,"IRPOUT%d",core+1); + value = info->irstat_pout[core]/100.0; + root = api_add_double(root,name,&value,true); + sprintf(name,"IRPIN%d",core+1); + value = info->irstat_pin[core]/100.0; + root = api_add_double(root,name,&value,true); + sprintf(name,"IREFFICIENCY%d",core+1); + value = info->irstat_efficiency[core]/100.0; + root = api_add_double(root,name,&value,true); + sprintf(name,"IRSTATUS%d",core+1); + //root = api_add_uint16(root,name,&info->irstat_status[core],false); + sprintf(str,"0x%04X",info->irstat_status[core]); + root = api_add_string(root, name, str, true); + } + + for (i = 0; i < CTA_CORES; i++) { + sprintf(buf, "CoreFmatch%d", i); + root = api_add_uint16(root, buf, &info->fmatch_errors[i], false); + } + + return root; +} + +static void cta_statline_before(char *buf, size_t bufsiz, struct cgpu_info *cointerra) +{ + struct cointerra_info *info = cointerra->device_data; + double max_volt = 0; + int freq = 0, i; + + for (i = 0; i < CTA_CORES; i++) { + if (info->corevolts[i] > max_volt) + max_volt = info->corevolts[i]; + if (info->corefreqs[i] > freq) + freq = info->corefreqs[i]; + } + max_volt /= 1000; + + tailsprintf(buf, bufsiz, "%3dMHz %3.1fC %3.2fV", freq, cointerra->temp, max_volt); +} + +struct device_drv cointerra_drv = { + .drv_id = DRIVER_cointerra, + .dname = "cointerra", + .name = "CTA", + .drv_detect = cta_detect, + .thread_prepare = cta_prepare, + .hash_work = hash_queued_work, + .queue_full = cta_fill, + .update_work = cta_update_work, + .scanwork = cta_scanwork, + .flush_work = cta_wake, + .get_api_stats = cta_api_stats, + .get_statline_before = cta_statline_before, + .thread_shutdown = cta_shutdown, + .zero_stats = cta_zero_stats, + .max_diff = 64, // Set it below the actual limit to check nonces +}; diff --git a/driver-cointerra.h b/driver-cointerra.h new file mode 100644 index 0000000..6f0649c --- /dev/null +++ b/driver-cointerra.h @@ -0,0 +1,251 @@ +/* + * Copyright 2013-2014 Con Kolivas + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#ifndef COINTERRA_H +#define COINTERRA_H + +#define CTA_READBUF_SIZE 8192 +#define CTA_MSG_SIZE 64 +#define CTA_READ_TIMEOUT 1 +#define CTA_READ_INTERVAL 100 +#define CTA_SCAN_INTERVAL 500 +#define CTA_RESET_TIMEOUT 1000 + +#define CTA_INIT_DIFF 32*0.9999847412109375 + +#if 0 +/* FIXME: how big should this be? */ +#define CTA_MAX_QUEUE 2300 +#else +#define CTA_MAX_QUEUE (32 / CTA_NROLL_TIME) +#endif + +#define CTA_NROLL_TIME 2 + +/* Offsets into buffer */ +#define CTA_MSG_TYPE 2 +#define CTA_RESET_TYPE 3 +#define CTA_RESET_DIFF 4 +#define CTA_RESET_LOAD 5 +#define CTA_RESET_PSLOAD 6 +#define CTA_DRIVER_TAG 3 +#define CTA_MCU_TAG 5 +#define CTA_MCU_CORE 5 +#define CTA_MCU_ASIC 6 +#define CTA_MCU_PIPE 8 +#define CTA_MATCH_NOFFSET 45 +#define CTA_MATCH_NONCE 60 +#define CTA_WDONE_NONCES 11 +#define CTA_MSG_RECVD 3 +#define CTA_WORK_MIDSTATE 9 +#define CTA_WORK_DATA 41 +#define CTA_WORK_NROLL 53 +#define CTA_WORK_DIFFBITS 55 +#define CTA_REQWORK_REQUESTS 3 +#define CTA_CORE_HASHRATES 3 + +/* Received message types */ +#define CTA_RECV_UNUSED 0 +#define CTA_RECV_REQWORK 1 +#define CTA_RECV_MATCH 2 +#define CTA_RECV_WDONE 3 +#define CTA_RECV_STATREAD 4 +#define CTA_RECV_STATSET 5 +#define CTA_RECV_INFO 6 +#define CTA_RECV_MSG 7 +#define CTA_RECV_RDONE 8 +#define CTA_RECV_STATDEBUG 10 +#define CTA_RECV_IRSTAT 11 + +/* Sent message types */ +#define CTA_SEND_UNUSED 0 +#define CTA_SEND_RESET 1 +#define CTA_SEND_WORK 2 +#define CTA_SEND_SETPERF 3 +#define CTA_SEND_REQUEST 4 +#define CTA_SEND_FMATCH 5 +#define CTA_SEND_IDENTIFY 6 +#define CTA_SEND_COREHASHRATE 7 + +/* Types of reset in CTA_RESET_TYPE */ +#define CTA_RESET_NONE 0 +#define CTA_RESET_UPDATE 1 +#define CTA_RESET_NEW 2 +#define CTA_RESET_INIT 3 + +#define CTA_INFO_HWREV 3 +#define CTA_INFO_SERNO 5 +#define CTA_INFO_NUMASICS 9 +#define CTA_INFO_NUMDIES 10 +#define CTA_INFO_NUMCORES 11 +#define CTA_INFO_BOARDNUMBER 13 +#define CTA_INFO_FWREV_MAJ 19 +#define CTA_INFO_FWREV_MIN 20 +#define CTA_INFO_FWREV_MIC 21 +#define CTA_INFO_FWDATE_YEAR 23 +#define CTA_INFO_FWDATE_MONTH 25 +#define CTA_INFO_FWDATE_DAY 26 +#define CTA_INFO_INITDIFFBITS 27 +#define CTA_INFO_MINDIFFBITS 28 +#define CTA_INFO_MAXDIFFBITS 29 + +#define CTA_STAT_CORETEMPS 3 +#define CTA_STAT_AMBTEMP_LOW 19 +#define CTA_STAT_AMBTEMP_AVG 21 +#define CTA_STAT_AMBTEMP_HIGH 23 +#define CTA_STAT_PUMP_TACHS 25 +#define CTA_STAT_FAN_TACHS 29 +#define CTA_STAT_CORE_VOLTS 37 +#define CTA_STAT_VOLTS33 53 +#define CTA_STAT_VOLTS12 55 +#define CTA_STAT_INACTIVE 57 +#define CTA_STAT_ACTIVE 59 + +#define CTA_STAT_PERFMODE 3 +#define CTA_STAT_FANSPEEDS 11 +#define CTA_STAT_DIES_ACTIVE 15 +#define CTA_STAT_PIPES_ENABLED 16 +#define CTA_STAT_MIN_FAN_SPEED 24 +#define CTA_STAT_UPTIME 25 +#define CTA_STAT_HEARTBEATS 29 +#define CTA_STAT_CORE_FREQS 45 + +#define CTA_STAT_UNDERRUNS 3 +#define CTA_STAT_HW_ERRORS 5 +#define CTA_STAT_UPTIME_MS 21 +#define CTA_STAT_HASHES 25 +#define CTA_STAT_FLUSHED_HASHES 33 +#define CTA_STAT_AUTOVOLTAGE 41 +#define CTA_STAT_POWER_PERCENT 42 +#define CTA_STAT_POWER_USED 43 +#define CTA_STAT_VOLTAGE 45 +#define CTA_STAT_IPOWER_USED 47 +#define CTA_STAT_IVOLTAGE 49 +#define CTA_STAT_PS_TEMP1 51 +#define CTA_STAT_PS_TEMP2 53 + +#define CTA_IRSTAT_CHANNEL 3 +#define CTA_IRSTAT_VIN 4 +#define CTA_IRSTAT_IIN 6 +#define CTA_IRSTAT_VOUT 8 +#define CTA_IRSTAT_IOUT 10 +#define CTA_IRSTAT_TEMP1 12 +#define CTA_IRSTAT_TEMP2 14 +#define CTA_IRSTAT_POUT 16 +#define CTA_IRSTAT_PIN 18 +#define CTA_IRSTAT_EFF 20 +#define CTA_IRSTAT_STATUS 22 + + +#define CTA_CORES 8 +#define CTA_PUMPS 2 +#define CTA_FANS 4 + +#define CTA_REQ_MSGTYPE 3 +#define CTA_REQ_INTERVAL 5 + + +int opt_cta_load; +int opt_ps_load; + +struct cointerra_info { + /* Info data */ + uint16_t hwrev; + uint32_t serial; + uint8_t asics; + uint8_t dies; + uint16_t cores; + uint8_t board_number; + uint8_t fwrev[3]; + uint16_t fw_year; + uint8_t fw_month; + uint8_t fw_day; + uint8_t init_diffbits; + uint8_t min_diffbits; + uint8_t max_diffbits; + + /* Status readings data */ + uint16_t coretemp[CTA_CORES]; + uint16_t ambtemp_low; + uint16_t ambtemp_avg; + uint16_t ambtemp_high; + uint16_t pump_tachs[CTA_PUMPS]; + uint16_t fan_tachs[CTA_FANS]; + uint16_t corevolts[CTA_CORES]; + uint16_t volts33; + uint16_t volts12; + uint16_t inactive; + uint16_t active; + uint16_t corefreqs[CTA_CORES]; + uint32_t uptime; + + /* Status settings data */ + uint8_t coreperf[CTA_CORES]; + uint8_t fanspeed[CTA_FANS]; + uint8_t dies_active; + uint8_t pipes_enabled[CTA_CORES]; + + /* Status debug data */ + uint16_t underruns; + uint16_t hw_errors[CTA_CORES]; + uint16_t fmatch_errors[CTA_CORES]; + + /* Running total from debug messages */ + int tot_underruns; + uint16_t tot_hw_errors[CTA_CORES]; + uint64_t tot_hashes; + uint64_t tot_reset_hashes; + uint64_t tot_flushed_hashes; + uint8_t autovoltage; + uint8_t current_ps_percent; + uint16_t power_used; + uint16_t power_voltage; + uint16_t ipower_used; + uint16_t ipower_voltage; + uint16_t power_temps[2]; + + bool autovoltage_complete; + + /* Calculated totals based on work done and nonces found */ + uint64_t hashes; + uint64_t tot_calc_hashes; + + /* Calculated totals based on shares returned */ + uint64_t share_hashes; + uint64_t tot_core_hashes[CTA_CORES]; + uint64_t tot_share_hashes; + struct timeval core_hash_start; + int requested; + uint16_t work_id; + int no_matching_work; + time_t last_pipe_nonce[1024]; + unsigned char pipe_bitmap[128]; + + struct thr_info *thr; + pthread_mutex_t lock; + pthread_mutex_t sendlock; + pthread_cond_t wake_cond; + pthread_t read_thr; + cgsem_t reset_sem; + + uint16_t irstat_vin[CTA_CORES]; + uint16_t irstat_iin[CTA_CORES]; + uint16_t irstat_vout[CTA_CORES]; + uint16_t irstat_iout[CTA_CORES]; + uint16_t irstat_temp1[CTA_CORES]; + uint16_t irstat_temp2[CTA_CORES]; + uint16_t irstat_pout[CTA_CORES]; + uint16_t irstat_pin[CTA_CORES]; + uint16_t irstat_efficiency[CTA_CORES]; + uint16_t irstat_status[CTA_CORES]; + + uint64_t old_hashes[16 * 2]; +}; + +#endif /* COINTERRA_H */ diff --git a/driver-drillbit.c b/driver-drillbit.c new file mode 100644 index 0000000..9b6a2c0 --- /dev/null +++ b/driver-drillbit.c @@ -0,0 +1,1092 @@ +/* + * Copyright 2013 Con Kolivas + * Copyright 2013 Angus Gratton + * Copyright 2013 James Nichols + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#include "config.h" + +#include "miner.h" +#include "driver-drillbit.h" +#include "sha2.h" + +#define TIMEOUT 3000 +#define RESULT_TIMEOUT 5000 +#define MAX_RESULTS 16 // max results from a single chip + +#define drvlog(prio, fmt, ...) do { \ + if (drillbit->device_id == -1) { \ + applog(prio, "%s: "fmt, \ + drillbit->drv->dname, \ + ##__VA_ARGS__); \ + } else { \ + applog(prio, "%s %d: "fmt, \ + drillbit->drv->name, \ + drillbit->device_id, \ + ##__VA_ARGS__); \ + } \ +} while (0) + +/* Request and response structs for firmware */ + +typedef struct { + uint16_t chip_id; + uint8_t midstate[32]; + uint8_t data[12]; +} WorkRequest; + +#define SZ_SERIALISED_WORKREQUEST 46 +static void serialise_work_request(char *buf, uint16_t chip_id, const struct work *wr); + +typedef struct { + uint16_t chip_id; + uint8_t num_nonces; + uint8_t is_idle; + uint32_t nonce[MAX_RESULTS]; +} WorkResult; + +#define SZ_SERIALISED_WORKRESULT (4+4*MAX_RESULTS) +static void deserialise_work_result(WorkResult *work_result, const char *buf); + +/* V4 config is the preferred one, used internally, non-ASIC-specific */ +typedef struct { + uint16_t core_voltage; // Millivolts + uint16_t clock_freq; // Clock frequency in MHz (or clock level 30-48 for Bitfury internal clock level) + uint8_t clock_div2; // Apply the /2 clock divider (both internal and external), where available + uint8_t use_ext_clock; // Flag. Ignored on boards without external clocks +} BoardConfig; + +typedef struct +{ + uint16_t chip_id; + uint8_t increase_clock; +} AutoTuneRequest; + +#define SZ_SERIALISED_AUTOTUNEREQUEST 3 +static void serialise_autotune_request(char *buf, AutoTuneRequest *aq); + +#define CONFIG_PW1 (1<<0) +#define CONFIG_PW2 (1<<1) + +// Possible core voltage settings on PW1 & PW2, used by legacy V3 config only +#define CONFIG_CORE_065V 0 +#define CONFIG_CORE_075V CONFIG_PW2 +#define CONFIG_CORE_085V CONFIG_PW1 +#define CONFIG_CORE_095V (CONFIG_PW1|CONFIG_PW2) + +/* V3 config is for backwards compatibility with older firmwares */ +typedef struct { + uint8_t core_voltage; // Set to flags defined above + uint8_t int_clock_level; // Clock level (30-48 without divider), see asic.c for details + uint8_t clock_div2; // Apply the /2 clock divider (both internal and external) + uint8_t use_ext_clock; // Ignored on boards without external clocks + uint16_t ext_clock_freq; + } BoardConfigV3; + +#define SZ_SERIALISED_BOARDCONFIG 6 +static void serialise_board_configV4(char *buf, BoardConfig *boardconfig); +static void serialise_board_configV3(char *buf, BoardConfigV3 *boardconfig); + +typedef struct { + uint8_t protocol_version; + char product[8]; + uint32_t serial; + uint8_t num_chips; + uint16_t capabilities; +} Identity; + +/* Capabilities flags known to bmminer */ +#define CAP_TEMP (1<<0) +#define CAP_EXT_CLOCK (1<<1) +#define CAP_IS_AVALON (1<<2) +#define CAP_LIMITER_REMOVED (1<<3) + +#define SZ_SERIALISED_IDENTITY 16 +static void deserialise_identity(Identity *identity, const char *buf); + +// Hashable structure of per-device config settings +typedef struct { + char key[9]; + BoardConfig config; + UT_hash_handle hh; +} config_setting; + +static config_setting *settings; + +static void drillbit_empty_buffer(struct cgpu_info *drillbit); + +/* Automatic tuning parameters */ +static uint32_t auto_every = 100; +static uint32_t auto_good = 1; +static uint32_t auto_bad = 3; +static uint32_t auto_max = 10; + +/* Return a pointer to the chip_info structure for a given chip id, or NULL otherwise */ +static struct drillbit_chip_info *find_chip(struct drillbit_info *info, uint16_t chip_id) { + int i; + + for (i = 0; i < info->num_chips; i++) { + if (info->chips[i].chip_id == chip_id) + return &info->chips[i]; + } + return NULL; +} + +/* Read a fixed size buffer back from USB, returns true on success */ +static bool usb_read_fixed_size(struct cgpu_info *drillbit, void *result, size_t result_size, int timeout, enum usb_cmds command_name) { + char *res = (char *)result; + int ms_left; + size_t count; + struct timeval tv_now, tv_start; + int amount; + + cgtime(&tv_start); + ms_left = timeout; + + amount = 1; + count = 0; + while (count < result_size && ms_left > 0) { + usb_read_timeout(drillbit, &res[count], result_size-count, &amount, ms_left, command_name); + count += amount; + cgtime(&tv_now); + ms_left = timeout - ms_tdiff(&tv_now, &tv_start); + } + if (count == result_size) { + return true; + } + drvlog(LOG_ERR, "Read incomplete fixed size packet - got %d bytes / %d (timeout %d)", + (int)count, (int)result_size, timeout); + drillbit_empty_buffer(drillbit); + return false; +} + +static bool usb_read_simple_response(struct cgpu_info *drillbit, char command, enum usb_cmds command_name); + +/* Write a simple one-byte command and expect a simple one-byte response + Returns true on success +*/ +static bool usb_send_simple_command(struct cgpu_info *drillbit, char command, enum usb_cmds command_name) { + int amount; + + usb_write_timeout(drillbit, &command, 1, &amount, TIMEOUT, C_BF_REQWORK); + if (amount != 1) { + drvlog(LOG_ERR, "Failed to write command %c", command); + return false; + } + return usb_read_simple_response(drillbit, command, command_name); +} + +/* Read a simple single-byte response and check it matches the correct command character + Return true on success +*/ +static bool usb_read_simple_response(struct cgpu_info *drillbit, char command, enum usb_cmds command_name) { + int amount; + char response; + /* Expect a single byte, matching the command, as acknowledgement */ + usb_read_timeout(drillbit, &response, 1, &amount, TIMEOUT, command_name); + if (amount != 1) { + drvlog(LOG_ERR, "Got no response to command %c", command); + return false; + } + if (response != command) { + drvlog(LOG_ERR, "Got unexpected response %c to command %c", response, command); + return false; + } + return true; +} + +#define EMPTY_TIMEOUT 5 + +static void drillbit_empty_buffer(struct cgpu_info *drillbit) +{ + char buf[512]; + int amount; + + do { + usb_read_timeout(drillbit, buf, sizeof(buf), &amount, EMPTY_TIMEOUT, C_BF_FLUSH); + } while (amount); +} + +static void drillbit_open(struct cgpu_info *drillbit) +{ + drillbit_empty_buffer(drillbit); +} + +static void drillbit_close(struct cgpu_info *drillbit) +{ + struct drillbit_info *info = drillbit->device_data; + drillbit_empty_buffer(drillbit); + if (info->chips) + free(info->chips); +} + +static void drillbit_identify(struct cgpu_info *drillbit) +{ + usb_send_simple_command(drillbit, 'L', C_BF_IDENTIFY); +} + +#define ID_TIMEOUT 1000 + +static bool drillbit_getinfo(struct cgpu_info *drillbit, struct drillbit_info *info) +{ + int err; + int amount; + char buf[SZ_SERIALISED_IDENTITY]; + Identity identity; + + drillbit_empty_buffer(drillbit); + err = usb_write_timeout(drillbit, "I", 1, &amount, TIMEOUT, C_BF_REQINFO); + if (err) { + drvlog(LOG_INFO, "Failed to write REQINFO"); + return false; + } + // can't call usb_read_fixed_size here as stats not initialised + err = usb_read_timeout(drillbit, buf, SZ_SERIALISED_IDENTITY, &amount, ID_TIMEOUT, C_BF_GETINFO); + if (err) { + drvlog(LOG_ERR, "Failed to read GETINFO"); + return false; + } + if (amount != SZ_SERIALISED_IDENTITY) { + drvlog(LOG_ERR, "Getinfo received %d bytes instead of %d", + amount, (int)sizeof(Identity)); + return false; + } + deserialise_identity(&identity, buf); + + // sanity checks on the identity buffer we get back + if (strlen(identity.product) == 0 || identity.serial == 0 || identity.num_chips == 0) { + drvlog(LOG_ERR, "Got invalid contents for GETINFO identity response"); + return false; + } + + const int MIN_VERSION = 2; + const int MAX_VERSION = 4; + if (identity.protocol_version < MIN_VERSION) { + drvlog(LOG_ERR, "Unknown device protocol version %d.", identity.protocol_version); + return false; + } + if (identity.protocol_version > MAX_VERSION) { + drvlog(LOG_ERR, "Device firmware uses newer Drillbit protocol %d. We only support up to %d. Find a newer cgminer!", identity.protocol_version, MAX_VERSION); + return false; + } + + if (identity.protocol_version == 2 && identity.num_chips == 1) { + // Production firmware Thumbs don't set any capability bits, so fill in the EXT_CLOCK one + identity.capabilities = CAP_EXT_CLOCK; + } + + // load identity data into device info structure + info->protocol_version = identity.protocol_version; + if (strncmp(identity.product, "DRILLBIT", sizeof(identity.product)) == 0) { + // Hack: first production firmwares all described themselves as DRILLBIT, so fill in the gaps + if (identity.num_chips == 1) + strcpy(info->product, "Thumb"); + else + strcpy(info->product, "Eight"); + } else { + memcpy(info->product, identity.product, sizeof(identity.product)); + } + info->serial = identity.serial; + info->num_chips = identity.num_chips; + info->capabilities = identity.capabilities; + + drvlog(LOG_INFO, "Getinfo returned version %d, product %s serial %08x num_chips %d", + info->protocol_version, info->product, info->serial, info->num_chips); + + drillbit_empty_buffer(drillbit); + return true; +} + +static bool drillbit_reset(struct cgpu_info *drillbit) +{ + struct drillbit_info *info = drillbit->device_data; + struct drillbit_chip_info *chip; + int i, k, res; + + res = usb_send_simple_command(drillbit, 'R', C_BF_REQRESET); + + for (i = 0; i < info->num_chips; i++) { + chip = &info->chips[i]; + chip->state = IDLE; + chip->work_sent_count = 0; + for (k = 0; k < WORK_HISTORY_LEN-1; k++) { + if (chip->current_work[k]) { + work_completed(drillbit, chip->current_work[k]); + chip->current_work[k] = NULL; + } + } + } + + drillbit_empty_buffer(drillbit); + return res; +} + +static config_setting *find_settings(struct cgpu_info *drillbit) +{ + struct drillbit_info *info = drillbit->device_data; + config_setting *setting; + char search_key[9]; + + if (!settings) { + drvlog(LOG_INFO, "Keeping onboard defaults for device %s (serial %08x)", + info->product, info->serial); + return NULL; + } + + // Search by serial + sprintf(search_key, "%08x", info->serial); + HASH_FIND_STR(settings, search_key, setting); + if (setting) { + drvlog(LOG_INFO, "Using serial specific settings for serial %s", search_key); + return setting; + } + + // Search by DRBxxx + snprintf(search_key, 9, "DRB%d", drillbit->device_id); + HASH_FIND_STR(settings, search_key, setting); + if (setting) { + drvlog(LOG_INFO, "Using device_id specific settings for device"); + return setting; + } + + // Failing that, search by product name + HASH_FIND_STR(settings, info->product, setting); + if (setting) { + drvlog(LOG_INFO, "Using product-specific settings for device %s", info->product); + return setting; + } + + // Search by "short" product name + snprintf(search_key, 9, "%c%d", info->product[0], info->num_chips); + HASH_FIND_STR(settings, search_key, setting); + if (setting) { + drvlog(LOG_INFO, "Using product-specific settings for device %s", info->product); + return setting; + } + + // Check for a generic/catchall drillbit-options argument (key set to NULL) + search_key[0] = 0; + HASH_FIND_STR(settings, search_key, setting); + if (setting) { + drvlog(LOG_INFO, "Using non-specific settings for device %s (serial %08x)", info->product, + info->serial); + return setting; + } + + drvlog(LOG_WARNING, "Keeping onboard defaults for device %s (serial %08x)", + info->product, info->serial); + return NULL; +} + +static void drillbit_send_config(struct cgpu_info *drillbit) +{ + struct drillbit_info *info = drillbit->device_data; + int amount; + char buf[SZ_SERIALISED_BOARDCONFIG+1]; + config_setting *setting; + BoardConfigV3 v3_config; + + // Find the relevant board config + setting = find_settings(drillbit); + if (!setting) + return; // Don't update board config from defaults + drvlog(LOG_NOTICE, "Config: %s:%d:%d:%d Serial: %08x", + setting->config.use_ext_clock ? "ext" : "int", + setting->config.clock_freq, + setting->config.clock_div2 ? 2 : 1, + setting->config.core_voltage, + info->serial); + + if (setting->config.use_ext_clock && !(info->capabilities & CAP_EXT_CLOCK)) { + drvlog(LOG_WARNING, "Chosen configuration specifies external clock but this device (serial %08x) has no external clock!", info->serial); + } + + if (info->protocol_version <= 3) { + /* Make up a backwards compatible V3 config structure to send to the miner */ + if (setting->config.core_voltage >= 950) + v3_config.core_voltage = CONFIG_CORE_095V; + else if (setting->config.core_voltage >= 850) + v3_config.core_voltage = CONFIG_CORE_085V; + else if (setting->config.core_voltage >= 750) + v3_config.core_voltage = CONFIG_CORE_075V; + else + v3_config.core_voltage = CONFIG_CORE_065V; + if (setting->config.clock_freq > 64) + v3_config.int_clock_level = setting->config.clock_freq / 5; + else + v3_config.int_clock_level = setting->config.clock_freq; + v3_config.clock_div2 = setting->config.clock_div2; + v3_config.use_ext_clock = setting->config.use_ext_clock; + v3_config.ext_clock_freq = setting->config.clock_freq; + serialise_board_configV3(&buf[1], &v3_config); + } else { + serialise_board_configV4(&buf[1], &setting->config); + } + buf[0] = 'C'; + usb_write_timeout(drillbit, buf, sizeof(buf), &amount, TIMEOUT, C_BF_CONFIG); + + /* Expect a single 'C' byte as acknowledgement */ + usb_read_simple_response(drillbit, 'C', C_BF_CONFIG); // TODO: verify response +} + +static void drillbit_updatetemps(struct thr_info *thr) +{ + struct cgpu_info *drillbit = thr->cgpu; + struct drillbit_info *info = drillbit->device_data; + char cmd; + int amount; + uint16_t temp; + struct timeval tv_now; + + if (!(info->capabilities & CAP_TEMP)) + return; + + cgtime(&tv_now); + if (ms_tdiff(&tv_now, &info->tv_lasttemp) < 1000) + return; // Only update temps once a second + info->tv_lasttemp = tv_now; + + cmd = 'T'; + usb_write_timeout(drillbit, &cmd, 1, &amount, TIMEOUT, C_BF_GETTEMP); + + if (!usb_read_fixed_size(drillbit, &temp, sizeof(temp), TIMEOUT, C_BF_GETTEMP)) { + drvlog(LOG_ERR, "Got no response to request for current temperature"); + return; + } + + drvlog(LOG_INFO, "Got temperature reading %d.%dC", temp/10, temp%10); + info->temp = temp; + if (temp > info->max_temp) + info->max_temp = temp; +} + +static void drillbit_get_statline_before(char *buf, size_t bufsiz, struct cgpu_info *drillbit) +{ + struct drillbit_info *info = drillbit->device_data; + + if ((info->capabilities & CAP_TEMP) && info->temp != 0) { + tailsprintf(buf, bufsiz, "%c%2d %.1fC max%.1fC", + info->product[0], + info->num_chips, + (float)(info->temp/10.0), + (float)(info->max_temp/10.0)); + } else { + tailsprintf(buf, bufsiz, "%c%2d", + info->product[0], + info->num_chips); + } +} + + +static bool drillbit_parse_options(__maybe_unused struct cgpu_info *drillbit) +{ + /* Read configuration options (currently global not per-ASIC or per-board) */ + if (settings != NULL) + return true; // Already initialised + + char *next_opt = opt_drillbit_options; + while (next_opt && strlen(next_opt)) { + BoardConfig parsed_config; + config_setting *new_setting; + char key[9]; + int count, freq, clockdiv, voltage; + char clksrc[4]; + + // Try looking for an option tagged with a key, first + count = sscanf(next_opt, "%8[^:]:%3s:%d:%d:%d", key, + clksrc, &freq, &clockdiv, &voltage); + if (count < 5) { + key[0] = 0; + count = sscanf(next_opt, "%3s:%d:%d:%d", + clksrc, &freq, &clockdiv, &voltage); + if (count < 4) { + quithere(1, "Failed to parse drillbit-options. Invalid options string: '%s'", next_opt); + } + } + + if (clockdiv != 1 && clockdiv != 2) { + quithere(1, "Invalid clock divider value %d. Valid values are 1 & 2.", clockdiv); + } + parsed_config.clock_div2 = count > 2 && clockdiv == 2; + + if (!strcmp("int",clksrc)) { + parsed_config.use_ext_clock = 0; + } + else if (!strcmp("ext", clksrc)) { + parsed_config.use_ext_clock = 1; + } else + quithere(1, "Invalid clock source. Valid choices are int, ext."); + + parsed_config.clock_freq = freq; + parsed_config.core_voltage = voltage; + + // Add the new set of settings to the configuration choices hash table + new_setting = (config_setting *)calloc(sizeof(config_setting), 1); + memcpy(&new_setting->config, &parsed_config, sizeof(BoardConfig)); + memcpy(&new_setting->key, key, 8); + config_setting *ignore; + HASH_REPLACE_STR(settings, key, new_setting, ignore); + + // Look for next comma-delimited Drillbit option + next_opt = strstr(next_opt, ","); + if (next_opt) + next_opt++; + } + + if (opt_drillbit_auto) { + sscanf(opt_drillbit_auto, "%d:%d:%d:%d", + &auto_every, &auto_good, &auto_bad, &auto_max); + if (auto_max < auto_bad) { + quithere(1, "Bad drillbit-auto: MAX limit must be greater than BAD limit"); + } + if (auto_bad < auto_good) { + quithere(1, "Bad drillbit-auto: GOOD limit must be greater than BAD limit"); + } + } + + return true; +} + +static struct cgpu_info *drillbit_detect_one(struct libusb_device *dev, struct usb_find_devices *found) +{ + struct cgpu_info *drillbit; + struct drillbit_info *info; + int i; + + drillbit = usb_alloc_cgpu(&drillbit_drv, 1); + drillbit->device_id = -1; // so drvlog() prints dname + + if (!drillbit_parse_options(drillbit)) + goto out; + + if (!usb_init(drillbit, dev, found)) + goto out; + + drvlog(LOG_INFO, "Device found at %s", drillbit->device_path); + + info = calloc(sizeof(struct drillbit_info), 1); + if (!info) + quit(1, "Failed to calloc info in %s", __func__); + drillbit->device_data = info; + + drillbit_open(drillbit); + + /* Send getinfo request */ + if (!drillbit_getinfo(drillbit, info)) + goto out_close; + + /* TODO: Add detection for actual chip ids based on command/response, + not prefill assumption about chip layout based on info structure */ + info->chips = calloc(sizeof(struct drillbit_chip_info), info->num_chips); + for (i = 0; i < info->num_chips; i++) { + info->chips[i].chip_id = i; + info->chips[i].auto_max = 999; + } + + /* Send reset request */ + if (!drillbit_reset(drillbit)) + goto out_close; + + drillbit_identify(drillbit); + drillbit_empty_buffer(drillbit); + + cgtime(&info->tv_lastchipinfo); + + if (!add_cgpu(drillbit)) + goto out_close; + + update_usb_stats(drillbit); + + if (info->capabilities & CAP_LIMITER_REMOVED) { + drvlog(LOG_WARNING, "Recommended limits have been disabled on this board, take care when changing settings."); + } + + drillbit_send_config(drillbit); + + drvlog(LOG_INFO, "Successfully initialised %s", + drillbit->device_path); + + return drillbit; +out_close: + drillbit_close(drillbit); + usb_uninit(drillbit); +out: + drillbit = usb_free_cgpu(drillbit); + return drillbit; +} + +static void drillbit_detect(bool __maybe_unused hotplug) +{ + usb_detect(&drillbit_drv, drillbit_detect_one); +} + +static uint32_t decnonce(uint32_t in) +{ + uint32_t out; + + /* First part load */ + out = (in & 0xFF) << 24; in >>= 8; + + /* Byte reversal */ + in = (((in & 0xaaaaaaaa) >> 1) | ((in & 0x55555555) << 1)); + in = (((in & 0xcccccccc) >> 2) | ((in & 0x33333333) << 2)); + in = (((in & 0xf0f0f0f0) >> 4) | ((in & 0x0f0f0f0f) << 4)); + + out |= (in >> 2)&0x3FFFFF; + + /* Extraction */ + if (in & 1) out |= (1 << 23); + if (in & 2) out |= (1 << 22); + + out -= 0x800004; + return out; +} + +#define BF_OFFSETS 3 +static const uint32_t bf_offsets[] = {-0x800000, 0, -0x400000}; + +static bool drillbit_checkresults(struct thr_info *thr, struct work *work, uint32_t nonce) +{ + struct cgpu_info *drillbit = thr->cgpu; + struct drillbit_info *info = drillbit->device_data; + int i; + + if (info->capabilities & CAP_IS_AVALON) { + if (test_nonce(work, nonce)) { + submit_tested_work(thr, work); + return true; + } + } + else { /* Bitfury */ + nonce = decnonce(nonce); + for (i = 0; i < BF_OFFSETS; i++) { + if (test_nonce(work, nonce + bf_offsets[i])) { + submit_tested_work(thr, work); + return true; + } + } + } + return false; +} + +/* Check if this ASIC should be tweaked up or down in clock speed */ +static void drillbit_check_auto(struct thr_info *thr, struct drillbit_chip_info *chip) +{ + struct cgpu_info *drillbit = thr->cgpu; + AutoTuneRequest request; + char buf[SZ_SERIALISED_AUTOTUNEREQUEST+1]; + int amount; + bool tune_up, tune_down; + + /* + Only check automatic tuning every "auto_every" work units, + or if the error count exceeds the 'max' count + */ + if (chip->success_auto + chip->error_auto < auto_every && + (chip->error_auto < auto_max)) + return; + + tune_up = chip->error_auto < auto_good && chip->auto_delta < chip->auto_max; + tune_down = chip->error_auto > auto_bad; + + + drvlog(tune_up||tune_down ? LOG_NOTICE : LOG_DEBUG, + "Chip id %d has %d/%d error rate %s", chip->chip_id, chip->error_auto, + chip->error_auto + chip->success_auto, + tune_up ? " - tuning up" : tune_down ? " - tuning down" : " - no change"); + + if (tune_up || tune_down) { + /* Value should be tweaked */ + buf[0] = 'A'; + request.chip_id = chip->chip_id; + request.increase_clock = tune_up; + serialise_autotune_request(&buf[1], &request); + usb_write_timeout(drillbit, buf, sizeof(buf), &amount, TIMEOUT, C_BF_AUTOTUNE); + usb_read_simple_response(drillbit, 'A', C_BF_AUTOTUNE); + if (tune_up) { + chip->auto_delta++; + } else { + chip->auto_delta--; + if (chip->error_auto >= auto_max + && chip->success_count + chip->error_count > auto_every) { + drvlog(LOG_ERR, "Chip id %d capping auto delta at max %d",chip->chip_id, + chip->auto_delta); + chip->auto_max = chip->auto_delta; + } + } + } + + chip->success_auto = 0; + chip->error_auto = 0; +} + +// Check and submit back any pending work results from firmware, +// returns number of successful results found +static int check_for_results(struct thr_info *thr) +{ + struct cgpu_info *drillbit = thr->cgpu; + struct drillbit_info *info = drillbit->device_data; + struct drillbit_chip_info *chip; + char cmd; + int amount, i, k, found; + uint8_t j; + int successful_results = 0; + uint32_t result_count; + char buf[SZ_SERIALISED_WORKRESULT]; + WorkResult *responses = NULL; + WorkResult *response; + + if (unlikely(thr->work_restart)) + goto cleanup; + + // Send request for completed work + cmd = 'E'; + usb_write_timeout(drillbit, &cmd, 1, &amount, TIMEOUT, C_BF_GETRES); + + // Receive count for work results + if (!usb_read_fixed_size(drillbit, &result_count, sizeof(result_count), TIMEOUT, C_BF_GETRES)) { + drvlog(LOG_ERR, "Got no response to request for work results"); + goto cleanup; + } + if (unlikely(drillbit->usbinfo.nodev)) + goto cleanup; + if (result_count) + drvlog(LOG_DEBUG, "Result count %d",result_count); + + if (result_count > 1024) { + drvlog(LOG_ERR, "Got implausible result count %d - treating as error!", result_count); + goto cleanup; + } + + if (result_count == 0) { + // Short circuit reading any work results + return 0; + } + + responses = calloc(result_count, sizeof(WorkResult)); + + // Receive work results (0 or more) into buffer + for (j = 0; j < result_count; j++) { + if (unlikely(drillbit->usbinfo.nodev)) + goto cleanup; + if (!usb_read_fixed_size(drillbit, buf, SZ_SERIALISED_WORKRESULT, TIMEOUT, C_BF_GETRES)) { + drvlog(LOG_ERR, "Failed to read response data packet idx %d count 0x%x", j, result_count); + drillbit_empty_buffer(drillbit); + goto cleanup; + } + deserialise_work_result(&responses[j], buf); + } + + for (j = 0; j < result_count; j++) { + if (unlikely(thr->work_restart)) + goto cleanup; + + response = &responses[j]; + drvlog(LOG_DEBUG, "Got response packet chip_id %d nonces %d is_idle %d", response->chip_id, response->num_nonces, response->is_idle); + chip = find_chip(info, response->chip_id); + if (!chip) { + drvlog(LOG_ERR, "Got work result for unknown chip id %d", response->chip_id); + drillbit_empty_buffer(drillbit); + continue; + } + if (chip->state == IDLE) { + drvlog(LOG_WARNING, "Got spurious work results for idle ASIC %d", response->chip_id); + } + if (response->num_nonces > MAX_RESULTS) { + drvlog(LOG_ERR, "Got invalid number of result nonces (%d) for chip id %d", response->num_nonces, response->chip_id); + drillbit_empty_buffer(drillbit); + goto cleanup; + } + + found = false; + for (i = 0; i < response->num_nonces; i++) { + if (unlikely(thr->work_restart)) + goto cleanup; + for (k = 0; k < WORK_HISTORY_LEN; k++) { + /* NB we deliberately check all results against all work because sometimes ASICs seem to give multiple "valid" nonces, + and this seems to avoid some result that would otherwise be rejected by the pool. + */ + if (chip->current_work[k] && drillbit_checkresults(thr, chip->current_work[k], response->nonce[i])) { + chip->success_count++; + chip->success_auto++; + successful_results++; + found = true; + } + } + } + drvlog(LOG_DEBUG, "%s nonce %08x", (found ? "Good":"Bad"), response->num_nonces ? response->nonce[0] : 0); + if (!found && chip->state != IDLE && response->num_nonces > 0) { + /* all nonces we got back from this chip were invalid */ + inc_hw_errors(thr); + chip->error_count++; + chip->error_auto++; + } + if (chip->state == WORKING_QUEUED && !response->is_idle) + chip->state = WORKING_NOQUEUED; // Time to queue up another piece of "next work" + else + chip->state = IDLE; // Uh-oh, we're totally out of work for this ASIC! + + if (opt_drillbit_auto && info->protocol_version >= 4) + drillbit_check_auto(thr, chip); + } + +cleanup: + if (responses) + free(responses); + return successful_results; +} + +static void drillbit_send_work_to_chip(struct thr_info *thr, struct drillbit_chip_info *chip) +{ + struct cgpu_info *drillbit = thr->cgpu; + struct work *work; + char buf[SZ_SERIALISED_WORKREQUEST+1]; + int amount, i; + + /* Get some new work for the chip */ + work = get_queue_work(thr, drillbit, thr->id); + if (unlikely(thr->work_restart)) { + work_completed(drillbit, work); + return; + } + + drvlog(LOG_DEBUG, "Sending work to chip_id %d", chip->chip_id); + serialise_work_request(&buf[1], chip->chip_id, work); + + /* Send work to cgminer */ + buf[0] = 'W'; + usb_write_timeout(drillbit, buf, sizeof(buf), &amount, TIMEOUT, C_BF_REQWORK); + + /* Expect a single 'W' byte as acknowledgement */ + usb_read_simple_response(drillbit, 'W', C_BF_REQWORK); + if (chip->state == WORKING_NOQUEUED) + chip->state = WORKING_QUEUED; + else + chip->state = WORKING_NOQUEUED; + + if (unlikely(thr->work_restart)) { + work_completed(drillbit, work); + return; + } + + // Read into work history + if (chip->current_work[0]) + work_completed(drillbit, chip->current_work[0]); + for (i = 0; i < WORK_HISTORY_LEN-1; i++) + chip->current_work[i] = chip->current_work[i+1]; + chip->current_work[WORK_HISTORY_LEN-1] = work; + cgtime(&chip->tv_start); + + chip->work_sent_count++; +} + +static int64_t drillbit_scanwork(struct thr_info *thr) +{ + struct cgpu_info *drillbit = thr->cgpu; + struct drillbit_info *info = drillbit->device_data; + struct drillbit_chip_info *chip; + struct timeval tv_now; + int amount, i, j, ms_diff, result_count = 0, sent_count = 0;; + char buf[200]; + + /* send work to an any chip without queued work */ + for (i = 0; i < info->num_chips && sent_count < 8; i++) { + if (info->chips[i].state != WORKING_QUEUED) { + drillbit_send_work_to_chip(thr, &info->chips[i]); + sent_count++; + } + if (unlikely(thr->work_restart) || unlikely(drillbit->usbinfo.nodev)) + goto cascade; + } + + /* check for any chips that have timed out on sending results */ + cgtime(&tv_now); + for (i = 0; i < info->num_chips; i++) { + if (info->chips[i].state == IDLE) + continue; + ms_diff = ms_tdiff(&tv_now, &info->chips[i].tv_start); + if (ms_diff > RESULT_TIMEOUT) { + if (info->chips[i].work_sent_count > 4) { + /* Only count ASIC timeouts after the pool has started to send work in earnest, + some pools can create unusual delays early on */ + drvlog(LOG_ERR, "Timing out unresponsive ASIC %d", info->chips[i].chip_id); + info->chips[i].timeout_count++; + info->chips[i].error_auto++; + } + info->chips[i].state = IDLE; + drillbit_send_work_to_chip(thr, &info->chips[i]); + } + if (unlikely(thr->work_restart) || unlikely(drillbit->usbinfo.nodev)) + goto cascade; + } + + /* Check for results */ + result_count = check_for_results(thr); + + /* Print a per-chip info line every 30 seconds */ + cgtime(&tv_now); + if (opt_log_level <= LOG_INFO && ms_tdiff(&tv_now, &info->tv_lastchipinfo) > 30000) { + /* TODO: this output line may get truncated (max debug is 256 bytes) once we get more + chips in a single device + */ + amount = sprintf(buf, "%s %d: S/E/T", drillbit->drv->name, drillbit->device_id); + if (amount > 0) { + for (i = 0; i < info->num_chips; i++) { + chip= &info->chips[i]; + j = snprintf(&buf[amount], sizeof(buf)-(size_t)amount, "%u:%u/%u/%u", + chip->chip_id, chip->success_count, chip->error_count, + chip->timeout_count); + if (j < 0) + break; + amount += j; + if ((size_t)amount >= sizeof(buf)) + break; + } + drvlog(LOG_INFO, "%s", buf); + cgtime(&info->tv_lastchipinfo); + } + } + + drillbit_updatetemps(thr); + +cascade: + if (unlikely(drillbit->usbinfo.nodev)) { + drvlog(LOG_WARNING, "Device disappeared, disabling thread"); + return -1; + } + + if (unlikely(thr->work_restart)) { + /* Issue an ASIC reset as we won't be coming back for any of these results */ + drvlog(LOG_DEBUG, "Received work restart, resetting ASIC"); + drillbit_reset(drillbit); + } + + return 0xffffffffULL * result_count; +} + +static struct api_data *drillbit_api_stats(struct cgpu_info *cgpu) +{ + struct drillbit_info *info = cgpu->device_data; + struct api_data *root = NULL; + char serial[16]; + int version; + + version = info->protocol_version; + root = api_add_int(root, "Protocol Version", &version, true); + root = api_add_string(root, "Product", info->product, false); + sprintf(serial, "%08x", info->serial); + root = api_add_string(root, "Serial", serial, true); + root = api_add_uint8(root, "ASIC Count", &info->num_chips, true); + if (info->capabilities & CAP_TEMP) { + float temp = (float)info->temp/10; + root = api_add_temp(root, "Temp", &temp, true); + temp = (float)info->max_temp/10; + root = api_add_temp(root, "Temp Max", &temp, true); + } + + return root; +} + +static void drillbit_reinit(struct cgpu_info *drillbit) +{ + drillbit_close(drillbit); + drillbit_open(drillbit); + drillbit_reset(drillbit); +} + +static void drillbit_shutdown(struct thr_info *thr) +{ + struct cgpu_info *drillbit = thr->cgpu; + + drillbit_close(drillbit); +} + +/* Currently hardcoded to BF1 devices */ +struct device_drv drillbit_drv = { + .drv_id = DRIVER_drillbit, + .dname = "Drillbit", + .name = "DRB", + .drv_detect = drillbit_detect, + .hash_work = &hash_driver_work, + .scanwork = drillbit_scanwork, + .get_api_stats = drillbit_api_stats, + .get_statline_before = drillbit_get_statline_before, + .reinit_device = drillbit_reinit, + .thread_shutdown = drillbit_shutdown, + .identify_device = drillbit_identify, +}; + + +/* Structure serialisation/deserialisation */ + +#define SERIALISE(FIELD) do { \ + memcpy(&buf[offset], &FIELD, sizeof(FIELD)); \ + offset += sizeof(FIELD); \ + } while (0) + +#define DESERIALISE(FIELD) do { \ + memcpy(&FIELD, &buf[offset], sizeof(FIELD)); \ + offset += sizeof(FIELD); \ + } while (0) + +static void serialise_work_request(char *buf, uint16_t chip_id, const struct work *work) +{ + size_t offset = 0; + SERIALISE(chip_id); + memcpy(&buf[offset], work->midstate, 32); + offset += 32; + memcpy(&buf[offset], work->data + 64, 12); + //offset += 12; +} + +static void deserialise_work_result(WorkResult *wr, const char *buf) +{ + int i; + size_t offset = 0; + DESERIALISE(wr->chip_id); + DESERIALISE(wr->num_nonces); + DESERIALISE(wr->is_idle); + for (i = 0; i < MAX_RESULTS; i++) + DESERIALISE(wr->nonce[i]); +} + +static void serialise_board_configV3(char *buf, BoardConfigV3 *bc) +{ + size_t offset = 0; + SERIALISE(bc->core_voltage); + SERIALISE(bc->int_clock_level); + SERIALISE(bc->clock_div2); + SERIALISE(bc->use_ext_clock); + SERIALISE(bc->ext_clock_freq); +} + +static void serialise_board_configV4(char *buf, BoardConfig *bc) +{ + size_t offset = 0; + SERIALISE(bc->core_voltage); + SERIALISE(bc->clock_freq); + SERIALISE(bc->clock_div2); + SERIALISE(bc->use_ext_clock); +} + +static void serialise_autotune_request(char *buf, AutoTuneRequest *aq) +{ + size_t offset = 0; + SERIALISE(aq->chip_id); + SERIALISE(aq->increase_clock); +} + +static void deserialise_identity(Identity *id, const char *buf) +{ + size_t offset = 0; + DESERIALISE(id->protocol_version); + DESERIALISE(id->product); + DESERIALISE(id->serial); + DESERIALISE(id->num_chips); + DESERIALISE(id->capabilities); +} diff --git a/driver-drillbit.h b/driver-drillbit.h new file mode 100644 index 0000000..b386107 --- /dev/null +++ b/driver-drillbit.h @@ -0,0 +1,56 @@ +/* + * Copyright 2013 Con Kolivas + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#ifndef BITFURY_H +#define BITFURY_H + +#include "miner.h" +#include "usbutils.h" + +#define WORK_HISTORY_LEN 4 + +struct drillbit_chip_info; + +/* drillbit_info structure applies to entire device */ +struct drillbit_info { + struct cgpu_info *base_cgpu; + uint8_t protocol_version; + uint8_t num_chips; + uint16_t capabilities; + char product[8]; + uint32_t serial; + struct drillbit_chip_info *chips; + struct timeval tv_lastchipinfo; + struct timeval tv_lasttemp; + uint16_t temp; + uint16_t max_temp; +}; + +enum drillbit_chip_state { + IDLE, /* Has no work */ + WORKING_NOQUEUED, /* Has current work but nothing queued as "next work" */ + WORKING_QUEUED /* Has current work and a piece of work queued for after that */ +}; + +struct drillbit_chip_info { + uint16_t chip_id; + struct work *current_work[WORK_HISTORY_LEN]; + enum drillbit_chip_state state; + struct timeval tv_start; + uint32_t success_count; + uint32_t error_count; + uint32_t timeout_count; + uint32_t work_sent_count; + uint32_t success_auto; + uint32_t error_auto; + int auto_delta; + int auto_max; +}; + +#endif /* BITFURY_H */ diff --git a/driver-hashfast.c b/driver-hashfast.c new file mode 100644 index 0000000..4175ade --- /dev/null +++ b/driver-hashfast.c @@ -0,0 +1,2066 @@ +/* + * Copyright 2013-2014 Con Kolivas + * Copyright 2013 Hashfast Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#include "config.h" + +#include +#include + +#include "miner.h" +#include "usbutils.h" + +#include "driver-hashfast.h" + +int opt_hfa_ntime_roll = 1; +int opt_hfa_hash_clock = HFA_CLOCK_DEFAULT; +int opt_hfa_overheat = HFA_TEMP_OVERHEAT; +int opt_hfa_target = HFA_TEMP_TARGET; +bool opt_hfa_pll_bypass; +bool opt_hfa_dfu_boot; +int opt_hfa_fan_default = HFA_FAN_DEFAULT; +int opt_hfa_fan_max = HFA_FAN_MAX; +int opt_hfa_fan_min = HFA_FAN_MIN; +int opt_hfa_fail_drop = 10; +bool opt_hfa_noshed; + +char *opt_hfa_name; +char *opt_hfa_options; + +//////////////////////////////////////////////////////////////////////////////// +// Support for the CRC's used in header (CRC-8) and packet body (CRC-32) +//////////////////////////////////////////////////////////////////////////////// + +#define GP8 0x107 /* x^8 + x^2 + x + 1 */ +#define DI8 0x07 + +static bool hfa_crc8_set; + +char *set_hfa_fan(char *arg) +{ + int val1, val2, ret; + + ret = sscanf(arg, "%d-%d", &val1, &val2); + if (ret < 1) + return "No values passed to hfa-fan"; + if (ret == 1) + val2 = val1; + + if (val1 < 0 || val1 > 100 || val2 < 0 || val2 > 100 || val2 < val1) + return "Invalid value passed to hfa-fan"; + + opt_hfa_fan_min = val1; + opt_hfa_fan_max = val2; + if (opt_hfa_fan_min > opt_hfa_fan_default) + opt_hfa_fan_default = opt_hfa_fan_min; + if (opt_hfa_fan_max < opt_hfa_fan_default) + opt_hfa_fan_default = opt_hfa_fan_max; + + return NULL; +} + +static unsigned char crc8_table[256]; /* CRC-8 table */ + +static void hfa_init_crc8(void) +{ + int i,j; + unsigned char crc; + + hfa_crc8_set = true; + for (i = 0; i < 256; i++) { + crc = i; + for (j = 0; j < 8; j++) + crc = (crc << 1) ^ ((crc & 0x80) ? DI8 : 0); + crc8_table[i] = crc & 0xFF; + } +} + +static unsigned char hfa_crc8(unsigned char *h) +{ + int i; + unsigned char crc; + + h++; // Preamble not included + for (i = 1, crc = 0xff; i < 7; i++) + crc = crc8_table[crc ^ *h++]; + + return crc; +} + +struct hfa_cmd { + uint8_t cmd; + char *cmd_name; + enum usb_cmds usb_cmd; +}; + +/* Entries in this array need to align with the actual op values specified + * in hf_protocol.h */ +#define C_NULL C_MAX +static const struct hfa_cmd hfa_cmds[] = { + {OP_NULL, "OP_NULL", C_NULL}, // 0 + {OP_ROOT, "OP_ROOT", C_NULL}, + {OP_RESET, "OP_RESET", C_HF_RESET}, + {OP_PLL_CONFIG, "OP_PLL_CONFIG", C_HF_PLL_CONFIG}, + {OP_ADDRESS, "OP_ADDRESS", C_HF_ADDRESS}, + {OP_READDRESS, "OP_READDRESS", C_NULL}, + {OP_HIGHEST, "OP_HIGHEST", C_NULL}, + {OP_BAUD, "OP_BAUD", C_HF_BAUD}, + {OP_UNROOT, "OP_UNROOT", C_NULL}, // 8 + {OP_HASH, "OP_HASH", C_HF_HASH}, + {OP_NONCE, "OP_NONCE", C_HF_NONCE}, + {OP_ABORT, "OP_ABORT", C_HF_ABORT}, + {OP_STATUS, "OP_STATUS", C_HF_STATUS}, + {OP_GPIO, "OP_GPIO", C_NULL}, + {OP_CONFIG, "OP_CONFIG", C_HF_CONFIG}, + {OP_STATISTICS, "OP_STATISTICS", C_HF_STATISTICS}, + {OP_GROUP, "OP_GROUP", C_NULL}, // 16 + {OP_CLOCKGATE, "OP_CLOCKGATE", C_HF_CLOCKGATE}, + + {OP_USB_INIT, "OP_USB_INIT", C_HF_USB_INIT}, // 18 + {OP_GET_TRACE, "OP_GET_TRACE", C_NULL}, + {OP_LOOPBACK_USB, "OP_LOOPBACK_USB", C_NULL}, + {OP_LOOPBACK_UART, "OP_LOOPBACK_UART", C_NULL}, + {OP_DFU, "OP_DFU", C_HF_DFU}, + {OP_USB_SHUTDOWN, "OP_USB_SHUTDOWN", C_NULL}, + {OP_DIE_STATUS, "OP_DIE_STATUS", C_HF_DIE_STATUS}, // 24 + {OP_GWQ_STATUS, "OP_GWQ_STATUS", C_HF_GWQ_STATUS}, + {OP_WORK_RESTART, "OP_WORK_RESTART", C_HF_WORK_RESTART}, + {OP_USB_STATS1, "OP_USB_STATS1", C_NULL}, + {OP_USB_GWQSTATS, "OP_USB_GWQSTATS", C_HF_GWQSTATS}, + {OP_USB_NOTICE, "OP_USB_NOTICE", C_HF_NOTICE}, + {OP_PING, "OP_PING", C_HF_PING}, + {OP_CORE_MAP, "OP_CORE_MAP", C_NULL}, + {OP_VERSION, "OP_VERSION", C_NULL}, // 32 + {OP_FAN, "OP_FAN", C_HF_FAN}, + {OP_NAME, "OP_NAME", C_OP_NAME} +}; + +#define HF_USB_CMD_OFFSET (128 - 18) +#define HF_USB_CMD(X) (X - HF_USB_CMD_OFFSET) + +/* Send an arbitrary frame, consisting of an 8 byte header and an optional + * packet body. */ +static bool __hfa_send_frame(struct cgpu_info *hashfast, uint8_t opcode, int tx_length, + uint8_t *packet) +{ + struct hashfast_info *info = hashfast->device_data; + int ret, amount; + bool retried = false; + + if (unlikely(hashfast->usbinfo.nodev)) + return false; + + info->last_send = time(NULL); + applog(LOG_DEBUG, "%s %s: Sending %s frame", hashfast->drv->name, hashfast->unique_id, hfa_cmds[opcode].cmd_name); +retry: + ret = usb_write(hashfast, (char *)packet, tx_length, &amount, + hfa_cmds[opcode].usb_cmd); + if (unlikely(ret < 0 || amount != tx_length)) { + if (hashfast->usbinfo.nodev) + return false; + if (!retried) { + applog(LOG_ERR, "%s %s: hfa_send_frame: USB Send error, ret %d amount %d vs. tx_length %d, retrying", + hashfast->drv->name, hashfast->unique_id, ret, amount, tx_length); + retried = true; + goto retry; + } + applog(LOG_ERR, "%s %s: hfa_send_frame: USB Send error, ret %d amount %d vs. tx_length %d", + hashfast->drv->name, hashfast->unique_id, ret, amount, tx_length); + return false; + } + + if (retried) + applog(LOG_WARNING, "%s %s: hfa_send_frame: recovered OK", hashfast->drv->name, hashfast->unique_id); + + return true; +} + +static bool hfa_send_generic_frame(struct cgpu_info *hashfast, uint8_t opcode, uint8_t chip_address, + uint8_t core_address, uint16_t hdata, uint8_t *data, int len) +{ + uint8_t packet[256]; + struct hf_header *p = (struct hf_header *)packet; + int tx_length, ret, amount; + + p->preamble = HF_PREAMBLE; + p->operation_code = opcode; + p->chip_address = chip_address; + p->core_address = core_address; + p->hdata = htole16(hdata); + p->data_length = len / 4; + p->crc8 = hfa_crc8(packet); + + if (len) + memcpy(&packet[sizeof(struct hf_header)], data, len); + tx_length = sizeof(struct hf_header) + len; + + ret = usb_write(hashfast, (char *)packet, tx_length, &amount, C_NULL); + + return ((ret >= 0) && (amount == tx_length)); +} + +static bool hfa_send_frame(struct cgpu_info *hashfast, uint8_t opcode, uint16_t hdata, + uint8_t *data, int len) +{ + uint8_t packet[256]; + struct hf_header *p = (struct hf_header *)packet; + int tx_length; + + p->preamble = HF_PREAMBLE; + p->operation_code = hfa_cmds[opcode].cmd; + p->chip_address = HF_GWQ_ADDRESS; + p->core_address = 0; + p->hdata = htole16(hdata); + p->data_length = len / 4; + p->crc8 = hfa_crc8(packet); + + if (len) + memcpy(&packet[sizeof(struct hf_header)], data, len); + tx_length = sizeof(struct hf_header) + len; + + return (__hfa_send_frame(hashfast, opcode, tx_length, packet)); +} + +/* Send an already assembled packet, consisting of an 8 byte header which may + * or may not be followed by a packet body. */ + +static bool hfa_send_packet(struct cgpu_info *hashfast, struct hf_header *h, int cmd) +{ + int amount, ret, len; + + if (unlikely(hashfast->usbinfo.nodev)) + return false; + + len = sizeof(*h) + h->data_length * 4; + ret = usb_write(hashfast, (char *)h, len, &amount, hfa_cmds[cmd].usb_cmd); + if (ret < 0 || amount != len) { + applog(LOG_WARNING, "%s %s: send_packet: %s USB Send error, ret %d amount %d vs. length %d", + hashfast->drv->name, hashfast->unique_id, hfa_cmds[cmd].cmd_name, ret, amount, len); + return false; + } + return true; +} + +#define HFA_GET_HEADER_BUFSIZE 512 + +static bool hfa_get_header(struct cgpu_info *hashfast, struct hf_header *h, uint8_t *computed_crc) +{ + int amount, ret, orig_len, len, ofs = 0; + cgtimer_t ts_start; + char buf[HFA_GET_HEADER_BUFSIZE]; + char *header; + + if (unlikely(hashfast->usbinfo.nodev)) + return false; + + orig_len = len = sizeof(*h); + + /* Read for up to 500ms till we find the first occurrence of HF_PREAMBLE + * though it should be the first byte unless we get woefully out of + * sync. */ + cgtimer_time(&ts_start); + do { + cgtimer_t ts_now, ts_diff; + + cgtimer_time(&ts_now); + cgtimer_sub(&ts_now, &ts_start, &ts_diff); + if (cgtimer_to_ms(&ts_diff) > 500) + return false; + + if (unlikely(hashfast->usbinfo.nodev)) + return false; + if(ofs + len > HFA_GET_HEADER_BUFSIZE) { + // Not expected to happen. + applog(LOG_WARNING, "hfa_get_header() tried to overflow buf[]."); + return false; + } + ret = usb_read(hashfast, buf + ofs, len, &amount, C_HF_GETHEADER); + + if (unlikely(ret && ret != LIBUSB_ERROR_TIMEOUT)) + return false; + ofs += amount; + header = memchr(buf, HF_PREAMBLE, ofs); + if (header) { + /* Toss any leading data we can't use */ + if (header != buf) { + memmove(buf, header, ofs); + ofs -= header - buf; + } + len -= ofs; + } + else { + /* HF_PREAMBLE not found, toss all the useless leading data. */ + ofs = 0; + len = sizeof(*h); + } + } while (len > 0); + + memcpy(h, header, orig_len); + *computed_crc = hfa_crc8((uint8_t *)h); + + return true; +} + +static bool hfa_get_data(struct cgpu_info *hashfast, char *buf, int len4) +{ + int amount, ret, len = len4 * 4; + + if (unlikely(hashfast->usbinfo.nodev)) + return false; + ret = usb_read(hashfast, buf, len, &amount, C_HF_GETDATA); + if (ret) + return false; + if (amount != len) { + applog(LOG_WARNING, "%s %s: get_data: Strange amount returned %d vs. expected %d", + hashfast->drv->name, hashfast->unique_id, amount, len); + return false; + } + return true; +} + +static const char *hf_usb_init_errors[] = { + "Success", + "Reset timeout", + "Address cycle timeout", + "Clockgate operation timeout", + "Configuration operation timeout", + "Excessive core failures", + "All cores failed diagnostics", + "Too many groups configured - increase ntime roll amount", + "Chaining connections detected but secondary board(s) did not respond", + "Secondary board communication error", + "Main board 12V power is bad", + "Secondary board(s) 12V power is bad", + "Main board FPGA programming error", + "Main board FPGA SPI read timeout", + "Main board FPGA Bad magic number", + "Main board FPGA SPI write timeout", + "Main board FPGA register read/write test failed", + "ASIC core power fault", + "Dynamic baud rate change timeout", + "Address failure", + "Regulator programming error", + "Address range inconsistent after mixed reconfiguration", + "Timeout after mixed reconfiguration" +}; + +static bool hfa_clear_readbuf(struct cgpu_info *hashfast); + +struct op_nameframe { + struct hf_header h; + char name[32]; +} __attribute__((packed)); + +static void hfa_write_opname(struct cgpu_info *hashfast, struct hashfast_info *info) +{ + const uint8_t opcode = HF_USB_CMD(OP_NAME); + struct op_nameframe nameframe; + struct hf_header *h = (struct hf_header *)&nameframe; + const int tx_length = sizeof(struct op_nameframe); + + memset(&nameframe, 0, sizeof(nameframe)); + strncpy(nameframe.name, info->op_name, 30); + h->preamble = HF_PREAMBLE; + h->operation_code = hfa_cmds[opcode].cmd; + h->core_address = 1; + h->data_length = 32 / 4; + h->crc8 = hfa_crc8((unsigned char *)h); + applog(LOG_DEBUG, "%s %d: Opname being set to %s", hashfast->drv->name, + hashfast->device_id, info->op_name); + __hfa_send_frame(hashfast, opcode, tx_length, (uint8_t *)&nameframe); +} + +/* If no opname or an invalid opname is set, change it to the serial number if + * it exists, or a random name based on timestamp if not. */ +static void hfa_choose_opname(struct cgpu_info *hashfast, struct hashfast_info *info) +{ + uint64_t usecs; + + if (info->serial_number) + sprintf(info->op_name, "%08x", info->serial_number); + else { + struct timeval tv_now; + + cgtime(&tv_now); + usecs = (uint64_t)(tv_now.tv_sec) * (uint64_t)1000000 + (uint64_t)tv_now.tv_usec; + sprintf(info->op_name, "%lx", (long unsigned int)usecs); + } + hfa_write_opname(hashfast, info); +} + +// Generic setting header +struct hf_settings_data { + uint8_t revision; + uint8_t ref_frequency; + uint16_t magic; + uint16_t frequency0; + uint16_t voltage0; + uint16_t frequency1; + uint16_t voltage1; + uint16_t frequency2; + uint16_t voltage2; + uint16_t frequency3; + uint16_t voltage3; +} __attribute__((packed,aligned(4))); + +static bool hfa_set_voltages(struct cgpu_info *hashfast, struct hashfast_info *info) +{ + struct hf_settings_data op_settings_data; + + op_settings_data.revision = 1; + op_settings_data.ref_frequency = 25; + op_settings_data.magic = HFA_MAGIC_SETTINGS_VALUE; + + op_settings_data.frequency0 = info->hash_clock_rate; + op_settings_data.voltage0 = info->hash_voltage; + op_settings_data.frequency1 = info->hash_clock_rate; + op_settings_data.voltage1 = info->hash_voltage; + op_settings_data.frequency2 = info->hash_clock_rate; + op_settings_data.voltage2 = info->hash_voltage; + op_settings_data.frequency3 = info->hash_clock_rate; + op_settings_data.voltage3 = info->hash_voltage; + + hfa_send_generic_frame(hashfast, OP_SETTINGS, 0x00, 0x01, HFA_MAGIC_SETTINGS_VALUE, + (uint8_t *)&op_settings_data, sizeof(op_settings_data)); + // reset the board once to switch to new voltage settings + hfa_send_generic_frame(hashfast, OP_POWER, 0xff, 0x00, 0x1, NULL, 0); + hfa_send_generic_frame(hashfast, OP_POWER, 0xff, 0x00, 0x2, NULL, 0); + + return true; +} + +static bool hfa_send_shutdown(struct cgpu_info *hashfast); + +static bool hfa_reset(struct cgpu_info *hashfast, struct hashfast_info *info) +{ + struct hf_usb_init_header usb_init[2], *hu = usb_init; + struct hf_usb_init_base *db; + struct hf_usb_init_options *ho; + int retries = 0, i; + bool ret = false; + char buf[1024]; + struct hf_header *h = (struct hf_header *)buf; + uint8_t hcrc; + + /* Hash clock rate in Mhz. Set to opt_hfa_hash_clock if it has not + * been inherited across a restart. */ + if (!info->hash_clock_rate) + info->hash_clock_rate = opt_hfa_hash_clock; + info->group_ntime_roll = opt_hfa_ntime_roll; + info->core_ntime_roll = 1; + + // Assemble the USB_INIT request + memset(hu, 0, sizeof(*hu)); + hu->preamble = HF_PREAMBLE; + hu->operation_code = OP_USB_INIT; + hu->protocol = PROTOCOL_GLOBAL_WORK_QUEUE; // Protocol to use + if (!opt_hfa_noshed) + hu->shed_supported = true; + // Force PLL bypass + hu->pll_bypass = opt_hfa_pll_bypass; + hu->hash_clock = info->hash_clock_rate; // Hash clock rate in Mhz + if (info->group_ntime_roll > 1 && info->core_ntime_roll) { + ho = (struct hf_usb_init_options *)(hu + 1); + memset(ho, 0, sizeof(*ho)); + ho->group_ntime_roll = info->group_ntime_roll; + ho->core_ntime_roll = info->core_ntime_roll; + hu->data_length = sizeof(*ho) / 4; + } + hu->crc8 = hfa_crc8((uint8_t *)hu); + applog(LOG_INFO, "%s %s: Sending OP_USB_INIT with GWQ protocol specified", + hashfast->drv->name, hashfast->unique_id); +resend: + if (unlikely(hashfast->usbinfo.nodev)) + goto out; + + if (!hfa_clear_readbuf(hashfast)) + goto out; + + if (!hfa_send_packet(hashfast, (struct hf_header *)hu, HF_USB_CMD(OP_USB_INIT))) + goto out; + + // Check for the correct response. + // We extend the normal timeout - a complete device initialization, including + // bringing power supplies up from standby, etc., can take over a second. +tryagain: + for (i = 0; i < 10; i++) { + ret = hfa_get_header(hashfast, h, &hcrc); + if (unlikely(hashfast->usbinfo.nodev)) + goto out; + if (ret) + break; + } + if (!ret) { + if (retries++ < 3) + goto resend; + applog(LOG_WARNING, "%s %s: OP_USB_INIT failed!", hashfast->drv->name, hashfast->unique_id); + goto out; + } + if (h->crc8 != hcrc) { + applog(LOG_WARNING, "%s %s: OP_USB_INIT failed! CRC mismatch", hashfast->drv->name, hashfast->unique_id); + ret = false; + goto out; + } + if (h->operation_code != OP_USB_INIT) { + // This can happen if valid packet(s) were in transit *before* the OP_USB_INIT arrived + // at the device, so we just toss the packets and keep looking for the response. + applog(LOG_WARNING, "%s %s: OP_USB_INIT: Tossing packet, valid but unexpected type %d", + hashfast->drv->name, hashfast->unique_id, h->operation_code); + hfa_get_data(hashfast, buf, h->data_length); + if (retries++ < 3) + goto tryagain; + ret = false; + goto out; + } + + applog(LOG_DEBUG, "%s %s: Good reply to OP_USB_INIT", hashfast->drv->name, hashfast->unique_id); + applog(LOG_DEBUG, "%s %s: OP_USB_INIT: %d die in chain, %d cores, device_type %d, refclk %d Mhz", + hashfast->drv->name, hashfast->unique_id, h->chip_address, h->core_address, h->hdata & 0xff, (h->hdata >> 8) & 0xff); + + // Save device configuration + info->asic_count = h->chip_address; + info->core_count = h->core_address; + info->device_type = (uint8_t)h->hdata; + info->ref_frequency = (uint8_t)(h->hdata >> 8); + info->hash_sequence_head = 0; + info->hash_sequence_tail = 0; + info->device_sequence_tail = 0; + + if (info->asic_count == 12) + hashfast->drv->name = "HFS"; + else if (info->asic_count == 4) + hashfast->drv->name = "HFB"; + + // Size in bytes of the core bitmap in bytes + info->core_bitmap_size = (((info->asic_count * info->core_count) + 31) / 32) * 4; + + // Get the usb_init_base structure + if (!hfa_get_data(hashfast, (char *)&info->usb_init_base, U32SIZE(info->usb_init_base))) { + applog(LOG_WARNING, "%s %s: OP_USB_INIT failed! Failure to get usb_init_base data", + hashfast->drv->name, hashfast->unique_id); + ret = false; + goto out; + } + db = &info->usb_init_base; + info->firmware_version = ((db->firmware_rev >> 8) & 0xff) + (double)(db->firmware_rev & 0xff) / 10.0; + info->hardware_version = ((db->hardware_rev >> 8) & 0xff) + (double)(db->hardware_rev & 0xff) / 10.0; + applog(LOG_INFO, "%s %s: firmware_rev: %.1f", hashfast->drv->name, hashfast->unique_id, + info->firmware_version); + applog(LOG_INFO, "%s %s: hardware_rev: %.1f", hashfast->drv->name, hashfast->unique_id, + info->hardware_version); + applog(LOG_INFO, "%s %s: serial number: %08x", hashfast->drv->name, hashfast->unique_id, + db->serial_number); + applog(LOG_INFO, "%s %s: hash clockrate: %d Mhz", hashfast->drv->name, hashfast->unique_id, + db->hash_clockrate); + applog(LOG_INFO, "%s %s: inflight_target: %d", hashfast->drv->name, hashfast->unique_id, + db->inflight_target); + applog(LOG_INFO, "%s %s: sequence_modulus: %d", hashfast->drv->name, hashfast->unique_id, + db->sequence_modulus); + + // Now a copy of the config data used + if (!hfa_get_data(hashfast, (char *)&info->config_data, U32SIZE(info->config_data))) { + applog(LOG_WARNING, "%s %s: OP_USB_INIT failed! Failure to get config_data", + hashfast->drv->name, hashfast->unique_id); + ret = false; + goto out; + } + + // Now the core bitmap + info->core_bitmap = malloc(info->core_bitmap_size); + if (!info->core_bitmap) + quit(1, "Failed to malloc info core bitmap in hfa_reset"); + if (!hfa_get_data(hashfast, (char *)info->core_bitmap, info->core_bitmap_size / 4)) { + applog(LOG_WARNING, "%s %s: OP_USB_INIT failed! Failure to get core_bitmap", hashfast->drv->name, hashfast->unique_id); + ret = false; + goto out; + } + + // See if the initialization suceeded + if (db->operation_status) { + applog(LOG_ERR, "%s %s: OP_USB_INIT failed! Operation status %d (%s)", + hashfast->drv->name, hashfast->unique_id, db->operation_status, + (db->operation_status < sizeof(hf_usb_init_errors)/sizeof(hf_usb_init_errors[0])) ? + hf_usb_init_errors[db->operation_status] : "Unknown error code"); + ret = false; + switch (db->operation_status) { + case E_CORE_POWER_FAULT: + for (i = 0; i < 4; i++) { + if (((db->extra_status_1 >> i) & 0x11) == 0x1) { + applog(LOG_ERR, "%s %s: OP_USB_INIT: Quadrant %d (of 4) regulator failure", + hashfast->drv->name, hashfast->unique_id, i + 1); + } + } + break; + default: + break; + } + goto out; + } + + if (!db->hash_clockrate) { + applog(LOG_INFO, "%s %s: OP_USB_INIT failed! Clockrate reported as zero", + hashfast->drv->name, hashfast->unique_id); + ret = false; + goto out; + } + info->num_sequence = db->sequence_modulus; + info->serial_number = db->serial_number; + info->base_clock = db->hash_clockrate; + + ret = hfa_clear_readbuf(hashfast); +out: + if (!ret) { + hfa_send_shutdown(hashfast); + usb_nodev(hashfast); + } + return ret; +} + +static bool hfa_clear_readbuf(struct cgpu_info *hashfast) +{ + int amount, ret = 0; + char buf[512]; + + do { + if (hashfast->usbinfo.nodev) { + ret = LIBUSB_ERROR_NO_DEVICE; + break; + } + ret = usb_read(hashfast, buf, 512, &amount, C_HF_CLEAR_READ); + } while (!ret && amount); + + if (ret && ret != LIBUSB_ERROR_TIMEOUT) + return false; + return true; +} + +static bool hfa_send_shutdown(struct cgpu_info *hashfast) +{ + bool ret = false; + + if (hashfast->usbinfo.nodev) + return ret; + /* Send a restart before the shutdown frame to tell the device to + * discard any work it thinks is in flight for a cleaner restart. */ + if (!hfa_send_frame(hashfast, HF_USB_CMD(OP_WORK_RESTART), 0, (uint8_t *)NULL, 0)) + return ret; + if (hfa_send_frame(hashfast, HF_USB_CMD(OP_USB_SHUTDOWN), 0, NULL, 0)) { + /* Wait to allow device to properly shut down. */ + cgsleep_ms(1000); + ret = true; + } + return ret; +} + +static struct cgpu_info *hfa_old_device(struct cgpu_info *hashfast, struct hashfast_info *info) +{ + struct cgpu_info *cgpu, *found = NULL; + struct hashfast_info *cinfo = NULL; + int i; + + /* See if we can find a zombie instance of the same device */ + for (i = 0; i < mining_threads; i++) { + cgpu = mining_thr[i]->cgpu; + if (!cgpu) + continue; + if (cgpu == hashfast) + continue; + if (cgpu->drv->drv_id != DRIVER_hashfast) + continue; + if (!cgpu->usbinfo.nodev) + continue; + cinfo = cgpu->device_data; + if (!cinfo) + continue; + if (info->op_name[0] != '\0' && !strncmp(info->op_name, cinfo->op_name, 32)) { + found = cgpu; + break; + } + if (info->serial_number && info->serial_number == cinfo->serial_number) { + found = cgpu; + break; + } + } + return found; +} + +static void hfa_set_clock(struct cgpu_info *hashfast, struct hashfast_info *info) +{ + uint16_t hdata; + int i; + + hdata = (WR_CLOCK_VALUE << WR_COMMAND_SHIFT) | info->hash_clock_rate; + + hfa_send_frame(hashfast, HF_USB_CMD(OP_WORK_RESTART), hdata, (uint8_t *)NULL, 0); + /* We won't know what the real clock is in this case without a + * usb_init_base message so we have to assume it's what we asked. */ + info->base_clock = info->hash_clock_rate; + for (i = 0; i < info->asic_count; i++) + info->die_data[i].hash_clock = info->base_clock; +} + +/* Look for an op name match and apply any options to its first attempted + * init sequence. This function allows any arbitrary number of extra parameters + * to be added in the future. */ +static void hfa_check_options(struct hashfast_info *info) +{ + char *p, *options, *found = NULL, *marker; + int maxlen, option = 0; + + if (!opt_hfa_options) + return; + + if (!info->op_name) + return; + + maxlen = strlen(info->op_name); + + options = strdup(opt_hfa_options); + for (p = strtok(options, ","); p; p = strtok(NULL, ",")) { + int cmplen = strlen(p); + + if (maxlen < cmplen) + cmplen = maxlen; + if (cmplen < maxlen) + continue; + if (!strncmp(info->op_name, p, cmplen)) { + found = strdup(p); + break; + } + } + free(options); + if (!found) + return; + + for (p = strtok(found, ":"); p; p = strtok(NULL, ":")) { + long lval; + + /* Parse each option in order, leaving room to add more */ + switch(option++) { + default: + break; + case 1: + lval = strtol(p, NULL, 10); + if (lval < HFA_CLOCK_MIN || lval > HFA_CLOCK_MAX) { + applog(LOG_ERR, "Invalid clock speed %ld set with hashfast option for %s", + lval, info->op_name); + break; + } + info->hash_clock_rate = lval; + marker = strchr(p,'@'); + if (marker != NULL) { + lval = strtol(marker+1, NULL, 10); + if (lval < HFA_VOLTAGE_MIN || lval > HFA_VOLTAGE_MAX) { + applog(LOG_ERR, "Invalid core voltage %ld set with hashfast option for %s", + lval, info->op_name); + break; + } + info->hash_voltage = lval; + } + break; + } + } + free(found); +} + +static bool hfa_detect_common(struct cgpu_info *hashfast) +{ + struct hashfast_info *info; + char buf[1024]; + struct hf_header *h = (struct hf_header *)buf; + uint8_t hcrc; + bool ret; + int i; + + info = calloc(sizeof(struct hashfast_info), 1); + if (!info) + quit(1, "Failed to calloc hashfast_info in hfa_detect_common"); + hashfast->device_data = info; + + /* Try sending and receiving an OP_NAME */ + ret = hfa_send_frame(hashfast, HF_USB_CMD(OP_NAME), 0, (uint8_t *)NULL, 0); + if (hashfast->usbinfo.nodev) { + ret = false; + goto out; + } + if (!ret) { + applog(LOG_WARNING, "%s %d: Failed to send OP_NAME!", hashfast->drv->name, + hashfast->device_id); + goto out; + } + ret = hfa_get_header(hashfast, h, &hcrc); + if (hashfast->usbinfo.nodev) { + ret = false; + goto out; + } + if (!ret) { + /* We should receive a valid header even if OP_NAME isn't + * supported by the firmware. */ + applog(LOG_NOTICE, "%s %d: No response to name query - failed init or firmware upgrade required.", + hashfast->drv->name, hashfast->device_id); + ret = true; + } else { + /* Only try to parse the name if the firmware supports OP_NAME */ + if (h->operation_code == OP_NAME) { + if (!hfa_get_data(hashfast, info->op_name, 32 / 4)) { + applog(LOG_WARNING, "%s %d: OP_NAME failed! Failure to get op_name data", + hashfast->drv->name, hashfast->device_id); + goto out; + } + info->has_opname = info->opname_valid = true; + applog(LOG_DEBUG, "%s: Returned an OP_NAME", hashfast->drv->name); + for (i = 0; i < 32; i++) { + if (i > 0 && info->op_name[i] == '\0') + break; + /* Make sure the op_name is valid ascii only */ + if (info->op_name[i] < 32 || info->op_name[i] > 126) { + info->opname_valid = false; + break; + } + } + } + } + + info->cgpu = hashfast; + /* Look for a matching zombie instance and inherit values from it if it + * exists. */ + if (info->has_opname && info->opname_valid) { + info->old_cgpu = hfa_old_device(hashfast, info); + if (info->old_cgpu) { + struct hashfast_info *cinfo = info->old_cgpu->device_data; + + applog(LOG_NOTICE, "%s: Found old instance by op name %s at device %d", + hashfast->drv->name, info->op_name, info->old_cgpu->device_id); + info->resets = ++cinfo->resets; + info->hash_clock_rate = cinfo->hash_clock_rate; + } else { + applog(LOG_NOTICE, "%s: Found device with name %s", hashfast->drv->name, + info->op_name); + hfa_check_options(info); + } + } + +out: + if (!ret) { + if (!hashfast->usbinfo.nodev) + hfa_clear_readbuf(hashfast); + hashfast->device_data = NULL; + free(info); + } + return ret; +} + +static bool hfa_initialise(struct cgpu_info *hashfast) +{ + int err = 7; + + if (hashfast->usbinfo.nodev) + return false; + + if (!hfa_clear_readbuf(hashfast)) + return false; +#ifdef WIN32 + err = usb_transfer(hashfast, 0, 9, 1, 0, C_ATMEL_RESET); + if (!err) + err = usb_transfer(hashfast, 0x21, 0x22, 0, 0, C_ATMEL_OPEN); + if (!err) { + uint32_t buf[2]; + + /* Magic sequence to reset device only really needed for windows + * but harmless on linux. */ + buf[0] = 0x80250000; + buf[1] = 0x00000800; + err = usb_transfer_data(hashfast, 0x21, 0x20, 0x0000, 0, buf, + 7, C_ATMEL_INIT); + } + if (err < 0) { + applog(LOG_INFO, "%s %s: Failed to open with error %s", + hashfast->drv->name, hashfast->unique_id, libusb_error_name(err)); + } +#endif + /* Must have transmitted init sequence sized buffer */ + return (err == 7); +} + +static void hfa_dfu_boot(struct cgpu_info *hashfast) +{ + bool ret; + + if (unlikely(hashfast->usbinfo.nodev)) + return; + + ret = hfa_send_frame(hashfast, HF_USB_CMD(OP_DFU), 0, NULL, 0); + applog(LOG_WARNING, "%s %s: %03d:%03d DFU Boot %s", hashfast->drv->name, hashfast->unique_id, + hashfast->usbinfo.bus_number, hashfast->usbinfo.device_address, + ret ? "Succeeded" : "Failed"); +} + +static struct cgpu_info *hfa_detect_one(libusb_device *dev, struct usb_find_devices *found) +{ + struct cgpu_info *hashfast; + + hashfast = usb_alloc_cgpu(&hashfast_drv, HASHFAST_MINER_THREADS); + if (!hashfast) + quit(1, "Failed to usb_alloc_cgpu hashfast"); + hashfast->unique_id = ""; + + if (!usb_init(hashfast, dev, found)) { + hashfast = usb_free_cgpu(hashfast); + return NULL; + } + + hashfast->usbdev->usb_type = USB_TYPE_STD; + + if (!hfa_initialise(hashfast)) { + hashfast = usb_free_cgpu(hashfast); + return NULL; + } + if (opt_hfa_dfu_boot) { + hfa_dfu_boot(hashfast); + hashfast = usb_free_cgpu(hashfast); + opt_hfa_dfu_boot = false; + return NULL; + } + if (!hfa_detect_common(hashfast)) { + usb_uninit(hashfast); + hashfast = usb_free_cgpu(hashfast); + return NULL; + } + if (!add_cgpu(hashfast)) + return NULL; + + if (opt_hfa_name) { + struct hashfast_info *info = hashfast->device_data; + + strncpy(info->op_name, opt_hfa_name, 30); + applog(LOG_NOTICE, "%s %d %03d:%03d: Writing name %s", hashfast->drv->name, + hashfast->device_id, hashfast->usbinfo.bus_number, hashfast->usbinfo.device_address, + info->op_name); + hfa_write_opname(hashfast, info); + opt_hfa_name = NULL; + } + + return hashfast; +} + +static void hfa_detect(bool __maybe_unused hotplug) +{ + /* Set up the CRC tables only once. */ + if (!hfa_crc8_set) + hfa_init_crc8(); + usb_detect(&hashfast_drv, hfa_detect_one); +} + +static bool hfa_get_packet(struct cgpu_info *hashfast, struct hf_header *h) +{ + uint8_t hcrc; + bool ret; + + if (unlikely(hashfast->usbinfo.nodev)) + return false; + + ret = hfa_get_header(hashfast, h, &hcrc); + if (unlikely(!ret)) + goto out; + if (unlikely(h->crc8 != hcrc)) { + applog(LOG_WARNING, "%s %s: Bad CRC %d vs %d, discarding packet", + hashfast->drv->name, hashfast->unique_id, h->crc8, hcrc); + ret = false; + goto out; + } + if (h->data_length > 0) + ret = hfa_get_data(hashfast, (char *)(h + 1), h->data_length); + if (unlikely(!ret)) { + applog(LOG_WARNING, "%s %s: Failed to get data associated with header", + hashfast->drv->name, hashfast->unique_id); + } + +out: + return ret; +} + +static void hfa_running_shutdown(struct cgpu_info *hashfast, struct hashfast_info *info); + +static void hfa_parse_gwq_status(struct cgpu_info *hashfast, struct hashfast_info *info, + struct hf_header *h) +{ + struct hf_gwq_data *g = (struct hf_gwq_data *)(h + 1); + struct work *work; + + applog(LOG_DEBUG, "%s %s: OP_GWQ_STATUS, device_head %4d tail %4d my tail %4d shed %3d inflight %4d", + hashfast->drv->name, hashfast->unique_id, g->sequence_head, g->sequence_tail, info->hash_sequence_tail, + g->shed_count, HF_SEQUENCE_DISTANCE(info->hash_sequence_head,g->sequence_tail)); + + /* This is a special flag that the thermal overload has been tripped */ + if (unlikely(h->core_address & 0x80)) { + applog(LOG_ERR, "%s %s: Thermal overload tripped! Shutting down device", + hashfast->drv->name, hashfast->unique_id); + hfa_running_shutdown(hashfast, info); + usb_nodev(hashfast); + return; + } + + mutex_lock(&info->lock); + info->raw_hashes += g->hash_count; + info->device_sequence_head = g->sequence_head; + info->device_sequence_tail = g->sequence_tail; + info->shed_count = g->shed_count; + /* Free any work that is no longer required */ + while (info->device_sequence_tail != info->hash_sequence_tail) { + if (++info->hash_sequence_tail >= info->num_sequence) + info->hash_sequence_tail = 0; + if (unlikely(!(work = info->works[info->hash_sequence_tail]))) { + applog(LOG_ERR, "%s %s: Bad work sequence tail %d head %d devhead %d devtail %d sequence %d", + hashfast->drv->name, hashfast->unique_id, info->hash_sequence_tail, + info->hash_sequence_head, info->device_sequence_head, + info->device_sequence_tail, info->num_sequence); + hashfast->shutdown = true; + usb_nodev(hashfast); + break; + } + applog(LOG_DEBUG, "%s %s: Completing work on hash_sequence_tail %d", + hashfast->drv->name, hashfast->unique_id, info->hash_sequence_tail); + free_work(work); + info->works[info->hash_sequence_tail] = NULL; + } + mutex_unlock(&info->lock); +} + +/* Board temperature conversion */ +static float board_temperature(uint16_t adc) +{ + float t, r, f, b; + + if (adc < 40 || adc > 650) + return((float) 0.0); // Bad count + + b = 3590.0; + f = (float)adc / 1023.0; + r = 1.0 / (1.0 / f - 1.0); + t = log(r) / b; + t += 1.0 / (25.0 + 273.15); + t = 1.0 / t - 273.15; + + return t; +} + +static void hfa_update_die_status(struct cgpu_info *hashfast, struct hashfast_info *info, + struct hf_header *h) +{ + struct hf_g1_die_data *d = (struct hf_g1_die_data *)(h + 1), *ds; + int num_included = (h->data_length * 4) / sizeof(struct hf_g1_die_data); + int i, j, die = h->chip_address; + + float die_temperature, board_temp; + float core_voltage[6]; + + // Copy in the data. They're numbered sequentially from the starting point + ds = info->die_status + h->chip_address; + for (i = 0; i < num_included; i++) + memcpy(ds++, d++, sizeof(struct hf_g1_die_data)); + + for (i = 0, d = &info->die_status[h->chip_address]; i < num_included; i++, d++) { + die += i; + die_temperature = GN_DIE_TEMPERATURE(d->die.die_temperature); + /* Sanity checking */ + if (unlikely(die_temperature > 255)) + die_temperature = info->die_data[die].temp; + else + info->die_data[die].temp = die_temperature; + board_temp = board_temperature(d->temperature); + if (unlikely(board_temp > 255)) + board_temp = info->die_data[die].board_temp; + else + info->die_data[die].board_temp = board_temp; + for (j = 0; j < 6; j++) + core_voltage[j] = GN_CORE_VOLTAGE(d->die.core_voltage[j]); + + applog(LOG_DEBUG, "%s %s: die %2d: OP_DIE_STATUS Temps die %.1fC board %.1fC vdd's %.2f %.2f %.2f %.2f %.2f %.2f", + hashfast->drv->name, hashfast->unique_id, die, die_temperature, board_temp, + core_voltage[0], core_voltage[1], core_voltage[2], + core_voltage[3], core_voltage[4], core_voltage[5]); + // XXX Convert board phase currents, voltage, temperature + } + if (die == info->asic_count - 1) { + /* We have a full set of die temperatures, find the highest + * current temperature. */ + float max_temp = 0; + + info->temp_updates++; + + for (die = 0; die < info->asic_count; die++) { + if (info->die_data[die].temp > max_temp) + max_temp = info->die_data[die].temp; + if (info->die_data[die].board_temp > max_temp) + max_temp = info->die_data[die].board_temp; + } + /* Exponentially change the max_temp to smooth out troughs. */ + hashfast->temp = hashfast->temp * 0.63 + max_temp * 0.37; + } + + if (unlikely(hashfast->temp >= opt_hfa_overheat)) { + /* -1 means new overheat condition */ + if (!info->overheat) + info->overheat = -1; + } else if (unlikely(info->overheat && hashfast->temp < opt_hfa_overheat - HFA_TEMP_HYSTERESIS)) + info->overheat = 0; +} + +static void hfa_parse_nonce(struct thr_info *thr, struct cgpu_info *hashfast, + struct hashfast_info *info, struct hf_header *h) +{ + struct hf_candidate_nonce *n = (struct hf_candidate_nonce *)(h + 1); + int i, num_nonces = h->data_length / U32SIZE(sizeof(struct hf_candidate_nonce)); + + applog(LOG_DEBUG, "%s %s: OP_NONCE: %2d/%2d:, num_nonces %d hdata 0x%04x", + hashfast->drv->name, hashfast->unique_id, h->chip_address, h->core_address, num_nonces, h->hdata); + for (i = 0; i < num_nonces; i++, n++) { + struct work *work = NULL; + + applog(LOG_DEBUG, "%s %s: OP_NONCE: %2d: %2d: ntime %2d sequence %4d nonce 0x%08x", + hashfast->drv->name, hashfast->unique_id, h->chip_address, i, n->ntime & HF_NTIME_MASK, n->sequence, n->nonce); + + if (n->sequence < info->usb_init_base.sequence_modulus) { + // Find the job from the sequence number + mutex_lock(&info->lock); + work = info->works[n->sequence]; + mutex_unlock(&info->lock); + } else { + applog(LOG_INFO, "%s %s: OP_NONCE: Sequence out of range %4d max %4d", + hashfast->drv->name, hashfast->unique_id, n->sequence, info->usb_init_base.sequence_modulus); + } + + if (unlikely(!work)) { + info->no_matching_work++; + applog(LOG_INFO, "%s %s: No matching work!", hashfast->drv->name, hashfast->unique_id); + } else { + applog(LOG_DEBUG, "%s %s: OP_NONCE: sequence %d: submitting nonce 0x%08x ntime %d", + hashfast->drv->name, hashfast->unique_id, n->sequence, n->nonce, n->ntime & HF_NTIME_MASK); + if (submit_noffset_nonce(thr, work, n->nonce, n->ntime & HF_NTIME_MASK)) { + mutex_lock(&info->lock); + info->hash_count += 0xffffffffull * work->device_diff; + mutex_unlock(&info->lock); + } +#if 0 /* Not used */ + if (unlikely(n->ntime & HF_NONCE_SEARCH)) { + /* This tells us there is another share in the + * next 128 nonces */ + applog(LOG_DEBUG, "%s %s: OP_NONCE: SEARCH PROXIMITY EVENT FOUND", + hashfast->drv->name, hashfast->unique_id); + } +#endif + } + } +} + +static void hfa_update_die_statistics(struct hashfast_info *info, struct hf_header *h) +{ + struct hf_statistics *s = (struct hf_statistics *)(h + 1); + struct hf_long_statistics *l; + + // Accumulate the data + l = info->die_statistics + h->chip_address; + + l->rx_header_crc += s->rx_header_crc; + l->rx_body_crc += s->rx_body_crc; + l->rx_header_timeouts += s->rx_header_timeouts; + l->rx_body_timeouts += s->rx_body_timeouts; + l->core_nonce_fifo_full += s->core_nonce_fifo_full; + l->array_nonce_fifo_full += s->array_nonce_fifo_full; + l->stats_overrun += s->stats_overrun; +} + +static void hfa_update_stats1(struct cgpu_info *hashfast, struct hashfast_info *info, + struct hf_header *h) +{ + struct hf_long_usb_stats1 *s1 = &info->stats1; + struct hf_usb_stats1 *sd = (struct hf_usb_stats1 *)(h + 1); + + s1->usb_rx_preambles += sd->usb_rx_preambles; + s1->usb_rx_receive_byte_errors += sd->usb_rx_receive_byte_errors; + s1->usb_rx_bad_hcrc += sd->usb_rx_bad_hcrc; + + s1->usb_tx_attempts += sd->usb_tx_attempts; + s1->usb_tx_packets += sd->usb_tx_packets; + s1->usb_tx_timeouts += sd->usb_tx_timeouts; + s1->usb_tx_incompletes += sd->usb_tx_incompletes; + s1->usb_tx_endpointstalled += sd->usb_tx_endpointstalled; + s1->usb_tx_disconnected += sd->usb_tx_disconnected; + s1->usb_tx_suspended += sd->usb_tx_suspended; +#if 0 + /* We don't care about UART stats so they're not in our struct */ + s1->uart_tx_queue_dma += sd->uart_tx_queue_dma; + s1->uart_tx_interrupts += sd->uart_tx_interrupts; + + s1->uart_rx_preamble_ints += sd->uart_rx_preamble_ints; + s1->uart_rx_missed_preamble_ints += sd->uart_rx_missed_preamble_ints; + s1->uart_rx_header_done += sd->uart_rx_header_done; + s1->uart_rx_data_done += sd->uart_rx_data_done; + s1->uart_rx_bad_hcrc += sd->uart_rx_bad_hcrc; + s1->uart_rx_bad_dma += sd->uart_rx_bad_dma; + s1->uart_rx_short_dma += sd->uart_rx_short_dma; + s1->uart_rx_buffers_full += sd->uart_rx_buffers_full; +#endif + if (sd->max_tx_buffers > s1->max_tx_buffers) + s1->max_tx_buffers = sd->max_tx_buffers; + if (sd->max_rx_buffers > s1->max_rx_buffers) + s1->max_rx_buffers = sd->max_rx_buffers; + + applog(LOG_DEBUG, "%s %s: OP_USB_STATS1:", hashfast->drv->name, hashfast->unique_id); + applog(LOG_DEBUG, " usb_rx_preambles: %6d", sd->usb_rx_preambles); + applog(LOG_DEBUG, " usb_rx_receive_byte_errors: %6d", sd->usb_rx_receive_byte_errors); + applog(LOG_DEBUG, " usb_rx_bad_hcrc: %6d", sd->usb_rx_bad_hcrc); + + applog(LOG_DEBUG, " usb_tx_attempts: %6d", sd->usb_tx_attempts); + applog(LOG_DEBUG, " usb_tx_packets: %6d", sd->usb_tx_packets); + applog(LOG_DEBUG, " usb_tx_timeouts: %6d", sd->usb_tx_timeouts); + applog(LOG_DEBUG, " usb_tx_incompletes: %6d", sd->usb_tx_incompletes); + applog(LOG_DEBUG, " usb_tx_endpointstalled: %6d", sd->usb_tx_endpointstalled); + applog(LOG_DEBUG, " usb_tx_disconnected: %6d", sd->usb_tx_disconnected); + applog(LOG_DEBUG, " usb_tx_suspended: %6d", sd->usb_tx_suspended); +#if 0 + applog(LOG_DEBUG, " uart_tx_queue_dma: %6d", sd->uart_tx_queue_dma); + applog(LOG_DEBUG, " uart_tx_interrupts: %6d", sd->uart_tx_interrupts); + + applog(LOG_DEBUG, " uart_rx_preamble_ints: %6d", sd->uart_rx_preamble_ints); + applog(LOG_DEBUG, " uart_rx_missed_preamble_ints: %6d", sd->uart_rx_missed_preamble_ints); + applog(LOG_DEBUG, " uart_rx_header_done: %6d", sd->uart_rx_header_done); + applog(LOG_DEBUG, " uart_rx_data_done: %6d", sd->uart_rx_data_done); + applog(LOG_DEBUG, " uart_rx_bad_hcrc: %6d", sd->uart_rx_bad_hcrc); + applog(LOG_DEBUG, " uart_rx_bad_dma: %6d", sd->uart_rx_bad_dma); + applog(LOG_DEBUG, " uart_rx_short_dma: %6d", sd->uart_rx_short_dma); + applog(LOG_DEBUG, " uart_rx_buffers_full: %6d", sd->uart_rx_buffers_full); +#endif + applog(LOG_DEBUG, " max_tx_buffers: %6d", sd->max_tx_buffers); + applog(LOG_DEBUG, " max_rx_buffers: %6d", sd->max_rx_buffers); +} + +static void hfa_parse_notice(struct cgpu_info *hashfast, struct hf_header *h) +{ + struct hf_usb_notice_data *d; + + if (h->data_length == 0) { + applog(LOG_DEBUG, "%s %s: Received OP_USB_NOTICE with zero data length", + hashfast->drv->name, hashfast->unique_id); + return; + } + d = (struct hf_usb_notice_data *)(h + 1); + /* FIXME Do something with the notification code d->extra_data here */ + applog(LOG_NOTICE, "%s %s NOTICE: %s", hashfast->drv->name, hashfast->unique_id, d->message); +} + +static void hfa_parse_settings(struct cgpu_info *hashfast, struct hf_header *h) +{ + struct hashfast_info *info = hashfast->device_data; + struct hf_settings_data *op_settings_data = (struct hf_settings_data *)(h + 1); + + // Check if packet size, revision and magic are matching + if ((h->data_length * 4 == sizeof(struct hf_settings_data)) && + (h->core_address == 0) && + (op_settings_data->revision == 1) && + (op_settings_data->magic == HFA_MAGIC_SETTINGS_VALUE)) + { + applog(LOG_NOTICE, "%s: Device settings (%dMHz@%dmV,%dMHz@%dmV,%dMHz@%dmV,%dMHz@%dmV)", hashfast->drv->name, + op_settings_data->frequency0, op_settings_data->voltage0, + op_settings_data->frequency1, op_settings_data->voltage1, + op_settings_data->frequency2, op_settings_data->voltage2, + op_settings_data->frequency3, op_settings_data->voltage3); + // Set voltage only when current voltage values are different + if ((info->hash_voltage != 0) && + ((op_settings_data->voltage0 != info->hash_voltage) || + (op_settings_data->voltage1 != info->hash_voltage) || + (op_settings_data->voltage2 != info->hash_voltage) || + (op_settings_data->voltage3 != info->hash_voltage))) { + applog(LOG_NOTICE, "%s: Setting default clock and voltage to %dMHz@%dmV", + hashfast->drv->name, info->hash_clock_rate, info->hash_voltage); + hfa_set_voltages(hashfast, info); + } + } +} + +static void *hfa_read(void *arg) +{ + struct thr_info *thr = (struct thr_info *)arg; + struct cgpu_info *hashfast = thr->cgpu; + struct hashfast_info *info = hashfast->device_data; + char threadname[16]; + + snprintf(threadname, sizeof(threadname), "%d/%sRead", hashfast->device_id, hashfast->drv->name); + RenameThread(threadname); + + while (likely(!hashfast->shutdown)) { + char buf[512]; + struct hf_header *h = (struct hf_header *)buf; + bool ret; + + mutex_lock(&info->rlock); + ret = hfa_get_packet(hashfast, h); + mutex_unlock(&info->rlock); + + if (unlikely(hashfast->usbinfo.nodev)) + break; + + if (unlikely(!ret)) + continue; + + switch (h->operation_code) { + case OP_GWQ_STATUS: + hfa_parse_gwq_status(hashfast, info, h); + break; + case OP_DIE_STATUS: + hfa_update_die_status(hashfast, info, h); + break; + case OP_NONCE: + hfa_parse_nonce(thr, hashfast, info, h); + break; + case OP_STATISTICS: + hfa_update_die_statistics(info, h); + break; + case OP_USB_STATS1: + hfa_update_stats1(hashfast, info, h); + break; + case OP_USB_NOTICE: + hfa_parse_notice(hashfast, h); + break; + case OP_SETTINGS: + hfa_parse_settings(hashfast, h); + break; + case OP_POWER: + case OP_PING: + /* Do nothing */ + break; + default: + if (h->operation_code == OP_FAN) { + applog(LOG_NOTICE, "%s %s: Firmware upgrade required to support fan control", + hashfast->drv->name, hashfast->unique_id); + opt_hfa_target = 0; + break; + } + applog(LOG_WARNING, "%s %s: Unhandled operation code %d", + hashfast->drv->name, hashfast->unique_id, h->operation_code); + break; + } + /* Make sure we send something to the device at least every 5 + * seconds so it knows the driver is still alive for when we + * run out of work. The read thread never blocks so is the + * best place to do this. */ + if (time(NULL) - info->last_send > 5) + hfa_send_frame(hashfast, HF_USB_CMD(OP_PING), 0, NULL, 0); + } + applog(LOG_DEBUG, "%s %s: Shutting down read thread", hashfast->drv->name, hashfast->unique_id); + + return NULL; +} + +static void hfa_set_fanspeed(struct cgpu_info *hashfast, struct hashfast_info *info, + int fanspeed); + +static bool hfa_init(struct thr_info *thr) +{ + struct cgpu_info *hashfast = thr->cgpu; + struct hashfast_info *info = hashfast->device_data; + struct timeval now; + bool ret; + int i; + + if (hashfast->usbinfo.nodev) + return false; + + /* hashfast_reset should fill in details for info */ + ret = hfa_reset(hashfast, info); + + // The per-die status array + info->die_status = calloc(info->asic_count, sizeof(struct hf_g1_die_data)); + if (unlikely(!(info->die_status))) + quit(1, "Failed to calloc die_status"); + + info->die_data = calloc(info->asic_count, sizeof(struct hf_die_data)); + if (unlikely(!(info->die_data))) + quit(1, "Failed to calloc die_data"); + for (i = 0; i < info->asic_count; i++) + info->die_data[i].hash_clock = info->base_clock; + + // The per-die statistics array + info->die_statistics = calloc(info->asic_count, sizeof(struct hf_long_statistics)); + if (unlikely(!(info->die_statistics))) + quit(1, "Failed to calloc die_statistics"); + + info->works = calloc(sizeof(struct work *), info->num_sequence); + if (!info->works) + quit(1, "Failed to calloc info works in hfa_detect_common"); + if (!ret) + goto out; + + /* We will have extracted the serial number by now */ + if (info->has_opname && !info->opname_valid) + hfa_choose_opname(hashfast, info); + + /* Use the opname as the displayed unique identifier */ + hashfast->unique_id = info->op_name; + + /* Inherit the old device id */ + if (info->old_cgpu) + hashfast->device_id = info->old_cgpu->device_id; + + /* If we haven't found a matching old instance, we might not have + * a valid op_name yet or lack support so try to match based on + * serial number. */ + if (!info->old_cgpu) + info->old_cgpu = hfa_old_device(hashfast, info); + + if (!info->has_opname && info->old_cgpu) { + struct hashfast_info *cinfo = info->old_cgpu->device_data; + + applog(LOG_NOTICE, "%s: Found old instance by serial number %08x at device %d", + hashfast->drv->name, info->serial_number, info->old_cgpu->device_id); + info->resets = ++cinfo->resets; + /* Set the device with the last hash_clock_rate if it's + * different. */ + if (info->hash_clock_rate != cinfo->hash_clock_rate) { + info->hash_clock_rate = cinfo->hash_clock_rate; + hfa_set_clock(hashfast, info); + } + } + + // Read current device settings if voltage was set in options + if (info->hash_voltage != 0) + hfa_send_generic_frame(hashfast, OP_SETTINGS, 0x00, 0x00, HFA_MAGIC_SETTINGS_VALUE, NULL, 0); + + mutex_init(&info->lock); + mutex_init(&info->rlock); + if (pthread_create(&info->read_thr, NULL, hfa_read, (void *)thr)) + quit(1, "Failed to pthread_create read thr in hfa_prepare"); + + cgtime(&now); + get_datestamp(hashfast->init, sizeof(hashfast->init), &now); + hashfast->last_device_valid_work = time(NULL); + hfa_set_fanspeed(hashfast, info, opt_hfa_fan_default); +out: + if (hashfast->usbinfo.nodev) + ret = false; + + if (!ret) { + hfa_clear_readbuf(hashfast); + free(info); + hashfast->device_data = NULL; + usb_nodev(hashfast); + } + + return ret; +} + +/* If this ever returns 0 it means we have shed all the cores which will lead + * to no work being done which will trigger the watchdog. */ +static inline int hfa_basejobs(struct hashfast_info *info) +{ + return info->usb_init_base.inflight_target - info->shed_count; +} + +/* Figure out how many jobs to send. */ +static int hfa_jobs(struct cgpu_info *hashfast, struct hashfast_info *info) +{ + int ret = 0; + + if (unlikely(info->overheat)) { + /* Acknowledge and notify of new condition.*/ + if (info->overheat < 0) { + applog(LOG_WARNING, "%s %s: Hit overheat temp %.1f, throttling!", + hashfast->drv->name, hashfast->unique_id, hashfast->temp); + /* Value of 1 means acknowledged overheat */ + info->overheat = 1; + } + goto out; + } + + mutex_lock(&info->lock); + ret = hfa_basejobs(info) - HF_SEQUENCE_DISTANCE(info->hash_sequence_head, info->device_sequence_tail); + /* Place an upper limit on how many jobs to queue to prevent sending + * more work than the device can use after a period of outage. */ + if (ret > hfa_basejobs(info)) + ret = hfa_basejobs(info); + mutex_unlock(&info->lock); + + if (unlikely(ret < 0)) + ret = 0; + +out: + return ret; +} + +static void hfa_set_fanspeed(struct cgpu_info *hashfast, struct hashfast_info *info, + int fandiff) +{ + const uint8_t opcode = HF_USB_CMD(OP_FAN); + uint8_t packet[256]; + struct hf_header *p = (struct hf_header *)packet; + const int tx_length = sizeof(struct hf_header); + uint16_t hdata; + int fandata; + + info->fanspeed += fandiff; + if (info->fanspeed > opt_hfa_fan_max) + info->fanspeed = opt_hfa_fan_max; + else if (info->fanspeed < opt_hfa_fan_min) + info->fanspeed = opt_hfa_fan_min; + fandata = info->fanspeed * 255 / 100; // Fanspeed is in percent, hdata 0-255 + hdata = fandata; // Use an int first to avoid overflowing uint16_t + p->preamble = HF_PREAMBLE; + p->operation_code = hfa_cmds[opcode].cmd; + p->chip_address = 0xff; + p->core_address = 1; + p->hdata = htole16(hdata); + p->data_length = 0; + p->crc8 = hfa_crc8(packet); + + __hfa_send_frame(hashfast, opcode, tx_length, packet); +} + +static void hfa_increase_clock(struct cgpu_info *hashfast, struct hashfast_info *info, + int die) +{ + int i, high_clock = 0, low_clock = info->hash_clock_rate; + struct hf_die_data *hdd = &info->die_data[die]; + uint32_t diebit = 0x00000001ul << die; + uint16_t hdata, increase = 10; + + if (hdd->hash_clock + increase > info->hash_clock_rate) + increase = info->hash_clock_rate - hdd->hash_clock; + hdd->hash_clock += increase; + hdata = (WR_MHZ_INCREASE << 12) | increase; + if (info->clock_offset) { + for (i = 0; i < info->asic_count; i++) { + if (info->die_data[i].hash_clock > high_clock) + high_clock = info->die_data[i].hash_clock; + if (info->die_data[i].hash_clock < low_clock) + low_clock = info->die_data[i].hash_clock; + } + if (info->firmware_version < 0.5 && low_clock + HFA_CLOCK_MAXDIFF > high_clock) { + /* We can increase all clocks again */ + for (i = 0; i < info->asic_count; i++) { + if (i == die) /* We've already added to this die */ + continue; + info->die_data[i].hash_clock += increase; + } + applog(LOG_INFO, "%s %s: Die %d temp below range %.1f, increasing ALL dies by %d", + hashfast->drv->name, hashfast->unique_id, die, info->die_data[die].temp, increase); + hfa_send_frame(hashfast, HF_USB_CMD(OP_WORK_RESTART), hdata, (uint8_t *)NULL, 0); + info->clock_offset -= increase; + return; + } + } + applog(LOG_INFO, "%s %s: Die temp below range %.1f, increasing die %d clock to %d", + hashfast->drv->name, hashfast->unique_id, info->die_data[die].temp, die, hdd->hash_clock); + hfa_send_frame(hashfast, HF_USB_CMD(OP_WORK_RESTART), hdata, (uint8_t *)&diebit, 4); +} + +static void hfa_decrease_clock(struct cgpu_info *hashfast, struct hashfast_info *info, + int die) +{ + struct hf_die_data *hdd = &info->die_data[die]; + uint32_t diebit = 0x00000001ul << die; + uint16_t hdata, decrease = 20; + int i, high_clock = 0; + + /* Find the fastest die for comparison */ + for (i = 0; i < info->asic_count; i++) { + if (info->die_data[i].hash_clock > high_clock) + high_clock = info->die_data[i].hash_clock; + } + if (hdd->hash_clock - decrease < HFA_CLOCK_MIN) + decrease = hdd->hash_clock - HFA_CLOCK_MIN; + hdata = (WR_MHZ_DECREASE << 12) | decrease; + if (info->firmware_version < 0.5 && high_clock >= hdd->hash_clock + HFA_CLOCK_MAXDIFF) { + /* We can't have huge differences in clocks as it will lead to + * starvation of the faster cores so we have no choice but to + * slow down all dies to tame this one. */ + for (i = 0; i < info->asic_count; i++) + info->die_data[i].hash_clock -= decrease; + applog(LOG_INFO, "%s %s: Die %d temp above range %.1f, decreasing ALL die clocks by %d", + hashfast->drv->name, hashfast->unique_id, die, info->die_data[die].temp, decrease); + hfa_send_frame(hashfast, HF_USB_CMD(OP_WORK_RESTART), hdata, (uint8_t *)NULL, 0); + info->clock_offset += decrease; + return; + + } + hdd->hash_clock -= decrease; + applog(LOG_INFO, "%s %s: Die temp above range %.1f, decreasing die %d clock to %d", + hashfast->drv->name, hashfast->unique_id, info->die_data[die].temp, die, hdd->hash_clock); + hfa_send_frame(hashfast, HF_USB_CMD(OP_WORK_RESTART), hdata, (uint8_t *)&diebit, 4); +} + +/* Adjust clock according to temperature if need be by changing the clock + * setting and issuing a work restart with the new clock speed. */ +static void hfa_temp_clock(struct cgpu_info *hashfast, struct hashfast_info *info) +{ + int temp_change, i, low_clock; + time_t now_t = time(NULL); + bool throttled = false; + + if (!opt_hfa_target) + return; + + /* First find out if any dies are throttled before trying to optimise + * fanspeed, and find the slowest clock. */ + low_clock = info->hash_clock_rate; + for (i = 0; i < info->asic_count ; i++) { + struct hf_die_data *hdd = &info->die_data[i]; + + if (hdd->hash_clock < info->hash_clock_rate) + throttled = true; + if (hdd->hash_clock < low_clock) + low_clock = hdd->hash_clock; + } + + /* Find the direction of temperature change since we last checked */ + if (info->temp_updates < 5) + goto dies_only; + info->temp_updates = 0; + temp_change = hashfast->temp - info->last_max_temp; + info->last_max_temp = hashfast->temp; + + /* Adjust fanspeeds first if possible before die speeds, increasing + * speed quickly and lowering speed slowly */ + if (hashfast->temp > opt_hfa_target || + (throttled && hashfast->temp >= opt_hfa_target - HFA_TEMP_HYSTERESIS)) { + /* We should be trying to decrease temperature, if it's not on + * its way down. */ + if (info->fanspeed < opt_hfa_fan_max) { + if (!temp_change) + hfa_set_fanspeed(hashfast, info, 5); + else if (temp_change > 0) + hfa_set_fanspeed(hashfast, info, 10); + } + } else if (hashfast->temp >= opt_hfa_target - HFA_TEMP_HYSTERESIS) { + /* In optimal range, try and maintain the same temp */ + if (temp_change > 0) { + /* Temp rising, tweak fanspeed up */ + if (info->fanspeed < opt_hfa_fan_max) + hfa_set_fanspeed(hashfast, info, 2); + } else if (temp_change < 0) { + /* Temp falling, tweak fanspeed down */ + if (info->fanspeed > opt_hfa_fan_min) + hfa_set_fanspeed(hashfast, info, -1); + } + } else { + /* Below optimal range, try and increase temp */ + if (temp_change <= 0 && !throttled) { + if (info->fanspeed > opt_hfa_fan_min) + hfa_set_fanspeed(hashfast, info, -1); + } + } + +dies_only: + /* Do no restarts at all if there has been one less than 15 seconds + * ago */ + if (now_t - info->last_restart < 15) + return; + + for (i = 1; i <= info->asic_count ; i++) { + int die = (info->last_die_adjusted + i) % info->asic_count; + struct hf_die_data *hdd = &info->die_data[die]; + + /* Sanity check */ + if (unlikely(hdd->temp == 0.0 || hdd->temp > 255)) + continue; + + /* In target temperature */ + if (hdd->temp >= opt_hfa_target - HFA_TEMP_HYSTERESIS && hdd->temp <= opt_hfa_target) + continue; + + if (hdd->temp > opt_hfa_target) { + /* Temp above target range */ + + /* Already at min speed */ + if (hdd->hash_clock == HFA_CLOCK_MIN) + continue; + /* Have some leeway before throttling speed */ + if (hdd->temp < opt_hfa_target + HFA_TEMP_HYSTERESIS) + break; + hfa_decrease_clock(hashfast, info, die); + } else { + /* Temp below target range. Only send a restart to + * increase speed no more than every 60 seconds. */ + if (now_t - hdd->last_restart < 60) + continue; + + /* Already at max speed */ + if (hdd->hash_clock == info->hash_clock_rate) + continue; + /* Do not increase the clocks on any dies if we have + * a forced offset due to wild differences in clocks, + * unless this is the slowest one. */ + if (info->clock_offset && hdd->hash_clock > low_clock) + continue; + hfa_increase_clock(hashfast, info, die); + } + /* Keep track of the last die adjusted since we only adjust + * one at a time to ensure we end up iterating over all of + * them. */ + info->last_restart = hdd->last_restart = now_t; + info->last_die_adjusted = die; + break; + } +} + +static void hfa_running_shutdown(struct cgpu_info *hashfast, struct hashfast_info *info) +{ + int iruntime = cgpu_runtime(hashfast); + + /* If the device has already disapperaed, don't drop the clock in case + * it was just unplugged as opposed to a failure. */ + if (hashfast->usbinfo.nodev) + return; + + /* Only decrease the clock speed if the device has run at this speed + * for less than an hour before failing, otherwise the hashrate gains + * are worth the occasional restart which takes at most a minute. */ + if (iruntime < 3600 && info->hash_clock_rate > HFA_CLOCK_DEFAULT && opt_hfa_fail_drop) { + info->hash_clock_rate -= opt_hfa_fail_drop; + if (info->hash_clock_rate < HFA_CLOCK_DEFAULT) + info->hash_clock_rate = HFA_CLOCK_DEFAULT; + if (info->old_cgpu && info->old_cgpu->device_data) { + struct hashfast_info *cinfo = info->old_cgpu->device_data; + + /* Set the master device's clock speed if this is a copy */ + cinfo->hash_clock_rate = info->hash_clock_rate; + } + applog(LOG_WARNING, "%s %s: Decreasing clock speed to %d with reset", + hashfast->drv->name, hashfast->unique_id, info->hash_clock_rate); + } + + if (!hfa_send_shutdown(hashfast)) + return; + + if (hashfast->usbinfo.nodev) + return; + + mutex_lock(&info->rlock); + hfa_clear_readbuf(hashfast); + mutex_unlock(&info->rlock); + + usb_nodev(hashfast); +} + +static int64_t hfa_scanwork(struct thr_info *thr) +{ + struct cgpu_info *hashfast = thr->cgpu; + struct hashfast_info *info = hashfast->device_data; + struct work *base_work = NULL; + int jobs, ret, cycles = 0; + double fail_time; + int64_t hashes; + + if (unlikely(hashfast->usbinfo.nodev)) { + applog(LOG_WARNING, "%s %s: device disappeared, disabling", + hashfast->drv->name, hashfast->unique_id); + return -1; + } + + /* Base the fail time on no valid nonces for 25 full nonce ranges at + * the current expected hashrate. */ + fail_time = 25.0 * (double)hashfast->drv->max_diff * 0xffffffffull / + (double)(info->base_clock * 1000000) / hfa_basejobs(info); + if (unlikely(share_work_tdiff(hashfast) > fail_time)) { + applog(LOG_WARNING, "%s %s: No valid hashes for over %.0f seconds, shutting down thread", + hashfast->drv->name, hashfast->unique_id, fail_time); + hfa_running_shutdown(hashfast, info); + return -1; + } + + if (unlikely(thr->work_restart)) { +restart: + info->last_restart = time(NULL); + thr->work_restart = false; + ret = hfa_send_frame(hashfast, HF_USB_CMD(OP_WORK_RESTART), 0, (uint8_t *)NULL, 0); + if (unlikely(!ret)) { + hfa_running_shutdown(hashfast, info); + return -1; + } + /* Give a full allotment of jobs after a restart, not waiting + * for the status update telling us how much to give. */ + jobs = hfa_basejobs(info); + } else { + /* Only adjust die clocks if there's no restart since two + * restarts back to back get ignored. */ + hfa_temp_clock(hashfast, info); + jobs = hfa_jobs(hashfast, info); + } + + /* Wait on restart_wait for up to 0.5 seconds or submit jobs as soon as + * they're required. */ + while (!jobs && ++cycles < 5) { + ret = restart_wait(thr, 100); + if (unlikely(!ret)) + goto restart; + jobs = hfa_jobs(hashfast, info); + } + + if (jobs) { + applog(LOG_DEBUG, "%s %s: Sending %d new jobs", hashfast->drv->name, hashfast->unique_id, + jobs); + } + + while (jobs-- > 0) { + struct hf_hash_usb op_hash_data; + struct work *work; + uint64_t intdiff; + int i, sequence; + uint32_t *p; + + /* This is a blocking function if there's no work */ + if (!base_work) + base_work = get_work(thr, thr->id); + + /* HFA hardware actually had ntime rolling disabled so we + * can roll the work ourselves here to minimise the amount of + * work we need to generate. */ + if (base_work->drv_rolllimit > jobs) { + base_work->drv_rolllimit--; + roll_work(base_work); + work = make_clone(base_work); + } else { + work = base_work; + base_work = NULL; + } + + /* Assemble the data frame and send the OP_HASH packet */ + memcpy(op_hash_data.midstate, work->midstate, sizeof(op_hash_data.midstate)); + memcpy(op_hash_data.merkle_residual, work->data + 64, 4); + p = (uint32_t *)(work->data + 64 + 4); + op_hash_data.timestamp = *p++; + op_hash_data.bits = *p++; + op_hash_data.starting_nonce = 0; + op_hash_data.nonce_loops = 0; + op_hash_data.ntime_loops = 0; + + /* Set the number of leading zeroes to look for based on diff. + * Diff 1 = 32, Diff 2 = 33, Diff 4 = 34 etc. */ + intdiff = (uint64_t)work->device_diff; + for (i = 31; intdiff; i++, intdiff >>= 1); + op_hash_data.search_difficulty = i; + op_hash_data.group = 0; + if ((sequence = info->hash_sequence_head + 1) >= info->num_sequence) + sequence = 0; + ret = hfa_send_frame(hashfast, OP_HASH, sequence, (uint8_t *)&op_hash_data, sizeof(op_hash_data)); + if (unlikely(!ret)) { + free_work(work); + if (base_work) + free_work(base_work); + hfa_running_shutdown(hashfast, info); + return -1; + } + + mutex_lock(&info->lock); + info->hash_sequence_head = sequence; + info->works[info->hash_sequence_head] = work; + mutex_unlock(&info->lock); + + applog(LOG_DEBUG, "%s %s: OP_HASH sequence %d search_difficulty %d work_difficulty %g", + hashfast->drv->name, hashfast->unique_id, info->hash_sequence_head, + op_hash_data.search_difficulty, work->work_difficulty); + } + + if (base_work) + free_work(base_work); + + /* Only count 2/3 of the hashes to smooth out the hashrate for cycles + * that have no hashes added. */ + mutex_lock(&info->lock); + hashes = info->hash_count / 3 * 2; + info->calc_hashes += hashes; + info->hash_count -= hashes; + mutex_unlock(&info->lock); + + return hashes; +} + +static struct api_data *hfa_api_stats(struct cgpu_info *cgpu) +{ + struct hashfast_info *info; + struct hf_long_usb_stats1 *s1; + struct api_data *root = NULL; + struct hf_usb_init_base *db; + int varint, i; + char buf[64]; + + info = cgpu->device_data; + if (!info) + return NULL; + + root = api_add_int(root, "asic count", &info->asic_count, false); + root = api_add_int(root, "core count", &info->core_count, false); + + root = api_add_double(root, "firmware rev", &info->firmware_version, false); + root = api_add_double(root, "hardware rev", &info->hardware_version, false); + db = &info->usb_init_base; + root = api_add_hex32(root, "serial number", &db->serial_number, true); + varint = db->hash_clockrate; + root = api_add_int(root, "base clockrate", &varint, true); + varint = db->inflight_target; + root = api_add_int(root, "inflight target", &varint, true); + varint = db->sequence_modulus; + root = api_add_int(root, "sequence modulus", &varint, true); + root = api_add_int(root, "fan percent", &info->fanspeed, false); + if (info->op_name[0] != '\0') + root = api_add_string(root, "op name", info->op_name, false); + + s1 = &info->stats1; + root = api_add_uint64(root, "rx preambles", &s1->usb_rx_preambles, false); + root = api_add_uint64(root, "rx rcv byte err", &s1->usb_rx_receive_byte_errors, false); + root = api_add_uint64(root, "rx bad hcrc", &s1->usb_rx_bad_hcrc, false); + root = api_add_uint64(root, "tx attempts", &s1->usb_tx_attempts, false); + root = api_add_uint64(root, "tx packets", &s1->usb_tx_packets, false); + root = api_add_uint64(root, "tx incompletes", &s1->usb_tx_incompletes, false); + root = api_add_uint64(root, "tx ep stalled", &s1->usb_tx_endpointstalled, false); + root = api_add_uint64(root, "tx disconnect", &s1->usb_tx_disconnected, false); + root = api_add_uint64(root, "tx suspend", &s1->usb_tx_suspended, false); + varint = s1->max_tx_buffers; + root = api_add_int(root, "max tx buf", &varint, true); + varint = s1->max_rx_buffers; + root = api_add_int(root, "max rx buf", &varint, true); + + for (i = 0; i < info->asic_count; i++) { + struct hf_long_statistics *l; + struct hf_g1_die_data *d; + char which[16]; + double val; + int j; + + if (!info->die_statistics || !info->die_status) + continue; + l = &info->die_statistics[i]; + if (!l) + continue; + d = &info->die_status[i]; + if (!d) + continue; + snprintf(which, sizeof(which), "Asic%d", i); + + snprintf(buf, sizeof(buf), "%s hash clockrate", which); + root = api_add_int(root, buf, &(info->die_data[i].hash_clock), false); + snprintf(buf, sizeof(buf), "%s die temperature", which); + val = GN_DIE_TEMPERATURE(d->die.die_temperature); + root = api_add_double(root, buf, &val, true); + snprintf(buf, sizeof(buf), "%s board temperature", which); + val = board_temperature(d->temperature); + root = api_add_double(root, buf, &val, true); + for (j = 0; j < 6; j++) { + snprintf(buf, sizeof(buf), "%s voltage %d", which, j); + val = GN_CORE_VOLTAGE(d->die.core_voltage[j]); + root = api_add_utility(root, buf, &val, true); + } + snprintf(buf, sizeof(buf), "%s rx header crc", which); + root = api_add_uint64(root, buf, &l->rx_header_crc, false); + snprintf(buf, sizeof(buf), "%s rx body crc", which); + root = api_add_uint64(root, buf, &l->rx_body_crc, false); + snprintf(buf, sizeof(buf), "%s rx header to", which); + root = api_add_uint64(root, buf, &l->rx_header_timeouts, false); + snprintf(buf, sizeof(buf), "%s rx body to", which); + root = api_add_uint64(root, buf, &l->rx_body_timeouts, false); + snprintf(buf, sizeof(buf), "%s cn fifo full", which); + root = api_add_uint64(root, buf, &l->core_nonce_fifo_full, false); + snprintf(buf, sizeof(buf), "%s an fifo full", which); + root = api_add_uint64(root, buf, &l->array_nonce_fifo_full, false); + snprintf(buf, sizeof(buf), "%s stats overrun", which); + root = api_add_uint64(root, buf, &l->stats_overrun, false); + } + + root = api_add_uint64(root, "raw hashcount", &info->raw_hashes, false); + root = api_add_uint64(root, "calc hashcount", &info->calc_hashes, false); + root = api_add_int(root, "no matching work", &info->no_matching_work, false); + root = api_add_uint16(root, "shed count", &info->shed_count, false); + root = api_add_int(root, "resets", &info->resets, false); + + return root; +} + +static void hfa_statline_before(char *buf, size_t bufsiz, struct cgpu_info *hashfast) +{ + struct hashfast_info *info; + struct hf_g1_die_data *d; + double max_volt; + int i; + + if (!hashfast->device_data) + return; + info = hashfast->device_data; + /* Can happen during init sequence */ + if (!info->die_status) + return; + max_volt = 0.0; + + for (i = 0; i < info->asic_count; i++) { + int j; + + d = &info->die_status[i]; + for (j = 0; j < 6; j++) { + double volt = GN_CORE_VOLTAGE(d->die.core_voltage[j]); + + if (volt > max_volt) + max_volt = volt; + } + } + + tailsprintf(buf, bufsiz, "%3dMHz %3.0fC %3d%% %3.2fV", info->base_clock, + hashfast->temp, info->fanspeed, max_volt); +} + +/* We cannot re-initialise so just shut down the device for it to hotplug + * again. */ +static void hfa_reinit(struct cgpu_info *hashfast) +{ + if (hashfast && hashfast->device_data) + hfa_running_shutdown(hashfast, hashfast->device_data); +} + +static void hfa_free_all_work(struct hashfast_info *info) +{ + while (info->device_sequence_tail != info->hash_sequence_head) { + struct work *work; + + if (++info->hash_sequence_tail >= info->num_sequence) + info->hash_sequence_tail = 0; + if (unlikely(!(work = info->works[info->hash_sequence_tail]))) + break; + free_work(work); + info->works[info->hash_sequence_tail] = NULL; + } +} + +static void hfa_shutdown(struct thr_info *thr) +{ + struct cgpu_info *hashfast = thr->cgpu; + struct hashfast_info *info = hashfast->device_data; + + hfa_send_shutdown(hashfast); + pthread_join(info->read_thr, NULL); + hfa_free_all_work(info); + hfa_clear_readbuf(hashfast); + free(info->works); + free(info->die_statistics); + free(info->die_status); + free(info->die_data); + /* Keep the device data intact to allow new instances to match old + * ones. */ +} + +struct device_drv hashfast_drv = { + .drv_id = DRIVER_hashfast, + .dname = "Hashfast", + .name = "HFA", + .max_diff = 32.0, // Limit max diff to get some nonces back regardless + .drv_detect = hfa_detect, + .thread_init = hfa_init, + .hash_work = &hash_driver_work, + .scanwork = hfa_scanwork, + .get_api_stats = hfa_api_stats, + .get_statline_before = hfa_statline_before, + .reinit_device = hfa_reinit, + .thread_shutdown = hfa_shutdown, +}; diff --git a/driver-hashfast.h b/driver-hashfast.h new file mode 100644 index 0000000..331bbd6 --- /dev/null +++ b/driver-hashfast.h @@ -0,0 +1,163 @@ +/* + * Copyright 2013-2014 Con Kolivas + * Copyright 2013 Hashfast + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#ifndef HASHFAST_H +#define HASHFAST_H + +#ifdef USE_HASHFAST +#include "miner.h" +#include "elist.h" +#include "hf_protocol.h" + +int opt_hfa_ntime_roll; +int opt_hfa_hash_clock; +int opt_hfa_overheat; +int opt_hfa_target; +bool opt_hfa_pll_bypass; +bool opt_hfa_dfu_boot; +int opt_hfa_fan_default; +int opt_hfa_fan_max; +int opt_hfa_fan_min; +int opt_hfa_fail_drop; +bool opt_hfa_noshed; + +char *set_hfa_fan(char *arg); +char *opt_hfa_name; +char *opt_hfa_options; + +#define HASHFAST_MINER_THREADS 1 +#define HFA_CLOCK_DEFAULT 550 +#define HFA_CLOCK_MIN 125 +#define HFA_CLOCK_MAX 1000 +#define HFA_CLOCK_MAXDIFF 100 +#define HFA_TEMP_OVERHEAT 95 +#define HFA_TEMP_TARGET 88 +#define HFA_TEMP_HYSTERESIS 3 +#define HFA_FAN_DEFAULT 33 +#define HFA_FAN_MAX 85 +#define HFA_FAN_MIN 5 +#define HFA_VOLTAGE_MAX 1000 +#define HFA_VOLTAGE_MIN 500 +#define HFA_MAGIC_SETTINGS_VALUE 0x42AA + +// # Factory Operation Codes +#define OP_SETTINGS 55 // Read or write settings +#define OP_POWER 57 + +// Matching fields for hf_statistics, but large #s for local accumulation, per-die +struct hf_long_statistics { + uint64_t rx_header_crc; // Header CRCs + uint64_t rx_body_crc; // Data CRCs + uint64_t rx_header_timeouts; // Header timeouts + uint64_t rx_body_timeouts; // Data timeouts + uint64_t core_nonce_fifo_full; // Core nonce Q overrun events + uint64_t array_nonce_fifo_full; // System nonce Q overrun events + uint64_t stats_overrun; // Overrun in statistics reporting +}; + +// Matching fields for hf_usb_stats1, but large #s for local accumulation, per device +struct hf_long_usb_stats1 { + // USB incoming + uint64_t usb_rx_preambles; + uint64_t usb_rx_receive_byte_errors; + uint64_t usb_rx_bad_hcrc; + + // USB outgoing + uint64_t usb_tx_attempts; + uint64_t usb_tx_packets; + uint64_t usb_tx_timeouts; + uint64_t usb_tx_incompletes; + uint64_t usb_tx_endpointstalled; + uint64_t usb_tx_disconnected; + uint64_t usb_tx_suspended; +#if 0 + /* We don't care about UART stats */ + // UART transmit + uint64_t uart_tx_queue_dma; + uint64_t uart_tx_interrupts; + + // UART receive + uint64_t uart_rx_preamble_ints; + uint64_t uart_rx_missed_preamble_ints; + uint64_t uart_rx_header_done; + uint64_t uart_rx_data_done; + uint64_t uart_rx_bad_hcrc; + uint64_t uart_rx_bad_dma; + uint64_t uart_rx_short_dma; + uint64_t uart_rx_buffers_full; +#endif + + uint8_t max_tx_buffers; + uint8_t max_rx_buffers; +}; + +/* Private per die data for dynamic clocking */ +struct hf_die_data { + int hash_clock; + double temp; + double board_temp; + time_t last_restart; +}; + +struct hashfast_info { + struct cgpu_info *cgpu; // Points back to parent structure + struct cgpu_info *old_cgpu ; // Points to old structure if hotplugged same device + int asic_count; // # of chips in the chain + int core_count; // # of cores per chip + int device_type; // What sort of device this is + int num_sequence; // A power of 2. What the sequence number range is. + int ref_frequency; // Reference clock rate + struct hf_g1_die_data *die_status; // Array of per-die voltage, current, temperature sensor data + struct hf_long_statistics *die_statistics; // Array of per-die error counters + struct hf_long_usb_stats1 stats1; + struct hf_die_data *die_data; + double firmware_version; + double hardware_version; + int hash_clock_rate; // Hash clock rate to use, in Mhz + int base_clock; // Clock rate we actually got + struct hf_usb_init_base usb_init_base; // USB Base information from USB_INIT + struct hf_config_data config_data; // Configuration data used from USB_INIT + int core_bitmap_size; // in bytes + uint32_t *core_bitmap; // Core OK bitmap test results, run with PLL Bypassed + int group_ntime_roll; // Total ntime roll amount per group + int core_ntime_roll; // Total core ntime roll amount + uint32_t serial_number; // db->serial_number if it exists + char op_name[36]; + bool has_opname; + bool opname_valid; + + pthread_mutex_t lock; + pthread_mutex_t rlock; + struct work **works; + uint16_t hash_sequence_head; // HOST: The next hash sequence # to be sent + uint16_t hash_sequence_tail; // HOST: Follows device_sequence_tail around to free work + uint16_t device_sequence_head; // DEVICE: The most recent sequence number the device dispatched + uint16_t device_sequence_tail; // DEVICE: The most recently completed job in the device + int64_t hash_count; + uint64_t raw_hashes; + uint64_t calc_hashes; + uint16_t shed_count; // Dynamic copy of #cores device has shed for thermal control + int no_matching_work; + int resets; + int overheat; + int last_max_temp; + int temp_updates; + int fanspeed; // Fanspeed in percent + int last_die_adjusted; + int clock_offset; + int hash_voltage; // Hash voltage to use, in mV + + pthread_t read_thr; + time_t last_restart; + time_t last_send; +}; + +#endif /* USE_HASHFAST */ +#endif /* HASHFAST_H */ diff --git a/driver-hashratio.c b/driver-hashratio.c new file mode 100644 index 0000000..a95f902 --- /dev/null +++ b/driver-hashratio.c @@ -0,0 +1,876 @@ +/* + * Copyright 2013-2014 Con Kolivas + * Copyright 2012-2014 Xiangfu + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#ifndef WIN32 + #include + #include + #include + #ifndef O_CLOEXEC + #define O_CLOEXEC 0 + #endif +#else + #include + #include +#endif + +#include "elist.h" +#include "miner.h" +#include "driver-hashratio.h" +#include "crc.h" +#include "usbutils.h" + +static int opt_hashratio_fan_min = HRTO_DEFAULT_FAN_MIN; +static int opt_hashratio_fan_max = HRTO_DEFAULT_FAN_MAX; + +static int hashratio_freq = HRTO_DEFAULT_FREQUENCY; + +//static int get_fan_pwm(int temp) { +// int pwm; +// uint8_t fan_pwm_arr[] = {30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, +// 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, +// 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, +// 30, 37, 49, 61, 73, 85, 88, 91, 94, 97, 100, 100, 100, 100, 100, 100, +// 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, +// 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, +// 100, 100, 100, 100, 100, 100, 100}; +// if (temp < 0 || temp >= sizeof(fan_pwm_arr)/sizeof(fan_pwm_arr[0]) || +// fan_pwm_arr[temp] > opt_hashratio_fan_max) { +// return opt_hashratio_fan_max; +// } +// pwm = HRTO_PWM_MAX - fan_pwm_arr[temp] * HRTO_PWM_MAX / 100; +// +// if (pwm < opt_hashratio_fan_min) { +// return opt_hashratio_fan_min; +// } +// if (pwm > opt_hashratio_fan_max) { +// return opt_hashratio_fan_max; +// } +// return pwm; +//} + +char *set_hashratio_freq(char *arg) +{ + int val, ret; + + ret = sscanf(arg, "%d", &val); + if (ret != 1) + return "No values passed to hashratio-freq"; + + if (val < HRTO_DEFAULT_FREQUENCY_MIN || val > HRTO_DEFAULT_FREQUENCY_MAX) + return "Invalid value passed to hashratio-freq"; + + hashratio_freq = val; + + return NULL; +} + +static inline uint8_t rev8(uint8_t d) +{ + int i; + uint8_t out = 0; + + /* (from left to right) */ + for (i = 0; i < 8; i++) + if (d & (1 << i)) + out |= (1 << (7 - i)); + + return out; +} + +char *set_hashratio_fan(char *arg) +{ + int val1, val2, ret; + + ret = sscanf(arg, "%d-%d", &val1, &val2); + if (ret < 1) + return "No values passed to hashratio-fan"; + if (ret == 1) + val2 = val1; + + if (val1 < 0 || val1 > 100 || val2 < 0 || val2 > 100 || val2 < val1) + return "Invalid value passed to hashratio-fan"; + + opt_hashratio_fan_min = val1 * HRTO_PWM_MAX / 100; + opt_hashratio_fan_max = val2 * HRTO_PWM_MAX / 100; + + return NULL; +} + +static int hashratio_init_pkg(struct hashratio_pkg *pkg, uint8_t type, + uint8_t idx, uint8_t cnt) +{ + unsigned short crc; + + pkg->head[0] = HRTO_H1; + pkg->head[1] = HRTO_H2; + + pkg->type = type; + pkg->idx = idx; + pkg->cnt = cnt; + + crc = crc16(pkg->data, HRTO_P_DATA_LEN); + + pkg->crc[0] = (crc & 0xff00) >> 8; + pkg->crc[1] = crc & 0x00ff; + return 0; +} + +static int job_idcmp(uint8_t *job_id, char *pool_job_id) +{ + int job_id_len; + unsigned short crc, crc_expect; + + if (!pool_job_id) + return 1; + + job_id_len = strlen(pool_job_id); + crc_expect = crc16((const unsigned char *)pool_job_id, job_id_len); + + crc = job_id[0] << 8 | job_id[1]; + + if (crc_expect == crc) + return 0; + + applog(LOG_DEBUG, "Hashratio: job_id not match! [%04x:%04x (%s)]", + crc, crc_expect, pool_job_id); + + return 1; +} + +static int decode_pkg(struct thr_info *thr, struct hashratio_ret *ar, uint8_t *pkg) +{ + struct cgpu_info *hashratio = thr->cgpu; + struct hashratio_info *info = hashratio->device_data; + struct pool *pool, *real_pool, *pool_stratum = &info->pool; + + unsigned int expected_crc; + unsigned int actual_crc; + uint32_t nonce, nonce2, miner; + int pool_no; + uint8_t job_id[4]; + int tmp; + + int type = HRTO_GETS_ERROR; + + memcpy((uint8_t *)ar, pkg, HRTO_READ_SIZE); + +// applog(LOG_DEBUG, "pkg.type, hex: %02x, dec: %d", ar->type, ar->type); + + if (ar->head[0] == HRTO_H1 && ar->head[1] == HRTO_H2) { + expected_crc = crc16(ar->data, HRTO_P_DATA_LEN); + actual_crc = (ar->crc[0] & 0xff) | + ((ar->crc[1] & 0xff) << 8); + + type = ar->type; + applog(LOG_DEBUG, "hashratio: %d: expected crc(%04x), actual_crc(%04x)", type, expected_crc, actual_crc); + if (expected_crc != actual_crc) + goto out; + + switch(type) { + case HRTO_P_NONCE: + applog(LOG_DEBUG, "Hashratio: HRTO_P_NONCE"); + memcpy(&miner, ar->data + 0, 4); + memcpy(&pool_no, ar->data + 4, 4); + memcpy(&nonce2, ar->data + 8, 4); + /* Calc time ar->data + 12 */ + memcpy(&nonce, ar->data + 12, 4); + memcpy(job_id, ar->data + 16, 4); + + miner = be32toh(miner); + pool_no = be32toh(pool_no); + if (miner >= HRTO_DEFAULT_MINERS || pool_no >= total_pools || pool_no < 0) { + applog(LOG_DEBUG, "hashratio: Wrong miner/pool/id no %d,%d", miner, pool_no); + break; + } else + info->matching_work[miner]++; + nonce2 = be32toh(nonce2); + nonce = be32toh(nonce); + + applog(LOG_DEBUG, "hashratio: Found! [%s] %d:(%08x) (%08x)", + job_id, pool_no, nonce2, nonce); + + real_pool = pool = pools[pool_no]; + if (job_idcmp(job_id, pool->swork.job_id)) { + if (!job_idcmp(job_id, pool_stratum->swork.job_id)) { + applog(LOG_DEBUG, "Hashratio: Match to previous stratum! (%s)", pool_stratum->swork.job_id); + pool = pool_stratum; + } else { + applog(LOG_DEBUG, "Hashratio Cannot match to any stratum! (%s)", pool->swork.job_id); + break; + } + } + submit_nonce2_nonce(thr, pool, real_pool, nonce2, nonce, 0); + break; + case HRTO_P_STATUS: + applog(LOG_DEBUG, "Hashratio: HRTO_P_STATUS"); + memcpy(&tmp, ar->data, 4); + tmp = be32toh(tmp); + info->temp = (tmp & 0x00f0) >> 8; + if (info->temp_max < info->temp) { + info->temp_max = info->temp; + } +// info->temp[1] = tmp & 0xffff; + + memcpy(&tmp, ar->data + 4, 4); + tmp = be32toh(tmp); + info->fan[0] = tmp >> 16; + info->fan[1] = tmp & 0xffff; + + // local_work + memcpy(&tmp, ar->data + 8, 4); + tmp = be32toh(tmp); + info->local_work = tmp; + info->local_works += tmp; + + // hw_work + memcpy(&tmp, ar->data + 12, 4); + tmp = be32toh(tmp); + info->hw_works += tmp; + + hashratio->temp = info->temp; + break; + case HRTO_P_ACKDETECT: + applog(LOG_DEBUG, "Hashratio: HRTO_P_ACKDETECT"); + break; + case HRTO_P_ACK: + applog(LOG_DEBUG, "Hashratio: HRTO_P_ACK"); + break; + case HRTO_P_NAK: + applog(LOG_DEBUG, "Hashratio: HRTO_P_NAK"); + break; + default: + applog(LOG_DEBUG, "Hashratio: HRTO_GETS_ERROR"); + type = HRTO_GETS_ERROR; + break; + } + } + +out: + return type; +} + +static inline int hashratio_gets(struct cgpu_info *hashratio, uint8_t *buf) +{ + int i; + int read_amount = HRTO_READ_SIZE; + uint8_t buf_tmp[HRTO_READ_SIZE]; + uint8_t buf_copy[2 * HRTO_READ_SIZE]; + uint8_t *buf_back = buf; + int ret = 0; + + while (true) { + int err; + + do { + memset(buf, 0, read_amount); + err = usb_read(hashratio, (char *)buf, read_amount, &ret, C_HRO_READ); + if (unlikely(err < 0 || ret != read_amount)) { + applog(LOG_ERR, "hashratio: Error on read in hashratio_gets got %d", ret); + return HRTO_GETS_ERROR; + } + if (likely(ret >= read_amount)) { + for (i = 1; i < read_amount; i++) { + if (buf_back[i - 1] == HRTO_H1 && buf_back[i] == HRTO_H2) + break; + } + i -= 1; + if (i) { + err = usb_read(hashratio, (char *)buf, read_amount, &ret, C_HRO_READ); + if (unlikely(err < 0 || ret != read_amount)) { + applog(LOG_ERR, "hashratio: Error on 2nd read in hashratio_gets got %d", ret); + return HRTO_GETS_ERROR; + } + memcpy(buf_copy, buf_back + i, HRTO_READ_SIZE - i); + memcpy(buf_copy + HRTO_READ_SIZE - i, buf_tmp, i); + memcpy(buf_back, buf_copy, HRTO_READ_SIZE); + } + return HRTO_GETS_OK; + } + buf += ret; + read_amount -= ret; + continue; + } while (ret > 0); + + return HRTO_GETS_TIMEOUT; + } +} + +static int hashratio_send_pkg(struct cgpu_info *hashratio, const struct hashratio_pkg *pkg) +{ + int err, amount; + uint8_t buf[HRTO_WRITE_SIZE]; + int nr_len = HRTO_WRITE_SIZE; + + memcpy(buf, pkg, HRTO_WRITE_SIZE); +// if (opt_debug) { +// applog(LOG_DEBUG, "hashratio: Sent(%d):", nr_len); +// hexdump((uint8_t *)buf, nr_len); +// } + + if (unlikely(hashratio->usbinfo.nodev)) + return HRTO_SEND_ERROR; + + err = usb_write(hashratio, (char *)buf, nr_len, &amount, C_HRO_WRITE); + if (err || amount != nr_len) { + applog(LOG_DEBUG, "hashratio: Send(%d)!", amount); + return HRTO_SEND_ERROR; + } + + return HRTO_SEND_OK; +} + +static int hashratio_send_pkgs(struct cgpu_info *hashratio, const struct hashratio_pkg *pkg) +{ + int ret; + + do { + if (unlikely(hashratio->usbinfo.nodev)) + return -1; + ret = hashratio_send_pkg(hashratio, pkg); + } while (ret != HRTO_SEND_OK); + return 0; +} + +static void hashratio_stratum_pkgs(struct cgpu_info *hashratio, struct pool *pool) +{ + const int merkle_offset = 36; + struct hashratio_pkg pkg; + int i, a, b, tmp; + unsigned char target[32]; + int job_id_len; + unsigned short crc; + + /* Send out the first stratum message STATIC */ + applog(LOG_DEBUG, "hashratio: Pool stratum message STATIC: %d, %d, %d, %d, %d, %d", + pool->coinbase_len, + pool->nonce2_offset, + pool->n2size, + merkle_offset, + pool->merkles, + pool->pool_no); + memset(pkg.data, 0, HRTO_P_DATA_LEN); + tmp = be32toh(pool->coinbase_len); + memcpy(pkg.data, &tmp, 4); + + tmp = be32toh(pool->nonce2_offset); + memcpy(pkg.data + 4, &tmp, 4); + + tmp = be32toh(pool->n2size); + memcpy(pkg.data + 8, &tmp, 4); + + tmp = be32toh(merkle_offset); + memcpy(pkg.data + 12, &tmp, 4); + + tmp = be32toh(pool->merkles); + memcpy(pkg.data + 16, &tmp, 4); + + tmp = be32toh((int)pool->sdiff); + memcpy(pkg.data + 20, &tmp, 4); + + tmp = be32toh((int)pool->pool_no); + memcpy(pkg.data + 24, &tmp, 4); + + hashratio_init_pkg(&pkg, HRTO_P_STATIC, 1, 1); + if (hashratio_send_pkgs(hashratio, &pkg)) + return; + + set_target(target, pool->sdiff); + memcpy(pkg.data, target, 32); + if (opt_debug) { + char *target_str; + target_str = bin2hex(target, 32); + applog(LOG_DEBUG, "hashratio: Pool stratum target: %s", target_str); + free(target_str); + } + hashratio_init_pkg(&pkg, HRTO_P_TARGET, 1, 1); + if (hashratio_send_pkgs(hashratio, &pkg)) + return; + + applog(LOG_DEBUG, "hashratio: Pool stratum message JOBS_ID: %s", + pool->swork.job_id); + memset(pkg.data, 0, HRTO_P_DATA_LEN); + + job_id_len = strlen(pool->swork.job_id); + crc = crc16((const unsigned char *)pool->swork.job_id, job_id_len); + pkg.data[0] = (crc & 0xff00) >> 8; + pkg.data[1] = crc & 0x00ff; + hashratio_init_pkg(&pkg, HRTO_P_JOB_ID, 1, 1); + if (hashratio_send_pkgs(hashratio, &pkg)) + return; + + a = pool->coinbase_len / HRTO_P_DATA_LEN; + b = pool->coinbase_len % HRTO_P_DATA_LEN; + applog(LOG_DEBUG, "pool->coinbase_len: %d", pool->coinbase_len); + applog(LOG_DEBUG, "hashratio: Pool stratum message COINBASE: %d %d", a, b); + for (i = 0; i < a; i++) { + memcpy(pkg.data, pool->coinbase + i * 32, 32); + hashratio_init_pkg(&pkg, HRTO_P_COINBASE, i + 1, a + (b ? 1 : 0)); + if (hashratio_send_pkgs(hashratio, &pkg)) + return; + if (i % 25 == 0) { + cgsleep_ms(2); + } + } + if (b) { + memset(pkg.data, 0, HRTO_P_DATA_LEN); + memcpy(pkg.data, pool->coinbase + i * 32, b); + hashratio_init_pkg(&pkg, HRTO_P_COINBASE, i + 1, i + 1); + if (hashratio_send_pkgs(hashratio, &pkg)) + return; + } + + b = pool->merkles; + applog(LOG_DEBUG, "hashratio: Pool stratum message MERKLES: %d", b); + for (i = 0; i < b; i++) { + memset(pkg.data, 0, HRTO_P_DATA_LEN); + memcpy(pkg.data, pool->swork.merkle_bin[i], 32); + hashratio_init_pkg(&pkg, HRTO_P_MERKLES, i + 1, b); + if (hashratio_send_pkgs(hashratio, &pkg)) + return; + } + + applog(LOG_DEBUG, "hashratio: Pool stratum message HEADER: 4"); + for (i = 0; i < 4; i++) { + memset(pkg.data, 0, HRTO_P_HEADER); + memcpy(pkg.data, pool->header_bin + i * 32, 32); + hashratio_init_pkg(&pkg, HRTO_P_HEADER, i + 1, 4); + if (hashratio_send_pkgs(hashratio, &pkg)) + return; + + } +} + +static int hashratio_get_result(struct thr_info *thr, struct hashratio_ret *ar) +{ + struct cgpu_info *hashratio = thr->cgpu; + uint8_t result[HRTO_READ_SIZE]; + int ret; + + memset(result, 0, HRTO_READ_SIZE); + + ret = hashratio_gets(hashratio, result); + if (ret != HRTO_GETS_OK) + return ret; + +// if (opt_debug) { +// applog(LOG_DEBUG, "hashratio: Get(ret = %d):", ret); +// hexdump((uint8_t *)result, HRTO_READ_SIZE); +// } + + return decode_pkg(thr, ar, result); +} + +#define HASHRATIO_LATENCY 5 + +static void hashratio_initialise(struct cgpu_info *hashratio) +{ + int err, interface; + + if (hashratio->usbinfo.nodev) + return; + + interface = usb_interface(hashratio); + // Reset + err = usb_transfer(hashratio, FTDI_TYPE_OUT, FTDI_REQUEST_RESET, + FTDI_VALUE_RESET, interface, C_RESET); + + applog(LOG_DEBUG, "%s%i: reset got err %d", + hashratio->drv->name, hashratio->device_id, err); + + if (hashratio->usbinfo.nodev) + return; + + // Set latency + err = usb_transfer(hashratio, FTDI_TYPE_OUT, FTDI_REQUEST_LATENCY, + HASHRATIO_LATENCY, interface, C_LATENCY); + + applog(LOG_DEBUG, "%s%i: latency got err %d", + hashratio->drv->name, hashratio->device_id, err); + + if (hashratio->usbinfo.nodev) + return; + + // Set data + err = usb_transfer(hashratio, FTDI_TYPE_OUT, FTDI_REQUEST_DATA, + FTDI_VALUE_DATA_AVA, interface, C_SETDATA); + + applog(LOG_DEBUG, "%s%i: data got err %d", + hashratio->drv->name, hashratio->device_id, err); + + if (hashratio->usbinfo.nodev) + return; + + // Set the baud + err = usb_transfer(hashratio, FTDI_TYPE_OUT, FTDI_REQUEST_BAUD, FTDI_VALUE_BAUD_AVA, + (FTDI_INDEX_BAUD_AVA & 0xff00) | interface, + C_SETBAUD); + + applog(LOG_DEBUG, "%s%i: setbaud got err %d", + hashratio->drv->name, hashratio->device_id, err); + + if (hashratio->usbinfo.nodev) + return; + + // Set Modem Control + err = usb_transfer(hashratio, FTDI_TYPE_OUT, FTDI_REQUEST_MODEM, + FTDI_VALUE_MODEM, interface, C_SETMODEM); + + applog(LOG_DEBUG, "%s%i: setmodemctrl got err %d", + hashratio->drv->name, hashratio->device_id, err); + + if (hashratio->usbinfo.nodev) + return; + + // Set Flow Control + err = usb_transfer(hashratio, FTDI_TYPE_OUT, FTDI_REQUEST_FLOW, + FTDI_VALUE_FLOW, interface, C_SETFLOW); + + applog(LOG_DEBUG, "%s%i: setflowctrl got err %d", + hashratio->drv->name, hashratio->device_id, err); + + if (hashratio->usbinfo.nodev) + return; + + /* hashratio repeats the following */ + // Set Modem Control + err = usb_transfer(hashratio, FTDI_TYPE_OUT, FTDI_REQUEST_MODEM, + FTDI_VALUE_MODEM, interface, C_SETMODEM); + + applog(LOG_DEBUG, "%s%i: setmodemctrl 2 got err %d", + hashratio->drv->name, hashratio->device_id, err); + + if (hashratio->usbinfo.nodev) + return; + + // Set Flow Control + err = usb_transfer(hashratio, FTDI_TYPE_OUT, FTDI_REQUEST_FLOW, + FTDI_VALUE_FLOW, interface, C_SETFLOW); + + applog(LOG_DEBUG, "%s%i: setflowctrl 2 got err %d", + hashratio->drv->name, hashratio->device_id, err); +} + +static struct cgpu_info *hashratio_detect_one(struct libusb_device *dev, struct usb_find_devices *found) +{ + struct hashratio_info *info; + int err, amount; + int ackdetect; + char mm_version[16]; + + struct cgpu_info *hashratio = usb_alloc_cgpu(&hashratio_drv, 1); + struct hashratio_pkg detect_pkg; + struct hashratio_ret ret_pkg; + + if (!usb_init(hashratio, dev, found)) { + applog(LOG_ERR, "Hashratio failed usb_init"); + hashratio = usb_free_cgpu(hashratio); + return NULL; + } + + hashratio_initialise(hashratio); + + strcpy(mm_version, "NONE"); + /* Send out detect pkg */ + memset(detect_pkg.data, 0, HRTO_P_DATA_LEN); + + hashratio_init_pkg(&detect_pkg, HRTO_P_DETECT, 1, 1); + hashratio_send_pkg(hashratio, &detect_pkg); + err = usb_read(hashratio, (char *)&ret_pkg, HRTO_READ_SIZE, &amount, C_HRO_READ); + if (err || amount != HRTO_READ_SIZE) { + applog(LOG_ERR, "%s %d: Hashratio failed usb_read with err %d amount %d", + hashratio->drv->name, hashratio->device_id, err, amount); + usb_uninit(hashratio); + usb_free_cgpu(hashratio); + return NULL; + } + + ackdetect = ret_pkg.type; + applog(LOG_DEBUG, "hashratio Detect ID: %d", ackdetect); + + if (ackdetect != HRTO_P_ACKDETECT) { + applog(LOG_DEBUG, "Not a hashratio device"); + usb_uninit(hashratio); + usb_free_cgpu(hashratio); + return NULL; + } + + memcpy(mm_version, ret_pkg.data, 15); + mm_version[15] = '\0'; + + /* We have a real Hashratio! */ + hashratio->threads = HRTO_MINER_THREADS; + add_cgpu(hashratio); + + update_usb_stats(hashratio); + + applog(LOG_INFO, "%s%d: Found at %s", hashratio->drv->name, hashratio->device_id, + hashratio->device_path); + + hashratio->device_data = calloc(sizeof(struct hashratio_info), 1); + if (unlikely(!(hashratio->device_data))) + quit(1, "Failed to malloc hashratio_info"); + + info = hashratio->device_data; + + strcpy(info->mm_version, mm_version); + + info->fan_pwm = HRTO_DEFAULT_FAN / 100 * HRTO_PWM_MAX; + info->temp_max = 0; + info->temp_history_index = 0; + info->temp_sum = 0; + info->temp_old = 0; + info->default_freq = hashratio_freq; + + return hashratio; +} + +static inline void hashratio_detect(bool __maybe_unused hotplug) +{ + usb_detect(&hashratio_drv, hashratio_detect_one); +} + +static bool hashratio_prepare(struct thr_info *thr) +{ + struct cgpu_info *hashratio = thr->cgpu; + struct hashratio_info *info = hashratio->device_data; + + cglock_init(&info->pool.data_lock); + + return true; +} + +static void copy_pool_stratum(struct hashratio_info *info, struct pool *pool) +{ + int i; + int merkles = pool->merkles; + size_t coinbase_len = pool->coinbase_len; + struct pool *pool_stratum = &info->pool; + + if (!job_idcmp((uint8_t *)pool->swork.job_id, pool_stratum->swork.job_id)) + return; + + cg_wlock(&(pool_stratum->data_lock)); + free(pool_stratum->swork.job_id); + free(pool_stratum->nonce1); + free(pool_stratum->coinbase); + + align_len(&coinbase_len); + pool_stratum->coinbase = calloc(coinbase_len, 1); + if (unlikely(!pool_stratum->coinbase)) + quit(1, "Failed to calloc pool_stratum coinbase in hashratio"); + memcpy(pool_stratum->coinbase, pool->coinbase, coinbase_len); + + + for (i = 0; i < pool_stratum->merkles; i++) + free(pool_stratum->swork.merkle_bin[i]); + if (merkles) { + pool_stratum->swork.merkle_bin = realloc(pool_stratum->swork.merkle_bin, + sizeof(char *) * merkles + 1); + for (i = 0; i < merkles; i++) { + pool_stratum->swork.merkle_bin[i] = malloc(32); + if (unlikely(!pool_stratum->swork.merkle_bin[i])) + quit(1, "Failed to malloc pool_stratum swork merkle_bin"); + memcpy(pool_stratum->swork.merkle_bin[i], pool->swork.merkle_bin[i], 32); + } + } + + pool_stratum->sdiff = pool->sdiff; + pool_stratum->coinbase_len = pool->coinbase_len; + pool_stratum->nonce2_offset = pool->nonce2_offset; + pool_stratum->n2size = pool->n2size; + pool_stratum->merkles = pool->merkles; + + pool_stratum->swork.job_id = strdup(pool->swork.job_id); + pool_stratum->nonce1 = strdup(pool->nonce1); + + memcpy(pool_stratum->ntime, pool->ntime, sizeof(pool_stratum->ntime)); + memcpy(pool_stratum->header_bin, pool->header_bin, sizeof(pool_stratum->header_bin)); + cg_wunlock(&(pool_stratum->data_lock)); +} + +static void hashratio_update_work(struct cgpu_info *hashratio) +{ + struct hashratio_info *info = hashratio->device_data; + struct thr_info *thr = hashratio->thr[0]; + struct hashratio_pkg send_pkg; + uint32_t tmp, range, start; + struct work *work; + struct pool *pool; + + applog(LOG_DEBUG, "hashratio: New stratum: restart: %d, update: %d", + thr->work_restart, thr->work_update); + thr->work_update = false; + thr->work_restart = false; + + work = get_work(thr, thr->id); /* Make sure pool is ready */ + discard_work(work); /* Don't leak memory */ + + pool = current_pool(); + if (!pool->has_stratum) + quit(1, "hashratio: Miner Manager have to use stratum pool"); + if (pool->coinbase_len > HRTO_P_COINBASE_SIZE) + quit(1, "hashratio: Miner Manager pool coinbase length have to less then %d", HRTO_P_COINBASE_SIZE); + if (pool->merkles > HRTO_P_MERKLES_COUNT) + quit(1, "hashratio: Miner Manager merkles have to less then %d", HRTO_P_MERKLES_COUNT); + + info->pool_no = pool->pool_no; + + cgtime(&info->last_stratum); + cg_rlock(&pool->data_lock); + info->pool_no = pool->pool_no; + copy_pool_stratum(info, pool); + hashratio_stratum_pkgs(hashratio, pool); + cg_runlock(&pool->data_lock); + + /* Configure the parameter from outside */ + memset(send_pkg.data, 0, HRTO_P_DATA_LEN); + + // fan. We're not measuring temperature so set a safe but not max value + info->fan_pwm = HRTO_PWM_MAX * 2 / 3; + tmp = be32toh(info->fan_pwm); + memcpy(send_pkg.data, &tmp, 4); + + // freq + tmp = be32toh(info->default_freq); + memcpy(send_pkg.data + 4, &tmp, 4); + applog(LOG_DEBUG, "set freq: %d", info->default_freq); + + /* Configure the nonce2 offset and range */ + range = 0xffffffff / (total_devices + 1); + start = range * (hashratio->device_id + 1); + + tmp = be32toh(start); + memcpy(send_pkg.data + 8, &tmp, 4); + + tmp = be32toh(range); + memcpy(send_pkg.data + 12, &tmp, 4); + + /* Package the data */ + hashratio_init_pkg(&send_pkg, HRTO_P_SET, 1, 1); + hashratio_send_pkgs(hashratio, &send_pkg); +} + +static int64_t hashratio_scanhash(struct thr_info *thr) +{ + struct cgpu_info *hashratio = thr->cgpu; + struct hashratio_info *info = hashratio->device_data; + struct hashratio_pkg send_pkg; + struct hashratio_ret ar; + + memset(send_pkg.data, 0, HRTO_P_DATA_LEN); + hashratio_init_pkg(&send_pkg, HRTO_P_POLLING, 1, 1); + + if (unlikely(hashratio->usbinfo.nodev || hashratio_send_pkgs(hashratio, &send_pkg))) { + applog(LOG_ERR, "%s%d: Device disappeared, shutting down thread", + hashratio->drv->name, hashratio->device_id); + return -1; + } + hashratio_get_result(thr, &ar); + + return (int64_t)info->local_work * 64 * 0xffffffff; +} + +static struct api_data *hashratio_api_stats(struct cgpu_info *cgpu) +{ + struct api_data *root = NULL; + struct hashratio_info *info = cgpu->device_data; + char buf[24]; + char buf2[256]; + double hwp; + int i; + + // mm version + sprintf(buf, "MM Version"); + root = api_add_string(root, buf, info->mm_version, false); + + // asic freq + sprintf(buf, "Asic Freq (MHz)"); + root = api_add_int(root, buf, &(info->default_freq), false); + + // match work count + for (i = 0; i < HRTO_DEFAULT_MODULARS; i++) { + sprintf(buf, "Match work Modular %02d", i + 1); + memset(buf2, 0, sizeof(buf2)); + snprintf(buf2, sizeof(buf2), + "%02d:%08d %02d:%08d %02d:%08d %02d:%08d " + "%02d:%08d %02d:%08d %02d:%08d %02d:%08d " + "%02d:%08d %02d:%08d %02d:%08d %02d:%08d " + "%02d:%08d %02d:%08d %02d:%08d %02d:%08d", + i*16 + 1, info->matching_work[i*16 + 0], + i*16 + 2, info->matching_work[i*16 + 1], + i*16 + 3, info->matching_work[i*16 + 2], + i*16 + 4, info->matching_work[i*16 + 3], + i*16 + 5, info->matching_work[i*16 + 4], + i*16 + 6, info->matching_work[i*16 + 5], + i*16 + 7, info->matching_work[i*16 + 6], + i*16 + 8, info->matching_work[i*16 + 7], + i*16 + 9, info->matching_work[i*16 + 8], + i*16 + 10, info->matching_work[i*16 + 9], + i*16 + 11, info->matching_work[i*16 + 10], + i*16 + 12, info->matching_work[i*16 + 11], + i*16 + 13, info->matching_work[i*16 + 12], + i*16 + 14, info->matching_work[i*16 + 13], + i*16 + 15, info->matching_work[i*16 + 14], + i*16 + 16, info->matching_work[i*16 + 15]); + root = api_add_string(root, buf, buf2, true); + } + + // local works + sprintf(buf, "Local works"); + root = api_add_int(root, buf, &(info->local_works), false); + + // hardware error works + sprintf(buf, "Hardware error works"); + root = api_add_int(root, buf, &(info->hw_works), false); + + // device hardware error % + hwp = info->local_works ? ((double)info->hw_works / (double)info->local_works) : 0; + sprintf(buf, "Device hardware error%%"); + root = api_add_percent(root, buf, &hwp, true); + + // Temperature + sprintf(buf, "Temperature"); + root = api_add_int(root, buf, &(info->temp), false); + + // Fan + for (i = 0; i < HRTO_FAN_COUNT; i++) { + sprintf(buf, "Fan%d", i+1); + root = api_add_int(root, buf, &(info->fan[i]), false); + } + + return root; +} + +static void hashratio_shutdown(struct thr_info __maybe_unused *thr) +{ +} + +struct device_drv hashratio_drv = { + .drv_id = DRIVER_hashratio, + .dname = "hashratio", + .name = "HRO", + .get_api_stats = hashratio_api_stats, + .drv_detect = hashratio_detect, + .thread_prepare = hashratio_prepare, + .hash_work = hash_driver_work, + .scanwork = hashratio_scanhash, + .flush_work = hashratio_update_work, + .update_work = hashratio_update_work, + .thread_shutdown = hashratio_shutdown, +}; diff --git a/driver-hashratio.h b/driver-hashratio.h new file mode 100644 index 0000000..e155b5a --- /dev/null +++ b/driver-hashratio.h @@ -0,0 +1,131 @@ +/* + * Copyright 2013-2014 Con Kolivas + * Copyright 2012-2014 Xiangfu + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#ifndef _HASHRATIO_H_ +#define _HASHRATIO_H_ + +#include "miner.h" +#include "util.h" + +#ifdef USE_HASHRATIO +char *opt_hashratio_freq; + +#define HRTO_MINER_THREADS 1 + +#define HRTO_RESET_FAULT_DECISECONDS 10 +#define HRTO_IO_SPEED 115200 + +#define HRTO_DEFAULT_MODULARS 5 +#define HRTO_DEFAULT_MINERS_PER_MODULAR 16 +/* total chips number */ +#define HRTO_DEFAULT_MINERS (HRTO_DEFAULT_MODULARS * 16) + +#define HRTO_PWM_MAX 0x3FF +#define HRTO_DEFAULT_FAN 20 /* N% */ +#define HRTO_DEFAULT_FAN_MIN 50 /* N% */ +#define HRTO_DEFAULT_FAN_MAX 100 /* N% */ + +#define HRTO_DEFAULT_FREQUENCY 280 /* MHz */ +#define HRTO_DEFAULT_FREQUENCY_MIN 100 +#define HRTO_DEFAULT_FREQUENCY_MAX 750 + +#define HRTO_FAN_COUNT 2 +//#define HRTO_TEMP_COUNT 1 + +/* Hashratio protocol package type */ +#define HRTO_H1 'H' +#define HRTO_H2 'R' + +#define HRTO_P_COINBASE_SIZE (6 * 1024) +#define HRTO_P_MERKLES_COUNT 20 + +#define HRTO_P_COUNT 39 +#define HRTO_P_DATA_LEN (HRTO_P_COUNT - 7) + +#define HRTO_P_DETECT 10 // 0x0a +#define HRTO_P_STATIC 11 // 0x0b +#define HRTO_P_JOB_ID 12 // 0x0c +#define HRTO_P_COINBASE 13 // 0x0d +#define HRTO_P_MERKLES 14 // 0x0e +#define HRTO_P_HEADER 15 // 0x0f +#define HRTO_P_POLLING 16 // 0x10 +#define HRTO_P_TARGET 17 // 0x11 +#define HRTO_P_REQUIRE 18 // 0x12 +#define HRTO_P_SET 19 // 0x13 +#define HRTO_P_TEST 20 // 0x14 + +#define HRTO_P_ACK 51 // 0x33 +#define HRTO_P_NAK 52 // 0x34 +#define HRTO_P_NONCE 53 // 0x35 +#define HRTO_P_STATUS 54 // 0x36 +#define HRTO_P_ACKDETECT 55 // 0x37 +#define HRTO_P_TEST_RET 56 // 0x38 +/* Hashratio protocol package type */ + +struct hashratio_pkg { + uint8_t head[2]; + uint8_t type; + uint8_t idx; + uint8_t cnt; + uint8_t data[32]; + uint8_t crc[2]; +}; +#define hashratio_ret hashratio_pkg + +struct hashratio_info { + int default_freq; + + int fan_pwm; + + int temp; + int fan[HRTO_FAN_COUNT]; +// uint8_t freq[HRTO_DEFAULT_MINERS]; + uint8_t target_freq[HRTO_DEFAULT_MINERS]; + + int temp_max; + int temp_history_count; + int temp_history_index; + int temp_sum; + int temp_old; + + struct timeval last_stratum; + struct pool pool; + int pool_no; + + int local_works; + int hw_works; + int matching_work[HRTO_DEFAULT_MINERS]; + int local_work; + int hw_work; + +// uint32_t get_result_counter; + + char mm_version[16]; +}; + +#define HRTO_WRITE_SIZE (sizeof(struct hashratio_pkg)) +#define HRTO_READ_SIZE HRTO_WRITE_SIZE + +#define HRTO_GETS_OK 0 +#define HRTO_GETS_TIMEOUT -1 +#define HRTO_GETS_RESTART -2 +#define HRTO_GETS_ERROR -3 + +#define HRTO_SEND_OK 0 +#define HRTO_SEND_ERROR -1 + +#define hashratio_open(devpath, baud, purge) serial_open(devpath, baud, HRTO_RESET_FAULT_DECISECONDS, purge) +#define hashratio_close(fd) close(fd) + +extern char *set_hashratio_fan(char *arg); +extern char *set_hashratio_freq(char *arg); + +#endif /* USE_HASHRATIO */ +#endif /* _HASHRATIO_H_ */ diff --git a/driver-icarus.c b/driver-icarus.c new file mode 100644 index 0000000..2971121 --- /dev/null +++ b/driver-icarus.c @@ -0,0 +1,2462 @@ +/* + * Copyright 2012-2013 Andrew Smith + * Copyright 2012 Xiangfu + * Copyright 2013-2014 Con Kolivas + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +/* + * Those code should be works fine with V2 and V3 bitstream of Icarus. + * Operation: + * No detection implement. + * Input: 64B = 32B midstate + 20B fill bytes + last 12 bytes of block head. + * Return: send back 32bits immediately when Icarus found a valid nonce. + * no query protocol implemented here, if no data send back in ~11.3 + * seconds (full cover time on 32bit nonce range by 380MH/s speed) + * just send another work. + * Notice: + * 1. Icarus will start calculate when you push a work to them, even they + * are busy. + * 2. The 2 FPGAs on Icarus will distribute the job, one will calculate the + * 0 ~ 7FFFFFFF, another one will cover the 80000000 ~ FFFFFFFF. + * 3. It's possible for 2 FPGAs both find valid nonce in the meantime, the 2 + * valid nonce will all be send back. + * 4. Icarus will stop work when: a valid nonce has been found or 32 bits + * nonce range is completely calculated. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" + +#ifdef WIN32 +#include +#endif + +#include "compat.h" +#include "miner.h" +#include "usbutils.h" + +// The serial I/O speed - Linux uses a define 'B115200' in bits/termios.h +#define ICARUS_IO_SPEED 115200 + +#define ICARUS_BUF_SIZE 8 +// The size of a successful nonce read +#define ANT_READ_SIZE 5 +#define ICARUS_READ_SIZE 4 +#define ROCK_READ_SIZE 8 + +// Ensure the sizes are correct for the Serial read +#if (ICARUS_READ_SIZE != 4) +#error ICARUS_READ_SIZE must be 4 +#endif +#define ASSERT1(condition) __maybe_unused static char sizeof_uint32_t_must_be_4[(condition)?1:-1] +ASSERT1(sizeof(uint32_t) == 4); + +// TODO: USB? Different calculation? - see usbstats to work it out e.g. 1/2 of normal send time +// or even use that number? 1/2 +// #define ICARUS_READ_TIME(baud) ((double)ICARUS_READ_SIZE * (double)8.0 / (double)(baud)) +// maybe 1ms? +#define ICARUS_READ_TIME(baud) (0.001) + +// USB ms timeout to wait - user specified timeouts are multiples of this +#define ICA_WAIT_TIMEOUT 100 +#define ANT_WAIT_TIMEOUT 10 +#define AU3_WAIT_TIMEOUT 1 +#define ICARUS_WAIT_TIMEOUT (info->u3 ? AU3_WAIT_TIMEOUT : (info->ant ? ANT_WAIT_TIMEOUT : ICA_WAIT_TIMEOUT)) + +#define ICARUS_CMR2_TIMEOUT 1 + +// Defined in multiples of ICARUS_WAIT_TIMEOUT +// Must of course be greater than ICARUS_READ_COUNT_TIMING/ICARUS_WAIT_TIMEOUT +// There's no need to have this bigger, since the overhead/latency of extra work +// is pretty small once you get beyond a 10s nonce range time and 10s also +// means that nothing slower than 429MH/s can go idle so most icarus devices +// will always mine without idling +#define ICARUS_READ_TIME_LIMIT_MAX 100 + +// In timing mode: Default starting value until an estimate can be obtained +// 5000 ms allows for up to a ~840MH/s device +#define ICARUS_READ_COUNT_TIMING 5000 + +// Antminer USB is > 1GH/s so use a shorter limit +// 1000 ms allows for up to ~4GH/s device +#define ANTUSB_READ_COUNT_TIMING 1000 + +#define ANTU3_READ_COUNT_TIMING 100 + +#define ICARUS_READ_COUNT_MIN ICARUS_WAIT_TIMEOUT +#define SECTOMS(s) ((int)((s) * 1000)) +// How many ms below the expected completion time to abort work +// extra in case the last read is delayed +#define ICARUS_READ_REDUCE ((int)(ICARUS_WAIT_TIMEOUT * 1.5)) + +// For a standard Icarus REV3 (to 5 places) +// Since this rounds up a the last digit - it is a slight overestimate +// Thus the hash rate will be a VERY slight underestimate +// (by a lot less than the displayed accuracy) +// Minor inaccuracy of these numbers doesn't affect the work done, +// only the displayed MH/s +#define ICARUS_REV3_HASH_TIME 0.0000000026316 +#define LANCELOT_HASH_TIME 0.0000000025000 +#define ASICMINERUSB_HASH_TIME 0.0000000029761 +// TODO: What is it? +#define CAIRNSMORE1_HASH_TIME 0.0000000027000 +// Per FPGA +#define CAIRNSMORE2_HASH_TIME 0.0000000066600 +#define NANOSEC 1000000000.0 +#define ANTMINERUSB_HASH_MHZ 0.000000125 +#define ANTMINERUSB_HASH_TIME (ANTMINERUSB_HASH_MHZ / (double)(opt_anu_freq)) +#define ANTU3_HASH_MHZ 0.0000000032 +#define ANTU3_HASH_TIME (ANTU3_HASH_MHZ / (double)(opt_au3_freq)) + +#define CAIRNSMORE2_INTS 4 + +// Icarus Rev3 doesn't send a completion message when it finishes +// the full nonce range, so to avoid being idle we must abort the +// work (by starting a new work item) shortly before it finishes +// +// Thus we need to estimate 2 things: +// 1) How many hashes were done if the work was aborted +// 2) How high can the timeout be before the Icarus is idle, +// to minimise the number of work items started +// We set 2) to 'the calculated estimate' - ICARUS_READ_REDUCE +// to ensure the estimate ends before idle +// +// The simple calculation used is: +// Tn = Total time in seconds to calculate n hashes +// Hs = seconds per hash +// Xn = number of hashes +// W = code/usb overhead per work +// +// Rough but reasonable estimate: +// Tn = Hs * Xn + W (of the form y = mx + b) +// +// Thus: +// Line of best fit (using least squares) +// +// Hs = (n*Sum(XiTi)-Sum(Xi)*Sum(Ti))/(n*Sum(Xi^2)-Sum(Xi)^2) +// W = Sum(Ti)/n - (Hs*Sum(Xi))/n +// +// N.B. W is less when aborting work since we aren't waiting for the reply +// to be transferred back (ICARUS_READ_TIME) +// Calculating the hashes aborted at n seconds is thus just n/Hs +// (though this is still a slight overestimate due to code delays) +// + +// Both below must be exceeded to complete a set of data +// Minimum how long after the first, the last data point must be +#define HISTORY_SEC 60 +// Minimum how many points a single ICARUS_HISTORY should have +#define MIN_DATA_COUNT 5 +// The value MIN_DATA_COUNT used is doubled each history until it exceeds: +#define MAX_MIN_DATA_COUNT 100 + +static struct timeval history_sec = { HISTORY_SEC, 0 }; + +// Store the last INFO_HISTORY data sets +// [0] = current data, not yet ready to be included as an estimate +// Each new data set throws the last old set off the end thus +// keeping a ongoing average of recent data +#define INFO_HISTORY 10 + +struct ICARUS_HISTORY { + struct timeval finish; + double sumXiTi; + double sumXi; + double sumTi; + double sumXi2; + uint32_t values; + uint32_t hash_count_min; + uint32_t hash_count_max; +}; + +enum timing_mode { MODE_DEFAULT, MODE_SHORT, MODE_LONG, MODE_VALUE }; + +static const char *MODE_DEFAULT_STR = "default"; +static const char *MODE_SHORT_STR = "short"; +static const char *MODE_SHORT_STREQ = "short="; +static const char *MODE_LONG_STR = "long"; +static const char *MODE_LONG_STREQ = "long="; +static const char *MODE_VALUE_STR = "value"; +static const char *MODE_UNKNOWN_STR = "unknown"; + +#define MAX_DEVICE_NUM 100 +#define MAX_WORK_BUFFER_SIZE 2 +#define MAX_CHIP_NUM 24 +// Set it to 3, 5 or 9 +#define NONCE_CORRECTION_TIMES 5 +#define MAX_TRIES 4 +#define RM_CMD_MASK 0x0F +#define RM_STATUS_MASK 0xF0 +#define RM_CHIP_MASK 0x3F +#define RM_PRODUCT_MASK 0xC0 +#define RM_PRODUCT_RBOX 0x00 +#define RM_PRODUCT_T1 0x40 +#define RM_PRODUCT_T2 0x80 +#define RM_PRODUCT_TEST 0xC0 + +#if (NONCE_CORRECTION_TIMES == 5) +static int32_t rbox_corr_values[] = {0, 1, -1, -2, -4}; +#endif +#if (NONCE_CORRECTION_TIMES == 9) +static int32_t rbox_corr_values[] = {0, 1, -1, 2, -2, 3, -3, 4, -4}; +#endif +#if (NONCE_CORRECTION_TIMES == 3) +static int32_t rbox_corr_values[] = {0, 1, -1}; +#endif + +#define ANT_QUEUE_NUM 36 + +typedef enum { + NONCE_DATA1_OFFSET = 0, + NONCE_DATA2_OFFSET, + NONCE_DATA3_OFFSET, + NONCE_DATA4_OFFSET, + NONCE_TASK_CMD_OFFSET, + NONCE_CHIP_NO_OFFSET, + NONCE_TASK_NO_OFFSET, + NONCE_COMMAND_OFFSET, + NONCE_MAX_OFFSET +} NONCE_OFFSET; + +typedef enum { + NONCE_DATA_CMD = 0, + NONCE_TASK_COMPLETE_CMD, + NONCE_GET_TASK_CMD, +} NONCE_COMMAND; + +typedef struct nonce_data { + int chip_no; + unsigned int task_no ; + unsigned char work_state; + int cmd_value; +} NONCE_DATA; + +typedef enum { + ROCKMINER_RBOX = 0, + ROCKMINER_T1, + ROCKMINER_T2, + ROCKMINER_MAX +} ROCKMINER_PRODUCT_T; + +typedef struct rockminer_chip_info { + unsigned char freq; + int error_cnt; + time_t last_received_task_complete_time; +} ROCKMINER_CHIP_INFO; + +typedef struct rockminer_device_info { + unsigned char detect_chip_no; + unsigned char chip_max; + unsigned char product_id; + float min_frq; + float def_frq; + float max_frq; + ROCKMINER_CHIP_INFO chip[MAX_CHIP_NUM]; + time_t dev_detect_time; +} ROCKMINER_DEVICE_INFO; + +struct ICARUS_INFO { + enum sub_ident ident; + int intinfo; + + // time to calculate the golden_ob + uint64_t golden_hashes; + struct timeval golden_tv; + + struct ICARUS_HISTORY history[INFO_HISTORY+1]; + uint32_t min_data_count; + + int timeout; + + // seconds per Hash + double Hs; + // ms til we abort + int read_time; + // ms limit for (short=/long=) read_time + int read_time_limit; + // How long without hashes is considered a failed device + int fail_time; + + enum timing_mode timing_mode; + bool do_icarus_timing; + + double fullnonce; + int count; + double W; + uint32_t values; + uint64_t hash_count_range; + + // Determine the cost of history processing + // (which will only affect W) + uint64_t history_count; + struct timeval history_time; + + // icarus-options + int baud; + int work_division; + int fpga_count; + uint32_t nonce_mask; + + uint8_t cmr2_speed; + bool speed_next_work; + bool flash_next_work; + + int nonce_size; + + bool failing; + + pthread_mutex_t lock; + + ROCKMINER_DEVICE_INFO rmdev; + struct work *base_work; // For when we roll work + struct work *g_work[MAX_CHIP_NUM][MAX_WORK_BUFFER_SIZE]; + uint32_t last_nonce[MAX_CHIP_NUM][MAX_WORK_BUFFER_SIZE]; + char rock_init[64]; + uint64_t nonces_checked; + uint64_t nonces_correction_times; + uint64_t nonces_correction_tests; + uint64_t nonces_fail; + uint64_t nonces_correction[NONCE_CORRECTION_TIMES]; + + struct work **antworks; + int nonces; + int workid; + bool ant; + bool u3; +}; + +#define ICARUS_MIDSTATE_SIZE 32 +#define ICARUS_UNUSED_SIZE 16 +#define ICARUS_WORK_SIZE 12 + +#define ICARUS_WORK_DATA_OFFSET 64 + +#define ICARUS_CMR2_SPEED_FACTOR 2.5 +#define ICARUS_CMR2_SPEED_MIN_INT 100 +#define ICARUS_CMR2_SPEED_DEF_INT 180 +#define ICARUS_CMR2_SPEED_MAX_INT 220 +#define CMR2_INT_TO_SPEED(_speed) ((uint8_t)((float)_speed / ICARUS_CMR2_SPEED_FACTOR)) +#define ICARUS_CMR2_SPEED_MIN CMR2_INT_TO_SPEED(ICARUS_CMR2_SPEED_MIN_INT) +#define ICARUS_CMR2_SPEED_DEF CMR2_INT_TO_SPEED(ICARUS_CMR2_SPEED_DEF_INT) +#define ICARUS_CMR2_SPEED_MAX CMR2_INT_TO_SPEED(ICARUS_CMR2_SPEED_MAX_INT) +#define ICARUS_CMR2_SPEED_INC 1 +#define ICARUS_CMR2_SPEED_DEC -1 +#define ICARUS_CMR2_SPEED_FAIL -10 + +#define ICARUS_CMR2_PREFIX ((uint8_t)0xB7) +#define ICARUS_CMR2_CMD_SPEED ((uint8_t)0) +#define ICARUS_CMR2_CMD_FLASH ((uint8_t)1) +#define ICARUS_CMR2_DATA_FLASH_OFF ((uint8_t)0) +#define ICARUS_CMR2_DATA_FLASH_ON ((uint8_t)1) +#define ICARUS_CMR2_CHECK ((uint8_t)0x6D) + +#define ANT_UNUSED_SIZE 15 + +struct ICARUS_WORK { + uint8_t midstate[ICARUS_MIDSTATE_SIZE]; + // These 4 bytes are for CMR2 bitstreams that handle MHz adjustment + uint8_t check; + uint8_t data; + uint8_t cmd; + uint8_t prefix; + uint8_t unused[ANT_UNUSED_SIZE]; + uint8_t id; // Used only by ANT, otherwise unused by other icarus + uint8_t work[ICARUS_WORK_SIZE]; +}; + +#define ANT_U1_DEFFREQ 200 +#define ANT_U3_DEFFREQ 225 +#define ANT_U3_MAXFREQ 250 +struct { + float freq; + uint16_t hex; +} u3freqtable[] = { + { 100, 0x0783 }, + { 125, 0x0983 }, + { 150, 0x0b83 }, + { 175, 0x0d83 }, + { 193.75, 0x0f03 }, + { 196.88, 0x1f07 }, + { 200, 0x0782 }, + { 206.25, 0x1006 }, + { 212.5, 0x1086 }, + { 218.75, 0x1106 }, + { 225, 0x0882 }, + { 237.5, 0x1286 }, + { 243.75, 0x1306 }, + { 250, 0x0982 }, +}; + +#define END_CONDITION 0x0000ffff + +// Looking for options in --icarus-timing and --icarus-options: +// +// Code increments this each time we start to look at a device +// However, this means that if other devices are checked by +// the Icarus code (e.g. Avalon only as at 20130517) +// they will count in the option offset +// +// This, however, is deterministic so that's OK +// +// If we were to increment after successfully finding an Icarus +// that would be random since an Icarus may fail and thus we'd +// not be able to predict the option order +// +// Devices are checked in the order libusb finds them which is ? +// +static int option_offset = -1; + +/* +#define ICA_BUFSIZ (0x200) + +static void transfer_read(struct cgpu_info *icarus, uint8_t request_type, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, char *buf, int bufsiz, int *amount, enum usb_cmds cmd) +{ + int err; + + err = usb_transfer_read(icarus, request_type, bRequest, wValue, wIndex, buf, bufsiz, amount, cmd); + + applog(LOG_DEBUG, "%s: cgid %d %s got err %d", + icarus->drv->name, icarus->cgminer_id, + usb_cmdname(cmd), err); +} +*/ + +static void _transfer(struct cgpu_info *icarus, uint8_t request_type, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, uint32_t *data, int siz, enum usb_cmds cmd) +{ + int err; + + err = usb_transfer_data(icarus, request_type, bRequest, wValue, wIndex, data, siz, cmd); + + applog(LOG_DEBUG, "%s: cgid %d %s got err %d", + icarus->drv->name, icarus->cgminer_id, + usb_cmdname(cmd), err); +} + +#define transfer(icarus, request_type, bRequest, wValue, wIndex, cmd) \ + _transfer(icarus, request_type, bRequest, wValue, wIndex, NULL, 0, cmd) + +static void icarus_initialise(struct cgpu_info *icarus, int baud) +{ + struct ICARUS_INFO *info = (struct ICARUS_INFO *)(icarus->device_data); + uint16_t wValue, wIndex; + enum sub_ident ident; + int interface; + + if (icarus->usbinfo.nodev) + return; + + interface = _usb_interface(icarus, info->intinfo); + ident = usb_ident(icarus); + + switch (ident) { + case IDENT_BLT: + case IDENT_LLT: + case IDENT_CMR1: + case IDENT_CMR2: + // Reset + transfer(icarus, FTDI_TYPE_OUT, FTDI_REQUEST_RESET, FTDI_VALUE_RESET, + interface, C_RESET); + + if (icarus->usbinfo.nodev) + return; + + // Latency + _usb_ftdi_set_latency(icarus, info->intinfo); + + if (icarus->usbinfo.nodev) + return; + + // Set data control + transfer(icarus, FTDI_TYPE_OUT, FTDI_REQUEST_DATA, FTDI_VALUE_DATA_BLT, + interface, C_SETDATA); + + if (icarus->usbinfo.nodev) + return; + + // default to BLT/LLT 115200 + wValue = FTDI_VALUE_BAUD_BLT; + wIndex = FTDI_INDEX_BAUD_BLT; + + if (ident == IDENT_CMR1 || ident == IDENT_CMR2) { + switch (baud) { + case 115200: + wValue = FTDI_VALUE_BAUD_CMR_115; + wIndex = FTDI_INDEX_BAUD_CMR_115; + break; + case 57600: + wValue = FTDI_VALUE_BAUD_CMR_57; + wIndex = FTDI_INDEX_BAUD_CMR_57; + break; + default: + quit(1, "icarus_intialise() invalid baud (%d) for Cairnsmore1", baud); + break; + } + } + + // Set the baud + transfer(icarus, FTDI_TYPE_OUT, FTDI_REQUEST_BAUD, wValue, + (wIndex & 0xff00) | interface, C_SETBAUD); + + if (icarus->usbinfo.nodev) + return; + + // Set Modem Control + transfer(icarus, FTDI_TYPE_OUT, FTDI_REQUEST_MODEM, FTDI_VALUE_MODEM, + interface, C_SETMODEM); + + if (icarus->usbinfo.nodev) + return; + + // Set Flow Control + transfer(icarus, FTDI_TYPE_OUT, FTDI_REQUEST_FLOW, FTDI_VALUE_FLOW, + interface, C_SETFLOW); + + if (icarus->usbinfo.nodev) + return; + + // Clear any sent data + transfer(icarus, FTDI_TYPE_OUT, FTDI_REQUEST_RESET, FTDI_VALUE_PURGE_TX, + interface, C_PURGETX); + + if (icarus->usbinfo.nodev) + return; + + // Clear any received data + transfer(icarus, FTDI_TYPE_OUT, FTDI_REQUEST_RESET, FTDI_VALUE_PURGE_RX, + interface, C_PURGERX); + break; + case IDENT_ICA: + // Set Data Control + transfer(icarus, PL2303_CTRL_OUT, PL2303_REQUEST_CTRL, PL2303_VALUE_CTRL, + interface, C_SETDATA); + + if (icarus->usbinfo.nodev) + return; + + // Set Line Control + uint32_t ica_data[2] = { PL2303_VALUE_LINE0, PL2303_VALUE_LINE1 }; + _transfer(icarus, PL2303_CTRL_OUT, PL2303_REQUEST_LINE, PL2303_VALUE_LINE, + interface, &ica_data[0], PL2303_VALUE_LINE_SIZE, C_SETLINE); + + if (icarus->usbinfo.nodev) + return; + + // Vendor + transfer(icarus, PL2303_VENDOR_OUT, PL2303_REQUEST_VENDOR, PL2303_VALUE_VENDOR, + interface, C_VENDOR); + break; + case IDENT_AMU: + case IDENT_ANU: + case IDENT_AU3: + case IDENT_LIN: + // Enable the UART + transfer(icarus, CP210X_TYPE_OUT, CP210X_REQUEST_IFC_ENABLE, + CP210X_VALUE_UART_ENABLE, + interface, C_ENABLE_UART); + + if (icarus->usbinfo.nodev) + return; + + // Set data control + transfer(icarus, CP210X_TYPE_OUT, CP210X_REQUEST_DATA, CP210X_VALUE_DATA, + interface, C_SETDATA); + + if (icarus->usbinfo.nodev) + return; + + // Set the baud + uint32_t data = CP210X_DATA_BAUD; + _transfer(icarus, CP210X_TYPE_OUT, CP210X_REQUEST_BAUD, 0, + interface, &data, sizeof(data), C_SETBAUD); + break; + case IDENT_AVA: + break; + default: + quit(1, "icarus_intialise() called with invalid %s cgid %i ident=%d", + icarus->drv->name, icarus->cgminer_id, ident); + } +} + +static void rev(unsigned char *s, size_t l) +{ + size_t i, j; + unsigned char t; + + for (i = 0, j = l - 1; i < j; i++, j--) { + t = s[i]; + s[i] = s[j]; + s[j] = t; + } +} + +#define ICA_NONCE_ERROR -1 +#define ICA_NONCE_OK 0 +#define ICA_NONCE_RESTART 1 +#define ICA_NONCE_TIMEOUT 2 + +static int icarus_get_nonce(struct cgpu_info *icarus, unsigned char *buf, struct timeval *tv_start, + struct timeval *tv_finish, struct thr_info *thr, int read_time) +{ + struct ICARUS_INFO *info = (struct ICARUS_INFO *)(icarus->device_data); + int err, amt, rc; + + if (icarus->usbinfo.nodev) + return ICA_NONCE_ERROR; + + cgtime(tv_start); + err = usb_read_ii_timeout_cancellable(icarus, info->intinfo, (char *)buf, + info->nonce_size, &amt, read_time, + C_GETRESULTS); + cgtime(tv_finish); + + if (err < 0 && err != LIBUSB_ERROR_TIMEOUT) { + applog(LOG_ERR, "%s %i: Comms error (rerr=%d amt=%d)", icarus->drv->name, + icarus->device_id, err, amt); + dev_error(icarus, REASON_DEV_COMMS_ERROR); + return ICA_NONCE_ERROR; + } + + if (amt >= info->nonce_size) + return ICA_NONCE_OK; + + rc = SECTOMS(tdiff(tv_finish, tv_start)); + if (thr && thr->work_restart) { + applog(LOG_DEBUG, "Icarus Read: Work restart at %d ms", rc); + return ICA_NONCE_RESTART; + } + + if (amt > 0) + applog(LOG_DEBUG, "Icarus Read: Timeout reading for %d ms", rc); + else + applog(LOG_DEBUG, "Icarus Read: No data for %d ms", rc); + return ICA_NONCE_TIMEOUT; +} + + +static const char *timing_mode_str(enum timing_mode timing_mode) +{ + switch(timing_mode) { + case MODE_DEFAULT: + return MODE_DEFAULT_STR; + case MODE_SHORT: + return MODE_SHORT_STR; + case MODE_LONG: + return MODE_LONG_STR; + case MODE_VALUE: + return MODE_VALUE_STR; + default: + return MODE_UNKNOWN_STR; + } +} + +static void set_timing_mode(int this_option_offset, struct cgpu_info *icarus) +{ + struct ICARUS_INFO *info = (struct ICARUS_INFO *)(icarus->device_data); + int read_count_timing = 0; + enum sub_ident ident; + double Hs, fail_time; + char buf[BUFSIZ+1]; + char *ptr, *comma, *eq; + size_t max; + int i; + + if (opt_icarus_timing == NULL) + buf[0] = '\0'; + else { + ptr = opt_icarus_timing; + for (i = 0; i < this_option_offset; i++) { + comma = strchr(ptr, ','); + if (comma == NULL) + break; + ptr = comma + 1; + } + + comma = strchr(ptr, ','); + if (comma == NULL) + max = strlen(ptr); + else + max = comma - ptr; + + if (max > BUFSIZ) + max = BUFSIZ; + strncpy(buf, ptr, max); + buf[max] = '\0'; + } + + ident = usb_ident(icarus); + switch (ident) { + case IDENT_ICA: + case IDENT_AVA: + info->Hs = ICARUS_REV3_HASH_TIME; + read_count_timing = ICARUS_READ_COUNT_TIMING; + break; + case IDENT_BLT: + case IDENT_LLT: + info->Hs = LANCELOT_HASH_TIME; + read_count_timing = ICARUS_READ_COUNT_TIMING; + break; + case IDENT_AMU: + info->Hs = ASICMINERUSB_HASH_TIME; + read_count_timing = ICARUS_READ_COUNT_TIMING; + break; + case IDENT_CMR1: + info->Hs = CAIRNSMORE1_HASH_TIME; + read_count_timing = ICARUS_READ_COUNT_TIMING; + break; + case IDENT_CMR2: + info->Hs = CAIRNSMORE2_HASH_TIME; + read_count_timing = ICARUS_READ_COUNT_TIMING; + break; + case IDENT_ANU: + info->Hs = ANTMINERUSB_HASH_TIME; + read_count_timing = ANTUSB_READ_COUNT_TIMING; + break; + case IDENT_AU3: + info->Hs = ANTU3_HASH_TIME; + read_count_timing = ANTU3_READ_COUNT_TIMING; + break; + default: + quit(1, "Icarus get_options() called with invalid %s ident=%d", + icarus->drv->name, ident); + } + + info->read_time = 0; + info->read_time_limit = 0; // 0 = no limit + + if (strcasecmp(buf, MODE_SHORT_STR) == 0) { + // short + info->read_time = read_count_timing; + + info->timing_mode = MODE_SHORT; + info->do_icarus_timing = true; + } else if (strncasecmp(buf, MODE_SHORT_STREQ, strlen(MODE_SHORT_STREQ)) == 0) { + // short=limit + info->read_time = read_count_timing; + + info->timing_mode = MODE_SHORT; + info->do_icarus_timing = true; + + info->read_time_limit = atoi(&buf[strlen(MODE_SHORT_STREQ)]); + if (info->read_time_limit < 0) + info->read_time_limit = 0; + if (info->read_time_limit > ICARUS_READ_TIME_LIMIT_MAX) + info->read_time_limit = ICARUS_READ_TIME_LIMIT_MAX; + } else if (strcasecmp(buf, MODE_LONG_STR) == 0) { + // long + info->read_time = read_count_timing; + + info->timing_mode = MODE_LONG; + info->do_icarus_timing = true; + } else if (strncasecmp(buf, MODE_LONG_STREQ, strlen(MODE_LONG_STREQ)) == 0) { + // long=limit + info->read_time = read_count_timing; + + info->timing_mode = MODE_LONG; + info->do_icarus_timing = true; + + info->read_time_limit = atoi(&buf[strlen(MODE_LONG_STREQ)]); + if (info->read_time_limit < 0) + info->read_time_limit = 0; + if (info->read_time_limit > ICARUS_READ_TIME_LIMIT_MAX) + info->read_time_limit = ICARUS_READ_TIME_LIMIT_MAX; + } else if ((Hs = atof(buf)) != 0) { + // ns[=read_time] + info->Hs = Hs / NANOSEC; + info->fullnonce = info->Hs * (((double)0xffffffff) + 1); + + if ((eq = strchr(buf, '=')) != NULL) + info->read_time = atoi(eq+1) * ICARUS_WAIT_TIMEOUT; + + if (info->read_time < ICARUS_READ_COUNT_MIN) + info->read_time = SECTOMS(info->fullnonce) - ICARUS_READ_REDUCE; + + if (unlikely(info->read_time < ICARUS_READ_COUNT_MIN)) + info->read_time = ICARUS_READ_COUNT_MIN; + + info->timing_mode = MODE_VALUE; + info->do_icarus_timing = false; + } else { + // Anything else in buf just uses DEFAULT mode + + info->fullnonce = info->Hs * (((double)0xffffffff) + 1); + + if ((eq = strchr(buf, '=')) != NULL) + info->read_time = atoi(eq+1) * ICARUS_WAIT_TIMEOUT; + + if (info->read_time < ICARUS_READ_COUNT_MIN) + info->read_time = SECTOMS(info->fullnonce) - ICARUS_READ_REDUCE; + + if (unlikely(info->read_time < ICARUS_READ_COUNT_MIN)) + info->read_time = ICARUS_READ_COUNT_MIN; + + info->timing_mode = MODE_DEFAULT; + info->do_icarus_timing = false; + } + + info->min_data_count = MIN_DATA_COUNT; + + // All values are in multiples of ICARUS_WAIT_TIMEOUT + info->read_time_limit *= ICARUS_WAIT_TIMEOUT; + + applog(LOG_DEBUG, "%s: cgid %d Init: mode=%s read_time=%dms limit=%dms Hs=%e", + icarus->drv->name, icarus->cgminer_id, + timing_mode_str(info->timing_mode), + info->read_time, info->read_time_limit, info->Hs); + + /* Set the time to detect a dead device to 30 full nonce ranges. */ + fail_time = info->Hs * 0xffffffffull * 30.0; + /* Integer accuracy is definitely enough. */ + info->fail_time = fail_time; +} + +static uint32_t mask(int work_division) +{ + uint32_t nonce_mask = 0x7fffffff; + + // yes we can calculate these, but this way it's easy to see what they are + switch (work_division) { + case 1: + nonce_mask = 0xffffffff; + break; + case 2: + nonce_mask = 0x7fffffff; + break; + case 4: + nonce_mask = 0x3fffffff; + break; + case 8: + nonce_mask = 0x1fffffff; + break; + default: + quit(1, "Invalid2 icarus-options for work_division (%d) must be 1, 2, 4 or 8", work_division); + } + + return nonce_mask; +} + +static void get_options(int this_option_offset, struct cgpu_info *icarus, int *baud, int *work_division, int *fpga_count) +{ + char buf[BUFSIZ+1]; + char *ptr, *comma, *colon, *colon2; + enum sub_ident ident; + size_t max; + int i, tmp; + + if (opt_icarus_options == NULL) + buf[0] = '\0'; + else { + ptr = opt_icarus_options; + for (i = 0; i < this_option_offset; i++) { + comma = strchr(ptr, ','); + if (comma == NULL) + break; + ptr = comma + 1; + } + + comma = strchr(ptr, ','); + if (comma == NULL) + max = strlen(ptr); + else + max = comma - ptr; + + if (max > BUFSIZ) + max = BUFSIZ; + strncpy(buf, ptr, max); + buf[max] = '\0'; + } + + ident = usb_ident(icarus); + switch (ident) { + case IDENT_ICA: + case IDENT_BLT: + case IDENT_LLT: + case IDENT_AVA: + *baud = ICARUS_IO_SPEED; + *work_division = 2; + *fpga_count = 2; + break; + case IDENT_AMU: + case IDENT_ANU: + case IDENT_AU3: + *baud = ICARUS_IO_SPEED; + *work_division = 1; + *fpga_count = 1; + break; + case IDENT_CMR1: + *baud = ICARUS_IO_SPEED; + *work_division = 2; + *fpga_count = 2; + break; + case IDENT_CMR2: + *baud = ICARUS_IO_SPEED; + *work_division = 1; + *fpga_count = 1; + break; + default: + quit(1, "Icarus get_options() called with invalid %s ident=%d", + icarus->drv->name, ident); + } + + if (*buf) { + colon = strchr(buf, ':'); + if (colon) + *(colon++) = '\0'; + + if (*buf) { + tmp = atoi(buf); + switch (tmp) { + case 115200: + *baud = 115200; + break; + case 57600: + *baud = 57600; + break; + default: + quit(1, "Invalid icarus-options for baud (%s) must be 115200 or 57600", buf); + } + } + + if (colon && *colon) { + colon2 = strchr(colon, ':'); + if (colon2) + *(colon2++) = '\0'; + + if (*colon) { + tmp = atoi(colon); + if (tmp == 1 || tmp == 2 || tmp == 4 || tmp == 8) { + *work_division = tmp; + *fpga_count = tmp; // default to the same + } else { + quit(1, "Invalid icarus-options for work_division (%s) must be 1, 2, 4 or 8", colon); + } + } + + if (colon2 && *colon2) { + tmp = atoi(colon2); + if (tmp > 0 && tmp <= *work_division) + *fpga_count = tmp; + else { + quit(1, "Invalid icarus-options for fpga_count (%s) must be >0 and <=work_division (%d)", colon2, *work_division); + } + } + } + } +} + +unsigned char crc5(unsigned char *ptr, unsigned char len) +{ + unsigned char i, j, k; + unsigned char crc = 0x1f; + + unsigned char crcin[5] = {1, 1, 1, 1, 1}; + unsigned char crcout[5] = {1, 1, 1, 1, 1}; + unsigned char din = 0; + + j = 0x80; + k = 0; + for (i = 0; i < len; i++) { + if (*ptr & j) + din = 1; + else + din = 0; + crcout[0] = crcin[4] ^ din; + crcout[1] = crcin[0]; + crcout[2] = crcin[1] ^ crcin[4] ^ din; + crcout[3] = crcin[2]; + crcout[4] = crcin[3]; + + j = j >> 1; + k++; + if (k == 8) { + j = 0x80; + k = 0; + ptr++; + } + memcpy(crcin, crcout, 5); + } + crc = 0; + if (crcin[4]) + crc |= 0x10; + if (crcin[3]) + crc |= 0x08; + if (crcin[2]) + crc |= 0x04; + if (crcin[1]) + crc |= 0x02; + if (crcin[0]) + crc |= 0x01; + return crc; +} + +static uint16_t anu_find_freqhex(void) +{ + float fout, best_fout = opt_anu_freq; + int od, nf, nr, no, n, m, bs; + uint16_t anu_freq_hex = 0; + float best_diff = 1000; + + if (!best_fout) + best_fout = ANT_U1_DEFFREQ; + + for (od = 0; od < 4; od++) { + no = 1 << od; + for (n = 0; n < 16; n++) { + nr = n + 1; + for (m = 0; m < 64; m++) { + nf = m + 1; + fout = 25 * (float)nf /((float)(nr) * (float)(no)); + if (fabsf(fout - opt_anu_freq) > best_diff) + continue; + if (500 <= (fout * no) && (fout * no) <= 1000) + bs = 1; + else + bs = 0; + best_diff = fabsf(fout - opt_anu_freq); + best_fout = fout; + anu_freq_hex = (bs << 14) | (m << 7) | (n << 2) | od; + if (fout == opt_anu_freq) { + applog(LOG_DEBUG, "ANU found exact frequency %.1f with hex %04x", + opt_anu_freq, anu_freq_hex); + goto out; + } + } + } + } + applog(LOG_NOTICE, "ANU found nearest frequency %.1f with hex %04x", best_fout, + anu_freq_hex); +out: + return anu_freq_hex; +} + +static uint16_t anu3_find_freqhex(void) +{ + int i = 0, freq = opt_au3_freq, u3freq; + uint16_t anu_freq_hex = 0x0882; + + if (!freq) + freq = ANT_U3_DEFFREQ; + + do { + u3freq = u3freqtable[i].freq; + if (u3freq <= freq) + anu_freq_hex = u3freqtable[i].hex; + i++; + } while (u3freq < ANT_U3_MAXFREQ); + + return anu_freq_hex; +} + +static bool set_anu_freq(struct cgpu_info *icarus, struct ICARUS_INFO *info, uint16_t anu_freq_hex) +{ + unsigned char cmd_buf[4], rdreg_buf[4]; + int amount, err; + char buf[512]; + + if (!anu_freq_hex) + anu_freq_hex = anu_find_freqhex(); + memset(cmd_buf, 0, 4); + memset(rdreg_buf, 0, 4); + cmd_buf[0] = 2 | 0x80; + cmd_buf[1] = (anu_freq_hex & 0xff00u) >> 8; + cmd_buf[2] = (anu_freq_hex & 0x00ffu); + cmd_buf[3] = crc5(cmd_buf, 27); + + rdreg_buf[0] = 4 | 0x80; + rdreg_buf[1] = 0; //16-23 + rdreg_buf[2] = 0x04; //8-15 + rdreg_buf[3] = crc5(rdreg_buf, 27); + + applog(LOG_DEBUG, "%s %i: Send frequency %02x%02x%02x%02x", icarus->drv->name, icarus->device_id, + cmd_buf[0], cmd_buf[1], cmd_buf[2], cmd_buf[3]); + err = usb_write_ii(icarus, info->intinfo, (char *)cmd_buf, 4, &amount, C_ANU_SEND_CMD); + if (err != LIBUSB_SUCCESS || amount != 4) { + applog(LOG_ERR, "%s %i: Write freq Comms error (werr=%d amount=%d)", + icarus->drv->name, icarus->device_id, err, amount); + return false; + } + err = usb_read_ii_timeout(icarus, info->intinfo, buf, 512, &amount, 100, C_GETRESULTS); + if (err < 0 && err != LIBUSB_ERROR_TIMEOUT) { + applog(LOG_ERR, "%s %i: Read freq Comms error (rerr=%d amount=%d)", + icarus->drv->name, icarus->device_id, err, amount); + return false; + } + + applog(LOG_DEBUG, "%s %i: Send freq getstatus %02x%02x%02x%02x", icarus->drv->name, icarus->device_id, + rdreg_buf[0], rdreg_buf[1], rdreg_buf[2], rdreg_buf[3]); + err = usb_write_ii(icarus, info->intinfo, (char *)cmd_buf, 4, &amount, C_ANU_SEND_RDREG); + if (err != LIBUSB_SUCCESS || amount != 4) { + applog(LOG_ERR, "%s %i: Write freq Comms error (werr=%d amount=%d)", + icarus->drv->name, icarus->device_id, err, amount); + return false; + } + err = usb_read_ii_timeout(icarus, info->intinfo, buf, 512, &amount, 100, C_GETRESULTS); + if (err < 0 && err != LIBUSB_ERROR_TIMEOUT) { + applog(LOG_ERR, "%s %i: Read freq Comms error (rerr=%d amount=%d)", + icarus->drv->name, icarus->device_id, err, amount); + return false; + } + + return true; +} + +static void set_anu_volt(struct cgpu_info *icarus) +{ + unsigned char voltage_data[2], cmd_buf[4]; + char volt_buf[8]; + int err, amount; + + /* Allow a zero setting to imply not to try and set voltage */ + if (!opt_au3_volt) + return; + if (opt_au3_volt < 725 || opt_au3_volt > 850) { + applog(LOG_WARNING, "Invalid ANU voltage %d specified, must be 725-850", opt_au3_volt); + return; + } + sprintf(volt_buf, "%04d", opt_au3_volt); + hex2bin(voltage_data, volt_buf, 2); + cmd_buf[0] = 0xaa; + cmd_buf[1] = voltage_data[0]; + cmd_buf[1] &=0x0f; + cmd_buf[1] |=0xb0; + cmd_buf[2] = voltage_data[1]; + cmd_buf[3] = 0x00; //0-7 + cmd_buf[3] = crc5(cmd_buf, 4*8 - 5); + cmd_buf[3] |= 0xc0; + applog(LOG_INFO, "Send ANU voltage %02x%02x%02x%02x", cmd_buf[0], cmd_buf[1], cmd_buf[2], cmd_buf[3]); + cgsleep_ms(500); + err = usb_write(icarus, (char * )cmd_buf, 4, &amount, C_ANU_SEND_VOLT); + if (err != LIBUSB_SUCCESS || amount != 4) + applog(LOG_ERR, "Write voltage Comms error (werr=%d amount=%d)", err, amount); +} + +static void rock_init_last_received_task_complete_time(struct ICARUS_INFO *info) +{ + int i; + + if (opt_rock_freq < info->rmdev.min_frq || + opt_rock_freq > info->rmdev.max_frq) + opt_rock_freq = info->rmdev.def_frq; + + for (i = 0; i < MAX_CHIP_NUM; ++i) { + info->rmdev.chip[i].last_received_task_complete_time = time(NULL); + info->rmdev.chip[i].freq = opt_rock_freq/10 - 1; + info->rmdev.chip[i].error_cnt = 0; + } + + info->rmdev.dev_detect_time = time(NULL); +} + + +static void icarus_clear(struct cgpu_info *icarus, struct ICARUS_INFO *info) +{ + char buf[512]; + int amt; + + do { + usb_read_ii_timeout(icarus, info->intinfo, buf, 512, &amt, 100, C_GETRESULTS); + } while (amt > 0); +} + +static struct cgpu_info *icarus_detect_one(struct libusb_device *dev, struct usb_find_devices *found) +{ + int this_option_offset = ++option_offset; + struct ICARUS_INFO *info; + struct timeval tv_start, tv_finish; + + // Block 171874 nonce = (0xa2870100) = 0x000187a2 + // N.B. golden_ob MUST take less time to calculate + // than the timeout set in icarus_open() + // This one takes ~0.53ms on Rev3 Icarus + const char golden_ob[] = + "4679ba4ec99876bf4bfe086082b40025" + "4df6c356451471139a3afa71e48f544a" + "00000000000000000000000000000000" + "0000000087320b1a1426674f2fa722ce"; + + const char golden_nonce[] = "000187a2"; + const uint32_t golden_nonce_val = 0x000187a2; + unsigned char nonce_bin[ICARUS_READ_SIZE]; + struct ICARUS_WORK workdata; + char *nonce_hex; + int baud, uninitialised_var(work_division), uninitialised_var(fpga_count); + bool anu_freqset = false; + struct cgpu_info *icarus; + int ret, err, amount, tries, i; + bool ok; + bool cmr2_ok[CAIRNSMORE2_INTS]; + int cmr2_count; + + if ((sizeof(workdata) << 1) != (sizeof(golden_ob) - 1)) + quithere(1, "Data and golden_ob sizes don't match"); + + icarus = usb_alloc_cgpu(&icarus_drv, 1); + + if (!usb_init(icarus, dev, found)) + goto shin; + + get_options(this_option_offset, icarus, &baud, &work_division, &fpga_count); + + hex2bin((void *)(&workdata), golden_ob, sizeof(workdata)); + + info = (struct ICARUS_INFO *)calloc(1, sizeof(struct ICARUS_INFO)); + if (unlikely(!info)) + quit(1, "Failed to malloc ICARUS_INFO"); + icarus->device_data = (void *)info; + + info->ident = usb_ident(icarus); + switch (info->ident) { + case IDENT_ICA: + case IDENT_AVA: + case IDENT_BLT: + case IDENT_LLT: + case IDENT_AMU: + case IDENT_CMR1: + info->timeout = ICARUS_WAIT_TIMEOUT; + break; + case IDENT_ANU: + case IDENT_AU3: + info->timeout = ANT_WAIT_TIMEOUT; + break; + case IDENT_CMR2: + if (found->intinfo_count != CAIRNSMORE2_INTS) { + quithere(1, "CMR2 Interface count (%d) isn't expected: %d", + found->intinfo_count, + CAIRNSMORE2_INTS); + } + info->timeout = ICARUS_CMR2_TIMEOUT; + cmr2_count = 0; + for (i = 0; i < CAIRNSMORE2_INTS; i++) + cmr2_ok[i] = false; + break; + default: + quit(1, "%s icarus_detect_one() invalid %s ident=%d", + icarus->drv->dname, icarus->drv->dname, info->ident); + } + + info->nonce_size = ICARUS_READ_SIZE; +// For CMR2 test each USB Interface + +retry: + + tries = 2; + ok = false; + while (!ok && tries-- > 0) { + icarus_clear(icarus, info); + icarus_initialise(icarus, baud); + + if (info->u3) { + uint16_t anu_freq_hex = anu3_find_freqhex(); + + set_anu_volt(icarus); + if (!set_anu_freq(icarus, info, anu_freq_hex)) { + applog(LOG_WARNING, "%s %i: Failed to set frequency, too much overclock?", + icarus->drv->name, icarus->device_id); + continue; + } + icarus->usbdev->ident = info->ident = IDENT_AU3; + info->Hs = ANTU3_HASH_TIME; + icarus->drv->name = "AU3"; + applog(LOG_DEBUG, "%s %i: Detected Antminer U3", icarus->drv->name, + icarus->device_id); + } else if (info->ident == IDENT_ANU && !info->u3) { + if (!set_anu_freq(icarus, info, 0)) { + applog(LOG_WARNING, "%s %i: Failed to set frequency, too much overclock?", + icarus->drv->name, icarus->device_id); + continue; + } + } + + err = usb_write_ii(icarus, info->intinfo, + (char *)(&workdata), sizeof(workdata), &amount, C_SENDWORK); + + if (err != LIBUSB_SUCCESS || amount != sizeof(workdata)) + continue; + + memset(nonce_bin, 0, sizeof(nonce_bin)); + ret = icarus_get_nonce(icarus, nonce_bin, &tv_start, &tv_finish, NULL, 300); + if (ret != ICA_NONCE_OK) + continue; + + if (info->nonce_size == ICARUS_READ_SIZE && usb_buffer_size(icarus) == 4) { + applog(LOG_DEBUG, "%s %i: Detected Rockminer, deferring detection", + icarus->drv->name, icarus->device_id); + usb_buffer_clear(icarus); + break; + + } + if (info->nonce_size == ICARUS_READ_SIZE && usb_buffer_size(icarus) == 1) { + info->ant = true; + usb_buffer_clear(icarus); + icarus->usbdev->ident = info->ident = IDENT_ANU; + info->nonce_size = ANT_READ_SIZE; + info->Hs = ANTMINERUSB_HASH_TIME; + icarus->drv->name = "ANU"; + applog(LOG_DEBUG, "%s %i: Detected Antminer U1/2/3, changing nonce size to %d", + icarus->drv->name, icarus->device_id, ANT_READ_SIZE); + } + + nonce_hex = bin2hex(nonce_bin, sizeof(nonce_bin)); + if (strncmp(nonce_hex, golden_nonce, 8) == 0) { + if (info->ant && !anu_freqset) + anu_freqset = true; + else + ok = true; + } else { + if (tries < 0 && info->ident != IDENT_CMR2) { + applog(LOG_ERR, + "Icarus Detect: " + "Test failed at %s: get %s, should: %s", + icarus->device_path, nonce_hex, golden_nonce); + } + } + free(nonce_hex); + } + + if (!ok) { + if (info->ident != IDENT_CMR2) { + if (info->u3) + goto unshin; + info->u3 = true; + goto retry; + } + + if (info->intinfo < CAIRNSMORE2_INTS-1) { + info->intinfo++; + goto retry; + } + } else { + if (info->ident == IDENT_CMR2) { + applog(LOG_DEBUG, + "Icarus Detect: " + "Test succeeded at %s i%d: got %s", + icarus->device_path, info->intinfo, golden_nonce); + + cmr2_ok[info->intinfo] = true; + cmr2_count++; + if (info->intinfo < CAIRNSMORE2_INTS-1) { + info->intinfo++; + goto retry; + } + } + } + + if (info->ident == IDENT_CMR2) { + if (cmr2_count == 0) { + applog(LOG_ERR, + "Icarus Detect: Test failed at %s: for all %d CMR2 Interfaces", + icarus->device_path, CAIRNSMORE2_INTS); + goto unshin; + } + + // set the interface to the first one that succeeded + for (i = 0; i < CAIRNSMORE2_INTS; i++) + if (cmr2_ok[i]) { + info->intinfo = i; + break; + } + } else { + applog(LOG_DEBUG, + "Icarus Detect: " + "Test succeeded at %s: got %s", + icarus->device_path, golden_nonce); + } + + /* We have a real Icarus! */ + if (!add_cgpu(icarus)) + goto unshin; + + update_usb_stats(icarus); + + applog(LOG_INFO, "%s %d: Found at %s", + icarus->drv->name, icarus->device_id, icarus->device_path); + + if (info->ident == IDENT_CMR2) { + applog(LOG_INFO, "%s %d: with %d Interface%s", + icarus->drv->name, icarus->device_id, + cmr2_count, cmr2_count > 1 ? "s" : ""); + + // Assume 1 or 2 are running FPGA pairs + if (cmr2_count < 3) { + work_division = fpga_count = 2; + info->Hs /= 2; + } + } + + applog(LOG_DEBUG, "%s %d: Init baud=%d work_division=%d fpga_count=%d", + icarus->drv->name, icarus->device_id, baud, work_division, fpga_count); + + info->baud = baud; + info->work_division = work_division; + info->fpga_count = fpga_count; + info->nonce_mask = mask(work_division); + + info->golden_hashes = (golden_nonce_val & info->nonce_mask) * fpga_count; + timersub(&tv_finish, &tv_start, &(info->golden_tv)); + + set_timing_mode(this_option_offset, icarus); + + if (info->ident == IDENT_CMR2) { + int i; + for (i = info->intinfo + 1; i < icarus->usbdev->found->intinfo_count; i++) { + struct cgpu_info *cgtmp; + struct ICARUS_INFO *intmp; + + if (!cmr2_ok[i]) + continue; + + cgtmp = usb_copy_cgpu(icarus); + if (!cgtmp) { + applog(LOG_ERR, "%s %d: Init failed initinfo %d", + icarus->drv->name, icarus->device_id, i); + continue; + } + + cgtmp->usbinfo.usbstat = USB_NOSTAT; + + intmp = (struct ICARUS_INFO *)malloc(sizeof(struct ICARUS_INFO)); + if (unlikely(!intmp)) + quit(1, "Failed2 to malloc ICARUS_INFO"); + + cgtmp->device_data = (void *)intmp; + + // Initialise everything to match + memcpy(intmp, info, sizeof(struct ICARUS_INFO)); + + intmp->intinfo = i; + + icarus_initialise(cgtmp, baud); + + if (!add_cgpu(cgtmp)) { + usb_uninit(cgtmp); + free(intmp); + continue; + } + + update_usb_stats(cgtmp); + } + } + + return icarus; + +unshin: + + usb_uninit(icarus); + free(info); + icarus->device_data = NULL; + +shin: + + icarus = usb_free_cgpu(icarus); + + return NULL; +} + +static int64_t rock_scanwork(struct thr_info *thr); + +static void rock_statline_before(char *buf, size_t bufsiz, struct cgpu_info *cgpu) +{ + if (cgpu->temp) + tailsprintf(buf, bufsiz, "%3.0fMHz %3.0fC", opt_rock_freq, cgpu->temp); + else + tailsprintf(buf, bufsiz, "%.0fMHz", opt_rock_freq); +} + +/* The only thing to do on flush_work is to remove the base work to prevent us + * rolling what is now stale work */ +static void rock_flush(struct cgpu_info *icarus) +{ + struct ICARUS_INFO *info = icarus->device_data; + struct work *work; + + mutex_lock(&info->lock); + work = info->base_work; + info->base_work = NULL; + mutex_unlock(&info->lock); + + if (work) + free_work(work); +} + +static struct cgpu_info *rock_detect_one(struct libusb_device *dev, struct usb_find_devices *found) +{ + struct ICARUS_INFO *info; + struct timeval tv_start, tv_finish; + char *ob_hex = NULL; + + // Block 171874 nonce = (0xa2870100) = 0x000187a2 + // N.B. golden_ob MUST take less time to calculate + // than the timeout set in icarus_open() + // This one takes ~0.53ms on Rev3 Icarus + const char golden_ob[] = + "4679ba4ec99876bf4bfe086082b40025" + "4df6c356451471139a3afa71e48f544a" + "00000000000000000000000000000000" + "aa1ff05587320b1a1426674f2fa722ce"; + + const char golden_nonce[] = "000187a2"; + const uint32_t golden_nonce_val = 0x000187a2; + unsigned char nonce_bin[ROCK_READ_SIZE]; + struct ICARUS_WORK workdata; + char *nonce_hex; + struct cgpu_info *icarus; + int ret, err, amount, tries; + bool ok; + int correction_times = 0; + NONCE_DATA nonce_data; + uint32_t nonce; + char *newname = NULL; + + if ((sizeof(workdata) << 1) != (sizeof(golden_ob) - 1)) + quithere(1, "Data and golden_ob sizes don't match"); + + icarus = usb_alloc_cgpu(&icarus_drv, 1); + + if (!usb_init(icarus, dev, found)) + goto shin; + + hex2bin((void *)(&workdata), golden_ob, sizeof(workdata)); + rev((void *)(&(workdata.midstate)), ICARUS_MIDSTATE_SIZE); + rev((void *)(&(workdata.work)), ICARUS_WORK_SIZE); + if (opt_debug) { + ob_hex = bin2hex((void *)(&workdata), sizeof(workdata)); + applog(LOG_WARNING, "%s %d: send_gold_nonce %s", + icarus->drv->name, icarus->device_id, ob_hex); + free(ob_hex); + } + + info = (struct ICARUS_INFO *)calloc(1, sizeof(struct ICARUS_INFO)); + if (unlikely(!info)) + quit(1, "Failed to malloc ICARUS_INFO"); + (void)memset(info, 0, sizeof(struct ICARUS_INFO)); + icarus->device_data = (void *)info; + icarus->usbdev->ident = info->ident = IDENT_LIN; + info->nonce_size = ROCK_READ_SIZE; + info->fail_time = 10; + info->nonce_mask = 0xffffffff; + update_usb_stats(icarus); + + tries = MAX_TRIES; + ok = false; + while (!ok && tries-- > 0) { + icarus_initialise(icarus, info->baud); + + applog(LOG_DEBUG, "tries: %d", tries); + workdata.unused[ICARUS_UNUSED_SIZE - 3] = opt_rock_freq/10 - 1; + workdata.unused[ICARUS_UNUSED_SIZE - 2] = (MAX_TRIES-1-tries); + info->rmdev.detect_chip_no++; + if (info->rmdev.detect_chip_no >= MAX_TRIES) + info->rmdev.detect_chip_no = 0; + //g_detect_chip_no = (g_detect_chip_no + 1) & MAX_CHIP_NUM; + + usb_buffer_clear(icarus); + err = usb_write_ii(icarus, info->intinfo, + (char *)(&workdata), sizeof(workdata), &amount, C_SENDWORK); + if (err != LIBUSB_SUCCESS || amount != sizeof(workdata)) + continue; + + memset(nonce_bin, 0, sizeof(nonce_bin)); + ret = icarus_get_nonce(icarus, nonce_bin, &tv_start, &tv_finish, NULL, 100); + + applog(LOG_DEBUG, "Rockminer nonce_bin: %02x %02x %02x %02x %02x %02x %02x %02x", + nonce_bin[0], nonce_bin[1], nonce_bin[2], nonce_bin[3], + nonce_bin[4], nonce_bin[5], nonce_bin[6], nonce_bin[7]); + if (ret != ICA_NONCE_OK) { + applog(LOG_DEBUG, "detect_one get_gold_nonce error, tries = %d", tries); + continue; + } + if (usb_buffer_size(icarus) == 1) { + applog(LOG_INFO, "Rock detect found an ANU, skipping"); + usb_buffer_clear(icarus); + break; + } + + newname = NULL; + switch (nonce_bin[NONCE_CHIP_NO_OFFSET] & RM_PRODUCT_MASK) { + case RM_PRODUCT_T1: + newname = "LIR"; // Rocketbox + info->rmdev.product_id = ROCKMINER_T1; + info->rmdev.chip_max = 12; + info->rmdev.min_frq = 200; + info->rmdev.def_frq = 330; + info->rmdev.max_frq = 400; + break; + case RM_PRODUCT_T2: // what's this? + newname = "LIX"; + info->rmdev.product_id = ROCKMINER_T2; + info->rmdev.chip_max = 16; + info->rmdev.min_frq = 200; + info->rmdev.def_frq = 300; + info->rmdev.max_frq = 400; + break; + case RM_PRODUCT_RBOX: + newname = "LIN"; // R-Box + info->rmdev.product_id = ROCKMINER_RBOX; + info->rmdev.chip_max = 4; + info->rmdev.min_frq = 200; + info->rmdev.def_frq = 270; + info->rmdev.max_frq = 400; + break; + default: + continue; + } + + snprintf(info->rock_init, sizeof(info->rock_init), "%02x %02x %02x %02x", + nonce_bin[4], nonce_bin[5], nonce_bin[6], nonce_bin[7]); + + nonce_data.chip_no = nonce_bin[NONCE_CHIP_NO_OFFSET] & RM_CHIP_MASK; + if (nonce_data.chip_no >= info->rmdev.chip_max) + nonce_data.chip_no = 0; + + nonce_data.cmd_value = nonce_bin[NONCE_TASK_CMD_OFFSET] & RM_CMD_MASK; + if (nonce_data.cmd_value == NONCE_TASK_COMPLETE_CMD) { + applog(LOG_DEBUG, "complete g_detect_chip_no: %d", info->rmdev.detect_chip_no); + workdata.unused[ICARUS_UNUSED_SIZE - 3] = opt_rock_freq/10 - 1; + workdata.unused[ICARUS_UNUSED_SIZE - 2] = info->rmdev.detect_chip_no; + info->rmdev.detect_chip_no++; + if (info->rmdev.detect_chip_no >= MAX_TRIES) + info->rmdev.detect_chip_no = 0; + + err = usb_write_ii(icarus, info->intinfo, + (char *)(&workdata), sizeof(workdata), &amount, C_SENDWORK); + if (err != LIBUSB_SUCCESS || amount != sizeof(workdata)) + continue; + applog(LOG_DEBUG, "send_gold_nonce usb_write_ii"); + continue; + } + + memcpy((char *)&nonce, nonce_bin, ICARUS_READ_SIZE); + nonce = htobe32(nonce); + applog(LOG_DEBUG, "Rockminer nonce: %08X", nonce); + correction_times = 0; + while (correction_times < NONCE_CORRECTION_TIMES) { + nonce_hex = bin2hex(nonce_bin, 4); + if (golden_nonce_val == nonce + rbox_corr_values[correction_times]) { + memset(&(info->g_work[0]), 0, sizeof(info->g_work)); + rock_init_last_received_task_complete_time(info); + + ok = true; + break; + } else { + applog(LOG_DEBUG, "detect_one gold_nonce compare error times = %d", + correction_times); + if (tries < 0 && info->ident != IDENT_CMR2) { + applog(LOG_WARNING, + "Icarus Detect: " + "Test failed at %s: get %s, should: %s", + icarus->device_path, nonce_hex, golden_nonce); + } + + if (nonce == 0) + break; + } + free(nonce_hex); + correction_times++; + } + } + + if (!ok) + goto unshin; + + if (newname) { + if (!icarus->drv->copy) + icarus->drv = copy_drv(icarus->drv); + icarus->drv->name = newname; + } + + applog(LOG_DEBUG, "Icarus Detect: Test succeeded at %s: got %s", + icarus->device_path, golden_nonce); + + /* We have a real Rockminer! */ + if (!add_cgpu(icarus)) + goto unshin; + + icarus->drv->scanwork = rock_scanwork; + icarus->drv->dname = "Rockminer"; + icarus->drv->get_statline_before = &rock_statline_before; + icarus->drv->flush_work = &rock_flush; + mutex_init(&info->lock); + + applog(LOG_INFO, "%s %d: Found at %s", + icarus->drv->name, icarus->device_id, + icarus->device_path); + + timersub(&tv_finish, &tv_start, &(info->golden_tv)); + + return icarus; + +unshin: + + usb_uninit(icarus); + free(info); + icarus->device_data = NULL; + +shin: + + icarus = usb_free_cgpu(icarus); + + return NULL; +} + +static void icarus_detect(bool __maybe_unused hotplug) +{ + usb_detect(&icarus_drv, rock_detect_one); + usb_detect(&icarus_drv, icarus_detect_one); +} + +static bool icarus_prepare(struct thr_info *thr) +{ + struct cgpu_info *icarus = thr->cgpu; + struct ICARUS_INFO *info = (struct ICARUS_INFO *)(icarus->device_data); + + if (info->ant) + info->antworks = calloc(sizeof(struct work *), ANT_QUEUE_NUM); + return true; +} + +static void cmr2_command(struct cgpu_info *icarus, uint8_t cmd, uint8_t data) +{ + struct ICARUS_INFO *info = (struct ICARUS_INFO *)(icarus->device_data); + struct ICARUS_WORK workdata; + int amount; + + memset((void *)(&workdata), 0, sizeof(workdata)); + + workdata.prefix = ICARUS_CMR2_PREFIX; + workdata.cmd = cmd; + workdata.data = data; + workdata.check = workdata.data ^ workdata.cmd ^ workdata.prefix ^ ICARUS_CMR2_CHECK; + + usb_write_ii(icarus, info->intinfo, (char *)(&workdata), sizeof(workdata), &amount, C_SENDWORK); +} + +static void cmr2_commands(struct cgpu_info *icarus) +{ + struct ICARUS_INFO *info = (struct ICARUS_INFO *)(icarus->device_data); + + if (info->speed_next_work) { + info->speed_next_work = false; + cmr2_command(icarus, ICARUS_CMR2_CMD_SPEED, info->cmr2_speed); + return; + } + + if (info->flash_next_work) { + info->flash_next_work = false; + cmr2_command(icarus, ICARUS_CMR2_CMD_FLASH, ICARUS_CMR2_DATA_FLASH_ON); + cgsleep_ms(250); + cmr2_command(icarus, ICARUS_CMR2_CMD_FLASH, ICARUS_CMR2_DATA_FLASH_OFF); + cgsleep_ms(250); + cmr2_command(icarus, ICARUS_CMR2_CMD_FLASH, ICARUS_CMR2_DATA_FLASH_ON); + cgsleep_ms(250); + cmr2_command(icarus, ICARUS_CMR2_CMD_FLASH, ICARUS_CMR2_DATA_FLASH_OFF); + return; + } +} + +void rock_send_task(unsigned char chip_no, unsigned int current_task_id, struct thr_info *thr) +{ + struct cgpu_info *icarus = thr->cgpu; + struct ICARUS_INFO *info = (struct ICARUS_INFO *)(icarus->device_data); + int err, amount; + struct ICARUS_WORK workdata; + char *ob_hex; + struct work *work = NULL; + + /* Only base_work needs locking since it can be asynchronously deleted + * by flush work */ + if (info->g_work[chip_no][current_task_id] == NULL) { + mutex_lock(&info->lock); + if (!info->base_work) + info->base_work = get_work(thr, thr->id); + if (info->base_work->drv_rolllimit > 0) { + info->base_work->drv_rolllimit--; + roll_work(info->base_work); + work = make_clone(info->base_work); + } else { + work = info->base_work; + info->base_work = NULL; + } + mutex_unlock(&info->lock); + + info->g_work[chip_no][current_task_id] = work; + } else { + work = info->g_work[chip_no][current_task_id]; + applog(LOG_DEBUG, "::resend work"); + } + + memset((void *)(&workdata), 0, sizeof(workdata)); + memcpy(&(workdata.midstate), work->midstate, ICARUS_MIDSTATE_SIZE); + memcpy(&(workdata.work), work->data + ICARUS_WORK_DATA_OFFSET, ICARUS_WORK_SIZE); + workdata.unused[ICARUS_UNUSED_SIZE - 4] = 0xaa; + if (info->rmdev.chip[chip_no].freq > (info->rmdev.max_frq/10 - 1) || + info->rmdev.chip[chip_no].freq < (info->rmdev.min_frq/10 - 1)) + rock_init_last_received_task_complete_time(info); + + workdata.unused[ICARUS_UNUSED_SIZE - 3] = info->rmdev.chip[chip_no].freq; //icarus->freq/10 - 1; ; + workdata.unused[ICARUS_UNUSED_SIZE - 2] = chip_no ; + workdata.id = 0x55; + + if (opt_debug) { + ob_hex = bin2hex((void *)(work->data), 128); + applog(LOG_WARNING, "%s %d: work->data %s", + icarus->drv->name, icarus->device_id, ob_hex); + free(ob_hex); + } + + // We only want results for the work we are about to send + usb_buffer_clear(icarus); + + err = usb_write_ii(icarus, info->intinfo, (char *)(&workdata), sizeof(workdata), &amount, C_SENDWORK); + + if (err < 0 || amount != sizeof(workdata)) { + applog(LOG_ERR, "%s %i: Comms error (werr=%d amt=%d)", + icarus->drv->name, icarus->device_id, err, amount); + dev_error(icarus, REASON_DEV_COMMS_ERROR); + icarus_initialise(icarus, info->baud); + + if (info->g_work[chip_no][current_task_id]) + { + free_work(info->g_work[chip_no][current_task_id]); + info->g_work[chip_no][current_task_id] = NULL; + } + + return; + } + + return; +} + +static void process_history(struct cgpu_info *icarus, struct ICARUS_INFO *info, uint32_t nonce, + uint64_t hash_count, struct timeval *elapsed, struct timeval *tv_start) +{ + struct ICARUS_HISTORY *history0, *history; + struct timeval tv_history_start, tv_history_finish; + int count; + double Hs, W, fullnonce; + int read_time, i; + bool limited; + uint32_t values; + int64_t hash_count_range; + double Ti, Xi; + + // Ignore possible end condition values ... + // TODO: set limitations on calculated values depending on the device + // to avoid crap values caused by CPU/Task Switching/Swapping/etc + if ((nonce & info->nonce_mask) <= END_CONDITION || + (nonce & info->nonce_mask) >= (info->nonce_mask & ~END_CONDITION)) + return; + + cgtime(&tv_history_start); + + history0 = &(info->history[0]); + + if (history0->values == 0) + timeradd(tv_start, &history_sec, &(history0->finish)); + + Ti = (double)(elapsed->tv_sec) + + ((double)(elapsed->tv_usec))/((double)1000000) + - ((double)ICARUS_READ_TIME(info->baud)); + Xi = (double)hash_count; + history0->sumXiTi += Xi * Ti; + history0->sumXi += Xi; + history0->sumTi += Ti; + history0->sumXi2 += Xi * Xi; + + history0->values++; + + if (history0->hash_count_max < hash_count) + history0->hash_count_max = hash_count; + if (history0->hash_count_min > hash_count || history0->hash_count_min == 0) + history0->hash_count_min = hash_count; + + if (history0->values >= info->min_data_count + && timercmp(tv_start, &(history0->finish), >)) { + for (i = INFO_HISTORY; i > 0; i--) + memcpy(&(info->history[i]), + &(info->history[i-1]), + sizeof(struct ICARUS_HISTORY)); + + // Initialise history0 to zero for summary calculation + memset(history0, 0, sizeof(struct ICARUS_HISTORY)); + + // We just completed a history data set + // So now recalc read_time based on the whole history thus we will + // initially get more accurate until it completes INFO_HISTORY + // total data sets + count = 0; + for (i = 1 ; i <= INFO_HISTORY; i++) { + history = &(info->history[i]); + if (history->values >= MIN_DATA_COUNT) { + count++; + + history0->sumXiTi += history->sumXiTi; + history0->sumXi += history->sumXi; + history0->sumTi += history->sumTi; + history0->sumXi2 += history->sumXi2; + history0->values += history->values; + + if (history0->hash_count_max < history->hash_count_max) + history0->hash_count_max = history->hash_count_max; + if (history0->hash_count_min > history->hash_count_min || history0->hash_count_min == 0) + history0->hash_count_min = history->hash_count_min; + } + } + + // All history data + Hs = (history0->values*history0->sumXiTi - history0->sumXi*history0->sumTi) + / (history0->values*history0->sumXi2 - history0->sumXi*history0->sumXi); + W = history0->sumTi/history0->values - Hs*history0->sumXi/history0->values; + hash_count_range = history0->hash_count_max - history0->hash_count_min; + values = history0->values; + + // Initialise history0 to zero for next data set + memset(history0, 0, sizeof(struct ICARUS_HISTORY)); + + fullnonce = W + Hs * (((double)0xffffffff) + 1); + read_time = SECTOMS(fullnonce) - ICARUS_READ_REDUCE; + if (info->read_time_limit > 0 && read_time > info->read_time_limit) { + read_time = info->read_time_limit; + limited = true; + } else + limited = false; + + info->Hs = Hs; + info->read_time = read_time; + + info->fullnonce = fullnonce; + info->count = count; + info->W = W; + info->values = values; + info->hash_count_range = hash_count_range; + + if (info->min_data_count < MAX_MIN_DATA_COUNT) + info->min_data_count *= 2; + else if (info->timing_mode == MODE_SHORT) + info->do_icarus_timing = false; + + applog(LOG_WARNING, "%s %d Re-estimate: Hs=%e W=%e read_time=%dms%s fullnonce=%.3fs", + icarus->drv->name, icarus->device_id, Hs, W, read_time, + limited ? " (limited)" : "", fullnonce); + } + info->history_count++; + cgtime(&tv_history_finish); + + timersub(&tv_history_finish, &tv_history_start, &tv_history_finish); + timeradd(&tv_history_finish, &(info->history_time), &(info->history_time)); +} + +static int64_t icarus_scanwork(struct thr_info *thr) +{ + struct cgpu_info *icarus = thr->cgpu; + struct ICARUS_INFO *info = (struct ICARUS_INFO *)(icarus->device_data); + int ret, err, amount; + unsigned char nonce_bin[ICARUS_BUF_SIZE]; + struct ICARUS_WORK workdata; + char *ob_hex; + uint32_t nonce; + int64_t hash_count = 0; + struct timeval tv_start, tv_finish, elapsed; + int curr_hw_errors; + bool was_hw_error; + struct work *work; + int64_t estimate_hashes; + uint8_t workid = 0; + + if (unlikely(share_work_tdiff(icarus) > info->fail_time)) { + if (info->failing) { + if (share_work_tdiff(icarus) > info->fail_time + 60) { + applog(LOG_ERR, "%s %d: Device failed to respond to restart", + icarus->drv->name, icarus->device_id); + usb_nodev(icarus); + return -1; + } + } else { + applog(LOG_WARNING, "%s %d: No valid hashes for over %d secs, attempting to reset", + icarus->drv->name, icarus->device_id, info->fail_time); + usb_reset(icarus); + info->failing = true; + } + } + + // Device is gone + if (icarus->usbinfo.nodev) + return -1; + + elapsed.tv_sec = elapsed.tv_usec = 0; + + work = get_work(thr, thr->id); + memset((void *)(&workdata), 0, sizeof(workdata)); + memcpy(&(workdata.midstate), work->midstate, ICARUS_MIDSTATE_SIZE); + memcpy(&(workdata.work), work->data + ICARUS_WORK_DATA_OFFSET, ICARUS_WORK_SIZE); + rev((void *)(&(workdata.midstate)), ICARUS_MIDSTATE_SIZE); + rev((void *)(&(workdata.work)), ICARUS_WORK_SIZE); + if (info->ant) { + workid = info->workid; + if (++info->workid >= 0x1F) + info->workid = 0; + if (info->antworks[workid]) + free_work(info->antworks[workid]); + info->antworks[workid] = work; + workdata.id = workid; + } + + if (info->speed_next_work || info->flash_next_work) + cmr2_commands(icarus); + + // We only want results for the work we are about to send + usb_buffer_clear(icarus); + + err = usb_write_ii(icarus, info->intinfo, (char *)(&workdata), sizeof(workdata), &amount, C_SENDWORK); + if (err < 0 || amount != sizeof(workdata)) { + applog(LOG_ERR, "%s %i: Comms error (werr=%d amt=%d)", + icarus->drv->name, icarus->device_id, err, amount); + dev_error(icarus, REASON_DEV_COMMS_ERROR); + icarus_initialise(icarus, info->baud); + goto out; + } + + if (opt_debug) { + ob_hex = bin2hex((void *)(&workdata), sizeof(workdata)); + applog(LOG_DEBUG, "%s %d: sent %s", + icarus->drv->name, icarus->device_id, ob_hex); + free(ob_hex); + } +more_nonces: + /* Icarus will return nonces or nothing. If we know we have enough data + * for a response in the buffer already, there will be no usb read + * performed. */ + memset(nonce_bin, 0, sizeof(nonce_bin)); + ret = icarus_get_nonce(icarus, nonce_bin, &tv_start, &tv_finish, thr, info->read_time); + if (ret == ICA_NONCE_ERROR) + goto out; + + // aborted before becoming idle, get new work + if (ret == ICA_NONCE_TIMEOUT || ret == ICA_NONCE_RESTART) { + if (info->ant) + goto out; + + timersub(&tv_finish, &tv_start, &elapsed); + + // ONLY up to just when it aborted + // We didn't read a reply so we don't subtract ICARUS_READ_TIME + estimate_hashes = ((double)(elapsed.tv_sec) + + ((double)(elapsed.tv_usec))/((double)1000000)) / info->Hs; + + // If some Serial-USB delay allowed the full nonce range to + // complete it can't have done more than a full nonce + if (unlikely(estimate_hashes > 0xffffffff)) + estimate_hashes = 0xffffffff; + + applog(LOG_DEBUG, "%s %d: no nonce = 0x%08lX hashes (%ld.%06lds)", + icarus->drv->name, icarus->device_id, + (long unsigned int)estimate_hashes, + (long)elapsed.tv_sec, (long)elapsed.tv_usec); + + hash_count = estimate_hashes; + goto out; + } + + if (info->ant) { + workid = nonce_bin[4] & 0x1F; + if (info->antworks[workid]) + work = info->antworks[workid]; + else + goto out; + } + + memcpy((char *)&nonce, nonce_bin, ICARUS_READ_SIZE); + nonce = htobe32(nonce); + curr_hw_errors = icarus->hw_errors; + if (submit_nonce(thr, work, nonce)) + info->failing = false; + was_hw_error = (curr_hw_errors < icarus->hw_errors); + + /* U3s return shares fast enough to use just that for hashrate + * calculation, otherwise the result is inaccurate instead. */ + if (info->ant) { + info->nonces++; + if (usb_buffer_size(icarus) >= ANT_READ_SIZE) + goto more_nonces; + } else { + hash_count = (nonce & info->nonce_mask); + hash_count++; + hash_count *= info->fpga_count; + } + +#if 0 + // This appears to only return zero nonce values + if (usb_buffer_size(icarus) > 3) { + memcpy((char *)&nonce, icarus->usbdev->buffer, sizeof(nonce_bin)); + nonce = htobe32(nonce); + applog(LOG_WARNING, "%s %d: attempting to submit 2nd nonce = 0x%08lX", + icarus->drv->name, icarus->device_id, + (long unsigned int)nonce); + curr_hw_errors = icarus->hw_errors; + submit_nonce(thr, work, nonce); + was_hw_error = (curr_hw_errors > icarus->hw_errors); + } +#endif + + if (opt_debug || info->do_icarus_timing) + timersub(&tv_finish, &tv_start, &elapsed); + + applog(LOG_DEBUG, "%s %d: nonce = 0x%08x = 0x%08lX hashes (%ld.%06lds)", + icarus->drv->name, icarus->device_id, + nonce, (long unsigned int)hash_count, + (long)elapsed.tv_sec, (long)elapsed.tv_usec); + + if (info->do_icarus_timing && !was_hw_error) + process_history(icarus, info, nonce, hash_count, &elapsed, &tv_start); +out: + if (!info->ant) + free_work(work); + else { + /* Ant USBs free the work themselves. Return only one full + * nonce worth on each pass to smooth out displayed hashrate */ + if (info->nonces) { + hash_count = 0xffffffff; + info->nonces--; + } + } + + return hash_count; +} + +static int64_t rock_scanwork(struct thr_info *thr) +{ + struct cgpu_info *icarus = thr->cgpu; + struct ICARUS_INFO *info = (struct ICARUS_INFO *)(icarus->device_data); + int ret; + unsigned char nonce_bin[ICARUS_BUF_SIZE]; + uint32_t nonce; + int64_t hash_count = 0; + struct timeval tv_start, tv_finish, elapsed; + struct work *work = NULL; + int64_t estimate_hashes; + int correction_times = 0; + NONCE_DATA nonce_data; + double temp; + + int chip_no = 0; + time_t recv_time = 0; + + if (unlikely(share_work_tdiff(icarus) > info->fail_time)) { + if (info->failing) { + if (share_work_tdiff(icarus) > info->fail_time + 60) { + applog(LOG_ERR, "%s %d: Device failed to respond to restart", + icarus->drv->name, icarus->device_id); + usb_nodev(icarus); + return -1; + } + } else { + applog(LOG_WARNING, "%s %d: No valid hashes for over %d secs, attempting to reset", + icarus->drv->name, icarus->device_id, info->fail_time); + usb_reset(icarus); + info->failing = true; + } + } + + // Device is gone + if (icarus->usbinfo.nodev) + return -1; + + elapsed.tv_sec = elapsed.tv_usec = 0; + + for (chip_no = 0; chip_no < info->rmdev.chip_max; chip_no++) { + recv_time = time(NULL); + if (recv_time > info->rmdev.chip[chip_no].last_received_task_complete_time + 1) { + info->rmdev.chip[chip_no].last_received_task_complete_time = recv_time; + rock_send_task(chip_no, 0,thr); + break; + } + } + + memset(nonce_bin, 0, sizeof(nonce_bin)); + ret = icarus_get_nonce(icarus, nonce_bin, &tv_start, &tv_finish, thr, 3000);//info->read_time); + + nonce_data.chip_no = nonce_bin[NONCE_CHIP_NO_OFFSET] & RM_CHIP_MASK; + if (nonce_data.chip_no >= info->rmdev.chip_max) + nonce_data.chip_no = 0; + nonce_data.task_no = nonce_bin[NONCE_TASK_NO_OFFSET] & 0x1; + nonce_data.cmd_value = nonce_bin[NONCE_TASK_CMD_OFFSET] & RM_CMD_MASK; + nonce_data.work_state = nonce_bin[NONCE_TASK_CMD_OFFSET] & RM_STATUS_MASK; + + temp = (double)nonce_bin[NONCE_COMMAND_OFFSET]; + if (temp != 128) + icarus->temp = temp; + + if (nonce_data.cmd_value == NONCE_TASK_COMPLETE_CMD) { + info->rmdev.chip[nonce_data.chip_no].last_received_task_complete_time = time(NULL); + if (info->g_work[nonce_data.chip_no][nonce_data.task_no]) { + free_work(info->g_work[nonce_data.chip_no][nonce_data.task_no]); + info->g_work[nonce_data.chip_no][nonce_data.task_no] = NULL; + } + goto out; + } + + if (nonce_data.cmd_value == NONCE_GET_TASK_CMD) { + rock_send_task(nonce_data.chip_no, nonce_data.task_no, thr); + goto out; + } + + if (ret == ICA_NONCE_TIMEOUT) + rock_send_task(nonce_data.chip_no, nonce_data.task_no, thr); + + work = info->g_work[nonce_data.chip_no][nonce_data.task_no]; + if (work == NULL) + goto out; + + if (ret == ICA_NONCE_ERROR) + goto out; + + // aborted before becoming idle, get new work + if (ret == ICA_NONCE_TIMEOUT || ret == ICA_NONCE_RESTART) { + timersub(&tv_finish, &tv_start, &elapsed); + + // ONLY up to just when it aborted + // We didn't read a reply so we don't subtract ICARUS_READ_TIME + estimate_hashes = ((double)(elapsed.tv_sec) + + ((double)(elapsed.tv_usec))/((double)1000000)) / info->Hs; + + // If some Serial-USB delay allowed the full nonce range to + // complete it can't have done more than a full nonce + if (unlikely(estimate_hashes > 0xffffffff)) + estimate_hashes = 0xffffffff; + + applog(LOG_DEBUG, "%s %d: no nonce = 0x%08lX hashes (%ld.%06lds)", + icarus->drv->name, icarus->device_id, + (long unsigned int)estimate_hashes, + (long)elapsed.tv_sec, (long)elapsed.tv_usec); + + goto out; + } + + memcpy((char *)&nonce, nonce_bin, ICARUS_READ_SIZE); + nonce = htobe32(nonce); + recv_time = time(NULL); + if ((recv_time-info->rmdev.dev_detect_time) >= 60) { + unsigned char i; + info->rmdev.dev_detect_time = recv_time; + for (i = 0; i < info->rmdev.chip_max; i ++) { + if (info->rmdev.chip[i].error_cnt >= 12) { + if (info->rmdev.chip[i].freq > info->rmdev.min_frq) + info->rmdev.chip[i].freq--; + } else if (info->rmdev.chip[i].error_cnt <= 1) { + if (info->rmdev.chip[i].freq < (info->rmdev.def_frq / 10 - 1)) + info->rmdev.chip[i].freq++; + } + info->rmdev.chip[i].error_cnt = 0; + } + } + + correction_times = 0; + info->nonces_checked++; + while (correction_times < NONCE_CORRECTION_TIMES) { + uint32_t new_nonce; + + if (correction_times > 0) { + info->nonces_correction_tests++; + if (correction_times == 1) + info->nonces_correction_times++; + } + new_nonce = nonce + rbox_corr_values[correction_times]; + /* Basic dupe testing */ + if (new_nonce == info->last_nonce[nonce_data.chip_no][nonce_data.task_no]) + break; + if (test_nonce(work, new_nonce)) { + nonce = new_nonce; + submit_tested_work(thr, work); + info->last_nonce[nonce_data.chip_no][nonce_data.task_no] = nonce; + info->nonces_correction[correction_times]++; + hash_count++; + info->failing = false; + applog(LOG_DEBUG, "Rockminer nonce :::OK:::"); + break; + } else { + applog(LOG_DEBUG, "Rockminer nonce error times = %d", correction_times); + if (new_nonce == 0) + break; + } + correction_times++; + } + if (correction_times >= NONCE_CORRECTION_TIMES) { + inc_hw_errors(thr); + info->nonces_fail++; + } + + hash_count = (hash_count * info->nonce_mask); + + if (opt_debug || info->do_icarus_timing) + timersub(&tv_finish, &tv_start, &elapsed); + + applog(LOG_DEBUG, "%s %d: nonce = 0x%08x = 0x%08lX hashes (%ld.%06lds)", + icarus->drv->name, icarus->device_id, + nonce, (long unsigned int)hash_count, + (long)elapsed.tv_sec, (long)elapsed.tv_usec); + +out: + + return hash_count; +} + +static struct api_data *icarus_api_stats(struct cgpu_info *cgpu) +{ + struct api_data *root = NULL; + struct ICARUS_INFO *info = (struct ICARUS_INFO *)(cgpu->device_data); + char data[4096]; + int i, off; + size_t len; + float avg; + + // Warning, access to these is not locked - but we don't really + // care since hashing performance is way more important than + // locking access to displaying API debug 'stats' + // If locking becomes an issue for any of them, use copy_data=true also + root = api_add_int(root, "read_time", &(info->read_time), false); + root = api_add_int(root, "read_time_limit", &(info->read_time_limit), false); + root = api_add_double(root, "fullnonce", &(info->fullnonce), false); + root = api_add_int(root, "count", &(info->count), false); + root = api_add_hs(root, "Hs", &(info->Hs), false); + root = api_add_double(root, "W", &(info->W), false); + root = api_add_uint(root, "total_values", &(info->values), false); + root = api_add_uint64(root, "range", &(info->hash_count_range), false); + root = api_add_uint64(root, "history_count", &(info->history_count), false); + root = api_add_timeval(root, "history_time", &(info->history_time), false); + root = api_add_uint(root, "min_data_count", &(info->min_data_count), false); + root = api_add_uint(root, "timing_values", &(info->history[0].values), false); + root = api_add_const(root, "timing_mode", timing_mode_str(info->timing_mode), false); + root = api_add_bool(root, "is_timing", &(info->do_icarus_timing), false); + root = api_add_int(root, "baud", &(info->baud), false); + root = api_add_int(root, "work_division", &(info->work_division), false); + root = api_add_int(root, "fpga_count", &(info->fpga_count), false); + + if (info->ident == IDENT_LIN) { + root = api_add_string(root, "rock_init", info->rock_init, false); + root = api_add_uint8(root, "rock_chips", &(info->rmdev.detect_chip_no), false); + root = api_add_uint8(root, "rock_chip_max", &(info->rmdev.chip_max), false); + root = api_add_uint8(root, "rock_prod_id", &(info->rmdev.product_id), false); + root = api_add_avg(root, "rock_min_freq", &(info->rmdev.min_frq), false); + root = api_add_avg(root, "rock_max_freq", &(info->rmdev.max_frq), false); + root = api_add_uint64(root, "rock_check", &(info->nonces_checked), false); + root = api_add_uint64(root, "rock_corr", &(info->nonces_correction_times), false); + root = api_add_uint64(root, "rock_corr_tests", &(info->nonces_correction_tests), false); + root = api_add_uint64(root, "rock_corr_fail", &(info->nonces_fail), false); + if (info->nonces_checked <= 0) + avg = 0; + else + avg = (float)(info->nonces_correction_tests) / (float)(info->nonces_checked); + root = api_add_avg(root, "rock_corr_avg", &avg, true); + data[0] = '\0'; + off = 0; + for (i = 0; i < NONCE_CORRECTION_TIMES; i++) { + len = snprintf(data+off, sizeof(data)-off, + "%s%"PRIu64, + i > 0 ? "/" : "", + info->nonces_correction[i]); + if (len >= (sizeof(data)-off)) + off = sizeof(data)-1; + else { + if (len > 0) + off += len; + } + } + root = api_add_string(root, "rock_corr_finds", data, true); + } + + return root; +} + +static void icarus_statline_before(char *buf, size_t bufsiz, struct cgpu_info *cgpu) +{ + struct ICARUS_INFO *info = (struct ICARUS_INFO *)(cgpu->device_data); + + if (info->ant) { + if (info->u3) + tailsprintf(buf, bufsiz, "%3.0fMHz %3dmV", opt_au3_freq, opt_au3_volt); + else + tailsprintf(buf, bufsiz, "%3.0fMHz", opt_anu_freq); + } else if (info->ident == IDENT_CMR2 && info->cmr2_speed > 0) + tailsprintf(buf, bufsiz, "%5.1fMhz", (float)(info->cmr2_speed) * ICARUS_CMR2_SPEED_FACTOR); +} + +static void icarus_shutdown(__maybe_unused struct thr_info *thr) +{ + // TODO: ? +} + +static void icarus_identify(struct cgpu_info *cgpu) +{ + struct ICARUS_INFO *info = (struct ICARUS_INFO *)(cgpu->device_data); + + if (info->ident == IDENT_CMR2) + info->flash_next_work = true; +} + +static char *icarus_set(struct cgpu_info *cgpu, char *option, char *setting, char *replybuf) +{ + struct ICARUS_INFO *info = (struct ICARUS_INFO *)(cgpu->device_data); + int val; + + if (info->ident != IDENT_CMR2) { + strcpy(replybuf, "no set options available"); + return replybuf; + } + + if (strcasecmp(option, "help") == 0) { + sprintf(replybuf, "clock: range %d-%d", + ICARUS_CMR2_SPEED_MIN_INT, ICARUS_CMR2_SPEED_MAX_INT); + return replybuf; + } + + if (strcasecmp(option, "clock") == 0) { + if (!setting || !*setting) { + sprintf(replybuf, "missing clock setting"); + return replybuf; + } + + val = atoi(setting); + if (val < ICARUS_CMR2_SPEED_MIN_INT || val > ICARUS_CMR2_SPEED_MAX_INT) { + sprintf(replybuf, "invalid clock: '%s' valid range %d-%d", + setting, + ICARUS_CMR2_SPEED_MIN_INT, + ICARUS_CMR2_SPEED_MAX_INT); + } + + info->cmr2_speed = CMR2_INT_TO_SPEED(val); + info->speed_next_work = true; + + return NULL; + } + + sprintf(replybuf, "Unknown option: %s", option); + return replybuf; +} + +struct device_drv icarus_drv = { + .drv_id = DRIVER_icarus, + .dname = "Icarus", + .name = "ICA", + .drv_detect = icarus_detect, + .hash_work = &hash_driver_work, + .get_api_stats = icarus_api_stats, + .get_statline_before = icarus_statline_before, + .set_device = icarus_set, + .identify_device = icarus_identify, + .thread_prepare = icarus_prepare, + .scanwork = icarus_scanwork, + .thread_shutdown = icarus_shutdown, +}; diff --git a/driver-klondike.c b/driver-klondike.c new file mode 100644 index 0000000..1429041 --- /dev/null +++ b/driver-klondike.c @@ -0,0 +1,1555 @@ +/* + * Copyright 2013 Andrew Smith + * Copyright 2013 Con Kolivas + * Copyright 2013 Chris Savery + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" + +#ifdef WIN32 +#include +#endif + +#include "compat.h" +#include "miner.h" +#include "usbutils.h" + +#define K1 "K1" +#define K16 "K16" +#define K64 "K64" + +static const char *msg_detect_send = "DSend"; +static const char *msg_detect_reply = "DReply"; +static const char *msg_send = "Send"; +static const char *msg_reply = "Reply"; + +#define KLN_CMD_ABORT 'A' +#define KLN_CMD_CONFIG 'C' +#define KLN_CMD_ENABLE 'E' +#define KLN_CMD_IDENT 'I' +#define KLN_CMD_NONCE '=' +#define KLN_CMD_STATUS 'S' +#define KLN_CMD_WORK 'W' + +#define KLN_CMD_ENABLE_OFF '0' +#define KLN_CMD_ENABLE_ON '1' + +#define MIDSTATE_BYTES 32 +#define MERKLE_OFFSET 64 +#define MERKLE_BYTES 12 + +#define REPLY_SIZE 15 // adequate for all types of replies +#define MAX_KLINES 1024 // unhandled reply limit +#define REPLY_WAIT_TIME 100 // poll interval for a cmd waiting it's reply +#define CMD_REPLY_RETRIES 8 // how many retries for cmds +#define MAX_WORK_COUNT 4 // for now, must be binary multiple and match firmware +#define TACH_FACTOR 87890 // fan rpm divisor + +#define KLN_KILLWORK_TEMP 53.5 +#define KLN_COOLED_DOWN 45.5 + +/* + * Work older than 5s will already be completed + * FYI it must not be possible to complete 256 work + * items this quickly on a single device - + * thus limited to 219.9GH/s per device + */ +#define OLD_WORK_MS ((int)(5 * 1000)) + +/* + * How many incorrect slave counts to ignore in a row + * 2 means it allows random grabage returned twice + * Until slaves are implemented, this should never occur + * so allowing 2 in a row should ignore random errros + */ +#define KLN_ISS_IGNORE 2 + +/* + * If the queue status hasn't been updated for this long then do it now + * 5GH/s = 859ms per full nonce range + */ +#define LATE_UPDATE_MS ((int)(2.5 * 1000)) + +// If 5 late updates in a row, try to reset the device +#define LATE_UPDATE_LIMIT 5 + +// If the reset fails sleep for 1s +#define LATE_UPDATE_SLEEP_MS 1000 + +// However give up after 8s +#define LATE_UPDATE_NODEV_MS ((int)(8.0 * 1000)) + +struct device_drv klondike_drv; + +typedef struct klondike_header { + uint8_t cmd; + uint8_t dev; + uint8_t buf[REPLY_SIZE-2]; +} HEADER; + +#define K_2(_bytes) ((int)(_bytes[0]) + \ + ((int)(_bytes[1]) << 8)) + +#define K_4(_bytes) ((uint64_t)(_bytes[0]) + \ + ((uint64_t)(_bytes[1]) << 8) + \ + ((uint64_t)(_bytes[2]) << 16) + \ + ((uint64_t)(_bytes[3]) << 24)) + +#define K_SERIAL(_serial) K_4(_serial) +#define K_HASHCOUNT(_hashcount) K_2(_hashcount) +#define K_MAXCOUNT(_maxcount) K_2(_maxcount) +#define K_NONCE(_nonce) K_4(_nonce) +#define K_HASHCLOCK(_hashclock) K_2(_hashclock) + +#define SET_HASHCLOCK(_hashclock, _value) do { \ + (_hashclock)[0] = (uint8_t)((_value) & 0xff); \ + (_hashclock)[1] = (uint8_t)(((_value) >> 8) & 0xff); \ + } while(0) + +#define KSENDHD(_add) (sizeof(uint8_t) + sizeof(uint8_t) + _add) + +typedef struct klondike_id { + uint8_t cmd; + uint8_t dev; + uint8_t version; + uint8_t product[7]; + uint8_t serial[4]; +} IDENTITY; + +typedef struct klondike_status { + uint8_t cmd; + uint8_t dev; + uint8_t state; + uint8_t chipcount; + uint8_t slavecount; + uint8_t workqc; + uint8_t workid; + uint8_t temp; + uint8_t fanspeed; + uint8_t errorcount; + uint8_t hashcount[2]; + uint8_t maxcount[2]; + uint8_t noise; +} WORKSTATUS; + +typedef struct _worktask { + uint8_t cmd; + uint8_t dev; + uint8_t workid; + uint8_t midstate[32]; + uint8_t merkle[12]; +} WORKTASK; + +typedef struct _workresult { + uint8_t cmd; + uint8_t dev; + uint8_t workid; + uint8_t nonce[4]; +} WORKRESULT; + +typedef struct klondike_cfg { + uint8_t cmd; + uint8_t dev; + uint8_t hashclock[2]; + uint8_t temptarget; + uint8_t tempcritical; + uint8_t fantarget; + uint8_t pad2; +} WORKCFG; + +typedef struct kline { + union { + HEADER hd; + IDENTITY id; + WORKSTATUS ws; + WORKTASK wt; + WORKRESULT wr; + WORKCFG cfg; + }; +} KLINE; + +#define zero_kline(_kline) memset((void *)(_kline), 0, sizeof(KLINE)); + +typedef struct device_info { + uint32_t noncecount; + uint32_t nextworkid; + uint16_t lasthashcount; + uint64_t totalhashcount; + uint32_t rangesize; + uint32_t *chipstats; +} DEVINFO; + +typedef struct klist { + struct klist *prev; + struct klist *next; + KLINE kline; + struct timeval tv_when; + int block_seq; + bool ready; + bool working; +} KLIST; + +typedef struct jobque { + int workqc; + struct timeval last_update; + bool overheat; + bool flushed; + int late_update_count; + int late_update_sequential; +} JOBQUE; + +struct klondike_info { + pthread_rwlock_t stat_lock; + struct thr_info replies_thr; + cglock_t klist_lock; + KLIST *used; + KLIST *free; + int kline_count; + int used_count; + int block_seq; + KLIST *status; + DEVINFO *devinfo; + KLIST *cfg; + JOBQUE *jobque; + int noncecount; + uint64_t hashcount; + uint64_t errorcount; + uint64_t noisecount; + int incorrect_slave_sequential; + + // us Delay from USB reply to being processed + double delay_count; + double delay_total; + double delay_min; + double delay_max; + + struct timeval tv_last_nonce_received; + + // Time from recieving one nonce to the next + double nonce_count; + double nonce_total; + double nonce_min; + double nonce_max; + + int wque_size; + int wque_cleared; + + bool initialised; +}; + +static KLIST *new_klist_set(struct cgpu_info *klncgpu) +{ + struct klondike_info *klninfo = (struct klondike_info *)(klncgpu->device_data); + KLIST *klist = NULL; + int i; + + klist = calloc(MAX_KLINES, sizeof(*klist)); + if (!klist) + quit(1, "Failed to calloc klist - when old count=%d", klninfo->kline_count); + + klninfo->kline_count += MAX_KLINES; + + klist[0].prev = NULL; + klist[0].next = &(klist[1]); + for (i = 1; i < MAX_KLINES-1; i++) { + klist[i].prev = &klist[i-1]; + klist[i].next = &klist[i+1]; + } + klist[MAX_KLINES-1].prev = &(klist[MAX_KLINES-2]); + klist[MAX_KLINES-1].next = NULL; + + return klist; +} + +static KLIST *allocate_kitem(struct cgpu_info *klncgpu) +{ + struct klondike_info *klninfo = (struct klondike_info *)(klncgpu->device_data); + KLIST *kitem = NULL; + int ran_out = 0; + char errbuf[1024]; + + cg_wlock(&klninfo->klist_lock); + + if (klninfo->free == NULL) { + ran_out = klninfo->kline_count; + klninfo->free = new_klist_set(klncgpu); + snprintf(errbuf, sizeof(errbuf), + "%s%i: KLINE count exceeded %d, now %d", + klncgpu->drv->name, klncgpu->device_id, + ran_out, klninfo->kline_count); + } + + kitem = klninfo->free; + + klninfo->free = klninfo->free->next; + if (klninfo->free) + klninfo->free->prev = NULL; + + kitem->next = klninfo->used; + kitem->prev = NULL; + if (kitem->next) + kitem->next->prev = kitem; + klninfo->used = kitem; + + kitem->ready = false; + kitem->working = false; + + memset((void *)&(kitem->kline), 0, sizeof(kitem->kline)); + + klninfo->used_count++; + + cg_wunlock(&klninfo->klist_lock); + + if (ran_out > 0) + applog(LOG_WARNING, "%s", errbuf); + + return kitem; +} + +static KLIST *release_kitem(struct cgpu_info *klncgpu, KLIST *kitem) +{ + struct klondike_info *klninfo = (struct klondike_info *)(klncgpu->device_data); + + cg_wlock(&klninfo->klist_lock); + + if (kitem == klninfo->used) + klninfo->used = kitem->next; + + if (kitem->next) + kitem->next->prev = kitem->prev; + if (kitem->prev) + kitem->prev->next = kitem->next; + + kitem->next = klninfo->free; + if (klninfo->free) + klninfo->free->prev = kitem; + + kitem->prev = NULL; + + klninfo->free = kitem; + + klninfo->used_count--; + + cg_wunlock(&klninfo->klist_lock); + + return NULL; +} + +static double cvtKlnToC(uint8_t temp) +{ + double Rt, stein, celsius; + + if (temp == 0) + return 0.0; + + Rt = 1000.0 * 255.0 / (double)temp - 1000.0; + + stein = log(Rt / 2200.0) / 3987.0; + + stein += 1.0 / (double)(25.0 + 273.15); + + celsius = (1.0 / stein) - 273.15; + + // For display of bad data + if (celsius < 0.0) + celsius = 0.0; + if (celsius > 200.0) + celsius = 200.0; + + return celsius; +} + +static int cvtCToKln(double deg) +{ + double Rt, stein, temp; + + if (deg < 0.0) + deg = 0.0; + + stein = 1.0 / (deg + 273.15); + + stein -= 1.0 / (double)(25.0 + 273.15); + + Rt = exp(stein * 3987.0) * 2200.0; + + if (Rt == -1000.0) + Rt++; + + temp = 1000.0 * 256.0 / (Rt + 1000.0); + + if (temp > 255) + temp = 255; + if (temp < 0) + temp = 0; + + return (int)temp; +} + +// Change this to LOG_WARNING if you wish to always see the replies +#define READ_DEBUG LOG_DEBUG + +static void display_kline(struct cgpu_info *klncgpu, KLINE *kline, const char *msg) +{ + char *hexdata; + + switch (kline->hd.cmd) { + case KLN_CMD_NONCE: + applog(READ_DEBUG, + "%s%i:%d %s work [%c] dev=%d workid=%d" + " nonce=0x%08x", + klncgpu->drv->name, klncgpu->device_id, + (int)(kline->wr.dev), msg, kline->wr.cmd, + (int)(kline->wr.dev), + (int)(kline->wr.workid), + (unsigned int)K_NONCE(kline->wr.nonce) - 0xC0); + break; + case KLN_CMD_STATUS: + case KLN_CMD_WORK: + case KLN_CMD_ENABLE: + case KLN_CMD_ABORT: + applog(READ_DEBUG, + "%s%i:%d %s status [%c] dev=%d chips=%d" + " slaves=%d workcq=%d workid=%d temp=%d fan=%d" + " errors=%d hashes=%d max=%d noise=%d", + klncgpu->drv->name, klncgpu->device_id, + (int)(kline->ws.dev), msg, kline->ws.cmd, + (int)(kline->ws.dev), + (int)(kline->ws.chipcount), + (int)(kline->ws.slavecount), + (int)(kline->ws.workqc), + (int)(kline->ws.workid), + (int)(kline->ws.temp), + (int)(kline->ws.fanspeed), + (int)(kline->ws.errorcount), + K_HASHCOUNT(kline->ws.hashcount), + K_MAXCOUNT(kline->ws.maxcount), + (int)(kline->ws.noise)); + break; + case KLN_CMD_CONFIG: + applog(READ_DEBUG, + "%s%i:%d %s config [%c] dev=%d clock=%d" + " temptarget=%d tempcrit=%d fan=%d", + klncgpu->drv->name, klncgpu->device_id, + (int)(kline->cfg.dev), msg, kline->cfg.cmd, + (int)(kline->cfg.dev), + K_HASHCLOCK(kline->cfg.hashclock), + (int)(kline->cfg.temptarget), + (int)(kline->cfg.tempcritical), + (int)(kline->cfg.fantarget)); + break; + case KLN_CMD_IDENT: + applog(READ_DEBUG, + "%s%i:%d %s info [%c] version=0x%02x prod=%.7s" + " serial=0x%08x", + klncgpu->drv->name, klncgpu->device_id, + (int)(kline->hd.dev), msg, kline->hd.cmd, + (int)(kline->id.version), + kline->id.product, + (unsigned int)K_SERIAL(kline->id.serial)); + break; + default: + hexdata = bin2hex((unsigned char *)&(kline->hd.dev), REPLY_SIZE - 1); + applog(LOG_ERR, + "%s%i:%d %s [%c:%s] unknown and ignored", + klncgpu->drv->name, klncgpu->device_id, + (int)(kline->hd.dev), msg, kline->hd.cmd, + hexdata); + free(hexdata); + break; + } +} + +static void display_send_kline(struct cgpu_info *klncgpu, KLINE *kline, const char *msg) +{ + char *hexdata; + + switch (kline->hd.cmd) { + case KLN_CMD_WORK: + applog(READ_DEBUG, + "%s%i:%d %s work [%c] dev=%d workid=0x%02x ...", + klncgpu->drv->name, klncgpu->device_id, + (int)(kline->wt.dev), msg, kline->ws.cmd, + (int)(kline->wt.dev), + (int)(kline->wt.workid)); + break; + case KLN_CMD_CONFIG: + applog(READ_DEBUG, + "%s%i:%d %s config [%c] dev=%d clock=%d" + " temptarget=%d tempcrit=%d fan=%d", + klncgpu->drv->name, klncgpu->device_id, + (int)(kline->cfg.dev), msg, kline->cfg.cmd, + (int)(kline->cfg.dev), + K_HASHCLOCK(kline->cfg.hashclock), + (int)(kline->cfg.temptarget), + (int)(kline->cfg.tempcritical), + (int)(kline->cfg.fantarget)); + break; + case KLN_CMD_IDENT: + case KLN_CMD_STATUS: + case KLN_CMD_ABORT: + applog(READ_DEBUG, + "%s%i:%d %s cmd [%c]", + klncgpu->drv->name, klncgpu->device_id, + (int)(kline->hd.dev), msg, kline->hd.cmd); + break; + case KLN_CMD_ENABLE: + applog(READ_DEBUG, + "%s%i:%d %s enable [%c] enable=%c", + klncgpu->drv->name, klncgpu->device_id, + (int)(kline->hd.dev), msg, kline->hd.cmd, + (char)(kline->hd.buf[0])); + break; + case KLN_CMD_NONCE: + default: + hexdata = bin2hex((unsigned char *)&(kline->hd.dev), REPLY_SIZE - 1); + applog(LOG_ERR, + "%s%i:%d %s [%c:%s] unknown/unexpected and ignored", + klncgpu->drv->name, klncgpu->device_id, + (int)(kline->hd.dev), msg, kline->hd.cmd, + hexdata); + free(hexdata); + break; + } +} + +static bool SendCmd(struct cgpu_info *klncgpu, KLINE *kline, int datalen) +{ + int err, amt, writ; + + if (klncgpu->usbinfo.nodev) + return false; + + display_send_kline(klncgpu, kline, msg_send); + writ = KSENDHD(datalen); + err = usb_write(klncgpu, (char *)kline, writ, &amt, C_REQUESTRESULTS); + if (err < 0 || amt != writ) { + applog(LOG_ERR, "%s%i:%d Cmd:%c Dev:%d, write failed (%d:%d:%d)", + klncgpu->drv->name, klncgpu->device_id, + (int)(kline->hd.dev), + kline->hd.cmd, (int)(kline->hd.dev), + writ, amt, err); + return false; + } + + return true; +} + +static KLIST *GetReply(struct cgpu_info *klncgpu, uint8_t cmd, uint8_t dev) +{ + struct klondike_info *klninfo = (struct klondike_info *)(klncgpu->device_data); + KLIST *kitem; + int retries = CMD_REPLY_RETRIES; + + while (retries-- > 0 && klncgpu->shutdown == false) { + cgsleep_ms(REPLY_WAIT_TIME); + cg_rlock(&klninfo->klist_lock); + kitem = klninfo->used; + while (kitem) { + if (kitem->kline.hd.cmd == cmd && + kitem->kline.hd.dev == dev && + kitem->ready == true && kitem->working == false) { + kitem->working = true; + cg_runlock(&klninfo->klist_lock); + return kitem; + } + kitem = kitem->next; + } + cg_runlock(&klninfo->klist_lock); + } + return NULL; +} + +static KLIST *SendCmdGetReply(struct cgpu_info *klncgpu, KLINE *kline, int datalen) +{ + if (!SendCmd(klncgpu, kline, datalen)) + return NULL; + + return GetReply(klncgpu, kline->hd.cmd, kline->hd.dev); +} + +static bool klondike_get_stats(struct cgpu_info *klncgpu) +{ + struct klondike_info *klninfo = (struct klondike_info *)(klncgpu->device_data); + KLIST *kitem; + KLINE kline; + int slaves, dev; + + if (klncgpu->usbinfo.nodev || klninfo->status == NULL) + return false; + + applog(LOG_DEBUG, "%s%i: getting status", + klncgpu->drv->name, klncgpu->device_id); + + rd_lock(&(klninfo->stat_lock)); + slaves = klninfo->status[0].kline.ws.slavecount; + rd_unlock(&(klninfo->stat_lock)); + + // loop thru devices and get status for each + for (dev = 0; dev <= slaves; dev++) { + zero_kline(&kline); + kline.hd.cmd = KLN_CMD_STATUS; + kline.hd.dev = dev; + kitem = SendCmdGetReply(klncgpu, &kline, 0); + if (kitem != NULL) { + wr_lock(&(klninfo->stat_lock)); + memcpy((void *)(&(klninfo->status[dev])), + (void *)kitem, + sizeof(klninfo->status[dev])); + wr_unlock(&(klninfo->stat_lock)); + kitem = release_kitem(klncgpu, kitem); + } else { + applog(LOG_ERR, "%s%i:%d failed to update stats", + klncgpu->drv->name, klncgpu->device_id, dev); + } + } + return true; +} + +// TODO: this only enables the master (no slaves) +static bool kln_enable(struct cgpu_info *klncgpu) +{ + KLIST *kitem; + KLINE kline; + int tries = 2; + bool ok = false; + + zero_kline(&kline); + kline.hd.cmd = KLN_CMD_ENABLE; + kline.hd.dev = 0; + kline.hd.buf[0] = KLN_CMD_ENABLE_ON; + + while (tries-- > 0) { + kitem = SendCmdGetReply(klncgpu, &kline, 1); + if (kitem) { + kitem = release_kitem(klncgpu, kitem); + ok = true; + break; + } + cgsleep_ms(50); + } + + if (ok) + cgsleep_ms(50); + + return ok; +} + +static void kln_disable(struct cgpu_info *klncgpu, int dev, bool all) +{ + KLINE kline; + int i; + + zero_kline(&kline); + kline.hd.cmd = KLN_CMD_ENABLE; + kline.hd.buf[0] = KLN_CMD_ENABLE_OFF; + for (i = (all ? 0 : dev); i <= dev; i++) { + kline.hd.dev = i; + SendCmd(klncgpu, &kline, KSENDHD(1)); + } +} + +static bool klondike_init(struct cgpu_info *klncgpu) +{ + struct klondike_info *klninfo = (struct klondike_info *)(klncgpu->device_data); + KLIST *kitem; + KLINE kline; + int slaves, dev; + + klninfo->initialised = false; + + zero_kline(&kline); + kline.hd.cmd = KLN_CMD_STATUS; + kline.hd.dev = 0; + kitem = SendCmdGetReply(klncgpu, &kline, 0); + if (kitem == NULL) + return false; + + slaves = kitem->kline.ws.slavecount; + if (klninfo->status == NULL) { + applog(LOG_DEBUG, "%s%i: initializing data", + klncgpu->drv->name, klncgpu->device_id); + + // alloc space for status, devinfo, cfg and jobque for master and slaves + klninfo->status = calloc(slaves+1, sizeof(*(klninfo->status))); + if (unlikely(!klninfo->status)) + quit(1, "Failed to calloc status array in klondke_get_stats"); + klninfo->devinfo = calloc(slaves+1, sizeof(*(klninfo->devinfo))); + if (unlikely(!klninfo->devinfo)) + quit(1, "Failed to calloc devinfo array in klondke_get_stats"); + klninfo->cfg = calloc(slaves+1, sizeof(*(klninfo->cfg))); + if (unlikely(!klninfo->cfg)) + quit(1, "Failed to calloc cfg array in klondke_get_stats"); + klninfo->jobque = calloc(slaves+1, sizeof(*(klninfo->jobque))); + if (unlikely(!klninfo->jobque)) + quit(1, "Failed to calloc jobque array in klondke_get_stats"); + } + + memcpy((void *)(&(klninfo->status[0])), (void *)kitem, sizeof(klninfo->status[0])); + kitem = release_kitem(klncgpu, kitem); + + // zero init triggers read back only + zero_kline(&kline); + kline.cfg.cmd = KLN_CMD_CONFIG; + + int size = 2; + + // boundaries are checked by device, with valid values returned + if (opt_klondike_options != NULL) { + int hashclock; + double temptarget; + + sscanf(opt_klondike_options, "%d:%lf", &hashclock, &temptarget); + SET_HASHCLOCK(kline.cfg.hashclock, hashclock); + kline.cfg.temptarget = cvtCToKln(temptarget); + kline.cfg.tempcritical = 0; // hard code for old firmware + kline.cfg.fantarget = 0xff; // hard code for old firmware + size = sizeof(kline.cfg) - 2; + } + + for (dev = 0; dev <= slaves; dev++) { + kline.cfg.dev = dev; + kitem = SendCmdGetReply(klncgpu, &kline, size); + if (kitem != NULL) { + memcpy((void *)&(klninfo->cfg[dev]), kitem, sizeof(klninfo->cfg[dev])); + applog(LOG_WARNING, "%s%i:%d config (%d: Clk: %d, T:%.0lf, C:%.0lf, F:%d)", + klncgpu->drv->name, klncgpu->device_id, dev, + dev, K_HASHCLOCK(klninfo->cfg[dev].kline.cfg.hashclock), + cvtKlnToC(klninfo->cfg[dev].kline.cfg.temptarget), + cvtKlnToC(klninfo->cfg[dev].kline.cfg.tempcritical), + (int)100*klninfo->cfg[dev].kline.cfg.fantarget/256); + kitem = release_kitem(klncgpu, kitem); + } + } + klondike_get_stats(klncgpu); + klninfo->initialised = true; + for (dev = 0; dev <= slaves; dev++) { + klninfo->devinfo[dev].rangesize = ((uint64_t)1<<32) / klninfo->status[dev].kline.ws.chipcount; + klninfo->devinfo[dev].chipstats = calloc(klninfo->status[dev].kline.ws.chipcount*2 , sizeof(uint32_t)); + } + + bool ok = kln_enable(klncgpu); + + if (!ok) + applog(LOG_ERR, "%s%i: failed to enable", klncgpu->drv->name, klncgpu->device_id); + + return ok; +} + +static void control_init(struct cgpu_info *klncgpu) +{ + int err, interface; + + if (klncgpu->usbinfo.nodev) + return; + + interface = usb_interface(klncgpu); + + err = usb_transfer(klncgpu, 0, 9, 1, interface, C_RESET); + + applog(LOG_DEBUG, "%s%i: reset got err %d", + klncgpu->drv->name, klncgpu->device_id, err); +} + +static struct cgpu_info *klondike_detect_one(struct libusb_device *dev, struct usb_find_devices *found) +{ + struct cgpu_info *klncgpu = usb_alloc_cgpu(&klondike_drv, 1); + struct klondike_info *klninfo = NULL; + KLINE kline; + + if (unlikely(!klncgpu)) + quit(1, "Failed to calloc klncgpu in klondike_detect_one"); + + klninfo = calloc(1, sizeof(*klninfo)); + if (unlikely(!klninfo)) + quit(1, "Failed to calloc klninfo in klondke_detect_one"); + klncgpu->device_data = (void *)klninfo; + + klninfo->free = new_klist_set(klncgpu); + + if (usb_init(klncgpu, dev, found)) { + int sent, recd, err; + KLIST kitem; + int attempts = 0; + + control_init(klncgpu); + + while (attempts++ < 3) { + kline.hd.cmd = KLN_CMD_IDENT; + kline.hd.dev = 0; + display_send_kline(klncgpu, &kline, msg_detect_send); + err = usb_write(klncgpu, (char *)&(kline.hd), 2, &sent, C_REQUESTRESULTS); + if (err < 0 || sent != 2) { + applog(LOG_ERR, "%s (%s) detect write failed (%d:%d)", + klncgpu->drv->dname, + klncgpu->device_path, + sent, err); + } + cgsleep_ms(REPLY_WAIT_TIME*10); + err = usb_read(klncgpu, (char *)&(kitem.kline), REPLY_SIZE, &recd, C_GETRESULTS); + if (err < 0) { + applog(LOG_ERR, "%s (%s) detect read failed (%d:%d)", + klncgpu->drv->dname, + klncgpu->device_path, + recd, err); + } else if (recd < 1) { + applog(LOG_ERR, "%s (%s) detect empty reply (%d)", + klncgpu->drv->dname, + klncgpu->device_path, + recd); + } else if (kitem.kline.hd.cmd == KLN_CMD_IDENT && kitem.kline.hd.dev == 0) { + display_kline(klncgpu, &kitem.kline, msg_detect_reply); + applog(LOG_DEBUG, "%s (%s) detect successful (%d attempt%s)", + klncgpu->drv->dname, + klncgpu->device_path, + attempts, attempts == 1 ? "" : "s"); + if (!add_cgpu(klncgpu)) + break; + update_usb_stats(klncgpu); + applog(LOG_DEBUG, "Klondike cgpu added"); + rwlock_init(&klninfo->stat_lock); + cglock_init(&klninfo->klist_lock); + return klncgpu; + } + } + usb_uninit(klncgpu); + } + free(klninfo->free); + free(klninfo); + free(klncgpu); + return NULL; +} + +static void klondike_detect(bool __maybe_unused hotplug) +{ + usb_detect(&klondike_drv, klondike_detect_one); +} + +static void klondike_identify(__maybe_unused struct cgpu_info *klncgpu) +{ +/* + KLINE kline; + + zero_kline(&kline); + kline.hd.cmd = KLN_CMD_IDENT; + kline.hd.dev = 0; + SendCmdGetReply(klncgpu, &kline, KSENDHD(0)); +*/ +} + +static void klondike_check_nonce(struct cgpu_info *klncgpu, KLIST *kitem) +{ + struct klondike_info *klninfo = (struct klondike_info *)(klncgpu->device_data); + struct work *work, *look, *tmp; + KLINE *kline = &(kitem->kline); + struct timeval tv_now; + double us_diff; + uint32_t nonce = K_NONCE(kline->wr.nonce) - 0xC0; + + applog(LOG_DEBUG, "%s%i:%d FOUND NONCE (%02x:%08x)", + klncgpu->drv->name, klncgpu->device_id, (int)(kline->wr.dev), + kline->wr.workid, (unsigned int)nonce); + + work = NULL; + cgtime(&tv_now); + rd_lock(&(klncgpu->qlock)); + HASH_ITER(hh, klncgpu->queued_work, look, tmp) { + if (ms_tdiff(&tv_now, &(look->tv_stamp)) < OLD_WORK_MS && + (look->subid == (kline->wr.dev*256 + kline->wr.workid))) { + work = look; + break; + } + } + rd_unlock(&(klncgpu->qlock)); + + if (work) { + wr_lock(&(klninfo->stat_lock)); + klninfo->devinfo[kline->wr.dev].noncecount++; + klninfo->noncecount++; + wr_unlock(&(klninfo->stat_lock)); + + applog(LOG_DEBUG, "%s%i:%d SUBMIT NONCE (%02x:%08x)", + klncgpu->drv->name, klncgpu->device_id, (int)(kline->wr.dev), + kline->wr.workid, (unsigned int)nonce); + + cgtime(&tv_now); + bool ok = submit_nonce(klncgpu->thr[0], work, nonce); + + applog(LOG_DEBUG, "%s%i:%d chip stats %d, %08x, %d, %d", + klncgpu->drv->name, klncgpu->device_id, (int)(kline->wr.dev), + kline->wr.dev, (unsigned int)nonce, + klninfo->devinfo[kline->wr.dev].rangesize, + klninfo->status[kline->wr.dev].kline.ws.chipcount); + + klninfo->devinfo[kline->wr.dev].chipstats[(nonce / klninfo->devinfo[kline->wr.dev].rangesize) + (ok ? 0 : klninfo->status[kline->wr.dev].kline.ws.chipcount)]++; + + us_diff = us_tdiff(&tv_now, &(kitem->tv_when)); + if (klninfo->delay_count == 0) { + klninfo->delay_min = us_diff; + klninfo->delay_max = us_diff; + } else { + if (klninfo->delay_min > us_diff) + klninfo->delay_min = us_diff; + if (klninfo->delay_max < us_diff) + klninfo->delay_max = us_diff; + } + klninfo->delay_count++; + klninfo->delay_total += us_diff; + + if (klninfo->nonce_count > 0) { + us_diff = us_tdiff(&(kitem->tv_when), &(klninfo->tv_last_nonce_received)); + if (klninfo->nonce_count == 1) { + klninfo->nonce_min = us_diff; + klninfo->nonce_max = us_diff; + } else { + if (klninfo->nonce_min > us_diff) + klninfo->nonce_min = us_diff; + if (klninfo->nonce_max < us_diff) + klninfo->nonce_max = us_diff; + } + klninfo->nonce_total += us_diff; + } + klninfo->nonce_count++; + + memcpy(&(klninfo->tv_last_nonce_received), &(kitem->tv_when), + sizeof(klninfo->tv_last_nonce_received)); + + return; + } + + applog(LOG_ERR, "%s%i:%d unknown work (%02x:%08x) - ignored", + klncgpu->drv->name, klncgpu->device_id, (int)(kline->wr.dev), + kline->wr.workid, (unsigned int)nonce); + + //inc_hw_errors(klncgpu->thr[0]); +} + +// thread to keep looking for replies +static void *klondike_get_replies(void *userdata) +{ + struct cgpu_info *klncgpu = (struct cgpu_info *)userdata; + struct klondike_info *klninfo = (struct klondike_info *)(klncgpu->device_data); + KLIST *kitem = NULL; + char *hexdata; + int err, recd, slaves, dev, isc; + bool overheat, sent; + + applog(LOG_DEBUG, "%s%i: listening for replies", + klncgpu->drv->name, klncgpu->device_id); + + while (klncgpu->shutdown == false) { + if (klncgpu->usbinfo.nodev) + return NULL; + + if (kitem == NULL) + kitem = allocate_kitem(klncgpu); + else + memset((void *)&(kitem->kline), 0, sizeof(kitem->kline)); + + err = usb_read(klncgpu, (char *)&(kitem->kline), REPLY_SIZE, &recd, C_GETRESULTS); + if (err || recd != REPLY_SIZE) { + if (err != -7) + applog(LOG_ERR, "%s%i: reply err=%d amt=%d", + klncgpu->drv->name, klncgpu->device_id, + err, recd); + } + if (!err && recd == REPLY_SIZE) { + cgtime(&(kitem->tv_when)); + rd_lock(&(klninfo->stat_lock)); + kitem->block_seq = klninfo->block_seq; + rd_unlock(&(klninfo->stat_lock)); + if (opt_log_level <= READ_DEBUG) { + hexdata = bin2hex((unsigned char *)&(kitem->kline.hd.dev), recd-1); + applog(READ_DEBUG, "%s%i:%d reply [%c:%s]", + klncgpu->drv->name, klncgpu->device_id, + (int)(kitem->kline.hd.dev), + kitem->kline.hd.cmd, hexdata); + free(hexdata); + } + + // We can't check this until it's initialised + if (klninfo->initialised) { + rd_lock(&(klninfo->stat_lock)); + slaves = klninfo->status[0].kline.ws.slavecount; + rd_unlock(&(klninfo->stat_lock)); + + if (kitem->kline.hd.dev > slaves) { + applog(LOG_ERR, "%s%i: reply [%c] has invalid dev=%d (max=%d) using 0", + klncgpu->drv->name, klncgpu->device_id, + (char)(kitem->kline.hd.cmd), + (int)(kitem->kline.hd.dev), + slaves); + /* TODO: this is rather problematic if there are slaves + * however without slaves - it should always be zero */ + kitem->kline.hd.dev = 0; + } else { + wr_lock(&(klninfo->stat_lock)); + klninfo->jobque[kitem->kline.hd.dev].late_update_sequential = 0; + wr_unlock(&(klninfo->stat_lock)); + } + } + + switch (kitem->kline.hd.cmd) { + case KLN_CMD_NONCE: + klondike_check_nonce(klncgpu, kitem); + display_kline(klncgpu, &kitem->kline, msg_reply); + break; + case KLN_CMD_WORK: + // We can't do/check this until it's initialised + if (klninfo->initialised) { + dev = kitem->kline.ws.dev; + if (kitem->kline.ws.workqc == 0) { + bool idle = false; + rd_lock(&(klninfo->stat_lock)); + if (klninfo->jobque[dev].flushed == false) + idle = true; + slaves = klninfo->status[0].kline.ws.slavecount; + rd_unlock(&(klninfo->stat_lock)); + if (idle) + applog(LOG_WARNING, "%s%i:%d went idle before work was sent", + klncgpu->drv->name, + klncgpu->device_id, + dev); + } + wr_lock(&(klninfo->stat_lock)); + klninfo->jobque[dev].flushed = false; + wr_unlock(&(klninfo->stat_lock)); + } + case KLN_CMD_STATUS: + case KLN_CMD_ABORT: + // We can't do/check this until it's initialised + if (klninfo->initialised) { + isc = 0; + dev = kitem->kline.ws.dev; + wr_lock(&(klninfo->stat_lock)); + klninfo->jobque[dev].workqc = (int)(kitem->kline.ws.workqc); + cgtime(&(klninfo->jobque[dev].last_update)); + slaves = klninfo->status[0].kline.ws.slavecount; + overheat = klninfo->jobque[dev].overheat; + if (dev == 0) { + if (kitem->kline.ws.slavecount != slaves) + isc = ++klninfo->incorrect_slave_sequential; + else + isc = klninfo->incorrect_slave_sequential = 0; + } + wr_unlock(&(klninfo->stat_lock)); + + if (isc) { + applog(LOG_ERR, "%s%i:%d reply [%c] has a diff" + " # of slaves=%d (curr=%d)%s", + klncgpu->drv->name, + klncgpu->device_id, + dev, + (char)(kitem->kline.ws.cmd), + (int)(kitem->kline.ws.slavecount), + slaves, + isc <= KLN_ISS_IGNORE ? "" : + " disabling device"); + if (isc > KLN_ISS_IGNORE) + usb_nodev(klncgpu); + break; + } + + if (!overheat) { + double temp = cvtKlnToC(kitem->kline.ws.temp); + if (temp >= KLN_KILLWORK_TEMP) { + KLINE kline; + + wr_lock(&(klninfo->stat_lock)); + klninfo->jobque[dev].overheat = true; + wr_unlock(&(klninfo->stat_lock)); + + applog(LOG_WARNING, "%s%i:%d Critical overheat (%.0fC)", + klncgpu->drv->name, + klncgpu->device_id, + dev, temp); + + zero_kline(&kline); + kline.hd.cmd = KLN_CMD_ABORT; + kline.hd.dev = dev; + sent = SendCmd(klncgpu, &kline, KSENDHD(0)); + kln_disable(klncgpu, dev, false); + if (!sent) { + applog(LOG_ERR, "%s%i:%d overheat failed to" + " abort work - disabling device", + klncgpu->drv->name, + klncgpu->device_id, + dev); + usb_nodev(klncgpu); + } + } + } + } + case KLN_CMD_ENABLE: + wr_lock(&(klninfo->stat_lock)); + klninfo->errorcount += kitem->kline.ws.errorcount; + klninfo->noisecount += kitem->kline.ws.noise; + wr_unlock(&(klninfo->stat_lock)); + display_kline(klncgpu, &kitem->kline, msg_reply); + kitem->ready = true; + kitem = NULL; + break; + case KLN_CMD_CONFIG: + display_kline(klncgpu, &kitem->kline, msg_reply); + kitem->ready = true; + kitem = NULL; + break; + case KLN_CMD_IDENT: + display_kline(klncgpu, &kitem->kline, msg_reply); + kitem->ready = true; + kitem = NULL; + break; + default: + display_kline(klncgpu, &kitem->kline, msg_reply); + break; + } + } + } + return NULL; +} + +static void klondike_flush_work(struct cgpu_info *klncgpu) +{ + struct klondike_info *klninfo = (struct klondike_info *)(klncgpu->device_data); + KLIST *kitem; + KLINE kline; + int slaves, dev; + + if (klninfo->initialised) { + wr_lock(&(klninfo->stat_lock)); + klninfo->block_seq++; + slaves = klninfo->status[0].kline.ws.slavecount; + wr_unlock(&(klninfo->stat_lock)); + + applog(LOG_DEBUG, "%s%i: flushing work", + klncgpu->drv->name, klncgpu->device_id); + zero_kline(&kline); + kline.hd.cmd = KLN_CMD_ABORT; + for (dev = 0; dev <= slaves; dev++) { + kline.hd.dev = dev; + kitem = SendCmdGetReply(klncgpu, &kline, KSENDHD(0)); + if (kitem != NULL) { + wr_lock(&(klninfo->stat_lock)); + memcpy((void *)&(klninfo->status[dev]), + kitem, + sizeof(klninfo->status[dev])); + klninfo->jobque[dev].flushed = true; + wr_unlock(&(klninfo->stat_lock)); + kitem = release_kitem(klncgpu, kitem); + } + } + } +} + +static bool klondike_thread_prepare(struct thr_info *thr) +{ + struct cgpu_info *klncgpu = thr->cgpu; + struct klondike_info *klninfo = (struct klondike_info *)(klncgpu->device_data); + + if (thr_info_create(&(klninfo->replies_thr), NULL, klondike_get_replies, (void *)klncgpu)) { + applog(LOG_ERR, "%s%i: thread create failed", klncgpu->drv->name, klncgpu->device_id); + return false; + } + pthread_detach(klninfo->replies_thr.pth); + + // let the listening get started + cgsleep_ms(100); + + return klondike_init(klncgpu); +} + +static bool klondike_thread_init(struct thr_info *thr) +{ + struct cgpu_info *klncgpu = thr->cgpu; + + if (klncgpu->usbinfo.nodev) + return false; + + klondike_flush_work(klncgpu); + + return true; +} + +static void klondike_shutdown(struct thr_info *thr) +{ + struct cgpu_info *klncgpu = thr->cgpu; + struct klondike_info *klninfo = (struct klondike_info *)(klncgpu->device_data); + + applog(LOG_DEBUG, "%s%i: shutting down work", + klncgpu->drv->name, klncgpu->device_id); + + kln_disable(klncgpu, klninfo->status[0].kline.ws.slavecount, true); + + klncgpu->shutdown = true; +} + +static void klondike_thread_enable(struct thr_info *thr) +{ + struct cgpu_info *klncgpu = thr->cgpu; + + if (klncgpu->usbinfo.nodev) + return; + +/* + KLINE kline; + + zero_kline(&kline); + kline.hd.cmd = KLN_CMD_ENABLE; + kline.hd.dev = dev; + kline.hd.buf[0] = KLN_CMD_ENABLE_OFF; + kitem = SendCmdGetReply(klncgpu, &kline, KSENDHD(1)); +*/ + +} + +static bool klondike_send_work(struct cgpu_info *klncgpu, int dev, struct work *work) +{ + struct klondike_info *klninfo = (struct klondike_info *)(klncgpu->device_data); + struct work *look, *tmp; + KLINE kline; + struct timeval tv_old; + int wque_size, wque_cleared; + + if (klncgpu->usbinfo.nodev) + return false; + + zero_kline(&kline); + kline.wt.cmd = KLN_CMD_WORK; + kline.wt.dev = dev; + memcpy(kline.wt.midstate, work->midstate, MIDSTATE_BYTES); + memcpy(kline.wt.merkle, work->data + MERKLE_OFFSET, MERKLE_BYTES); + kline.wt.workid = (uint8_t)(klninfo->devinfo[dev].nextworkid++ & 0xFF); + work->subid = dev*256 + kline.wt.workid; + cgtime(&work->tv_stamp); + + if (opt_log_level <= LOG_DEBUG) { + char *hexdata = bin2hex((void *)&kline.wt, sizeof(kline.wt)); + applog(LOG_DEBUG, "WORKDATA: %s", hexdata); + free(hexdata); + } + + applog(LOG_DEBUG, "%s%i:%d sending work (%d:%02x)", + klncgpu->drv->name, klncgpu->device_id, dev, + dev, kline.wt.workid); + KLIST *kitem = SendCmdGetReply(klncgpu, &kline, sizeof(kline.wt)); + if (kitem != NULL) { + wr_lock(&(klninfo->stat_lock)); + memcpy((void *)&(klninfo->status[dev]), kitem, sizeof(klninfo->status[dev])); + wr_unlock(&(klninfo->stat_lock)); + kitem = release_kitem(klncgpu, kitem); + + // remove old work + wque_size = 0; + wque_cleared = 0; + cgtime(&tv_old); + wr_lock(&klncgpu->qlock); + HASH_ITER(hh, klncgpu->queued_work, look, tmp) { + if (ms_tdiff(&tv_old, &(look->tv_stamp)) > OLD_WORK_MS) { + __work_completed(klncgpu, look); + free_work(look); + wque_cleared++; + } else + wque_size++; + } + wr_unlock(&klncgpu->qlock); + + wr_lock(&(klninfo->stat_lock)); + klninfo->wque_size = wque_size; + klninfo->wque_cleared = wque_cleared; + wr_unlock(&(klninfo->stat_lock)); + return true; + } + return false; +} + +static bool klondike_queue_full(struct cgpu_info *klncgpu) +{ + struct klondike_info *klninfo = (struct klondike_info *)(klncgpu->device_data); + struct work *work = NULL; + int dev, queued, slaves, seq, howlong; + struct timeval now; + bool nowork; + + if (klncgpu->shutdown == true) + return true; + + cgtime(&now); + rd_lock(&(klninfo->stat_lock)); + slaves = klninfo->status[0].kline.ws.slavecount; + for (dev = 0; dev <= slaves; dev++) + if (ms_tdiff(&now, &(klninfo->jobque[dev].last_update)) > LATE_UPDATE_MS) { + klninfo->jobque[dev].late_update_count++; + seq = ++klninfo->jobque[dev].late_update_sequential; + rd_unlock(&(klninfo->stat_lock)); + if (seq < LATE_UPDATE_LIMIT) { + applog(LOG_DEBUG, "%s%i:%d late update", + klncgpu->drv->name, klncgpu->device_id, dev); + klondike_get_stats(klncgpu); + goto que; + } else { + applog(LOG_WARNING, "%s%i:%d late update (%d) reached - attempting reset", + klncgpu->drv->name, klncgpu->device_id, + dev, LATE_UPDATE_LIMIT); + control_init(klncgpu); + kln_enable(klncgpu); + klondike_get_stats(klncgpu); + rd_lock(&(klninfo->stat_lock)); + howlong = ms_tdiff(&now, &(klninfo->jobque[dev].last_update)); + if (howlong > LATE_UPDATE_MS) { + rd_unlock(&(klninfo->stat_lock)); + if (howlong > LATE_UPDATE_NODEV_MS) { + applog(LOG_ERR, "%s%i:%d reset failed - dropping device", + klncgpu->drv->name, klncgpu->device_id, dev); + usb_nodev(klncgpu); + } else + cgsleep_ms(LATE_UPDATE_SLEEP_MS); + + return true; + } + break; + } + } + rd_unlock(&(klninfo->stat_lock)); + +que: + + nowork = true; + for (queued = 0; queued < MAX_WORK_COUNT-1; queued++) + for (dev = 0; dev <= slaves; dev++) { +tryagain: + rd_lock(&(klninfo->stat_lock)); + if (klninfo->jobque[dev].overheat) { + double temp = cvtKlnToC(klninfo->status[0].kline.ws.temp); + if ((queued == MAX_WORK_COUNT-2) && + ms_tdiff(&now, &(klninfo->jobque[dev].last_update)) > (LATE_UPDATE_MS/2)) { + rd_unlock(&(klninfo->stat_lock)); + klondike_get_stats(klncgpu); + goto tryagain; + } + if (temp <= KLN_COOLED_DOWN) { + klninfo->jobque[dev].overheat = false; + rd_unlock(&(klninfo->stat_lock)); + applog(LOG_WARNING, "%s%i:%d Overheat recovered (%.0fC)", + klncgpu->drv->name, klncgpu->device_id, + dev, temp); + kln_enable(klncgpu); + goto tryagain; + } else { + rd_unlock(&(klninfo->stat_lock)); + continue; + } + } + + if (klninfo->jobque[dev].workqc <= queued) { + rd_unlock(&(klninfo->stat_lock)); + if (!work) + work = get_queued(klncgpu); + if (unlikely(!work)) + return false; + nowork = false; + if (klondike_send_work(klncgpu, dev, work)) + return false; + } else + rd_unlock(&(klninfo->stat_lock)); + } + + if (nowork) + cgsleep_ms(10); // avoid a hard loop in case we have nothing to do + + return true; +} + +static int64_t klondike_scanwork(struct thr_info *thr) +{ + struct cgpu_info *klncgpu = thr->cgpu; + struct klondike_info *klninfo = (struct klondike_info *)(klncgpu->device_data); + int64_t newhashcount = 0; + int dev, slaves; + + if (klncgpu->usbinfo.nodev) + return -1; + + restart_wait(thr, 200); + if (klninfo->status != NULL) { + rd_lock(&(klninfo->stat_lock)); + slaves = klninfo->status[0].kline.ws.slavecount; + for (dev = 0; dev <= slaves; dev++) { + uint64_t newhashdev = 0, hashcount; + int maxcount; + + hashcount = K_HASHCOUNT(klninfo->status[dev].kline.ws.hashcount); + maxcount = K_MAXCOUNT(klninfo->status[dev].kline.ws.maxcount); + // todo: chg this to check workid for wrapped instead + if (klninfo->devinfo[dev].lasthashcount > hashcount) + newhashdev += maxcount; // hash counter wrapped + newhashdev += hashcount - klninfo->devinfo[dev].lasthashcount; + klninfo->devinfo[dev].lasthashcount = hashcount; + if (maxcount != 0) + klninfo->hashcount += (newhashdev << 32) / maxcount; + } + newhashcount += 0xffffffffull * (uint64_t)klninfo->noncecount; + klninfo->noncecount = 0; + rd_unlock(&(klninfo->stat_lock)); + } + + return newhashcount; +} + + +static void get_klondike_statline_before(char *buf, size_t siz, struct cgpu_info *klncgpu) +{ + struct klondike_info *klninfo = (struct klondike_info *)(klncgpu->device_data); + uint8_t temp = 0xFF; + uint16_t fan = 0; + uint16_t clock = 0; + int dev, slaves; + + if (klninfo->status == NULL) { + blank_get_statline_before(buf, siz, klncgpu); + return; + } + + rd_lock(&(klninfo->stat_lock)); + slaves = klninfo->status[0].kline.ws.slavecount; + for (dev = 0; dev <= slaves; dev++) { + if (klninfo->status[dev].kline.ws.temp < temp) + temp = klninfo->status[dev].kline.ws.temp; + fan += klninfo->cfg[dev].kline.cfg.fantarget; + clock += (uint16_t)K_HASHCLOCK(klninfo->cfg[dev].kline.cfg.hashclock); + } + rd_unlock(&(klninfo->stat_lock)); + fan /= slaves + 1; + //fan *= 100/255; // <-- You can't do this because int 100 / int 255 == 0 + fan = 100 * fan / 255; + if (fan > 100) + fan = 100; + clock /= slaves + 1; + if (clock > 999) // error - so truncate it + clock = 999; + + tailsprintf(buf, siz, "%3dMHz %3d%% %.1fC", (int)clock, (int)fan, cvtKlnToC(temp)); +} + +static struct api_data *klondike_api_stats(struct cgpu_info *klncgpu) +{ + struct klondike_info *klninfo = (struct klondike_info *)(klncgpu->device_data); + struct api_data *root = NULL; + char buf[32]; + int dev, slaves; + + if (klninfo->status == NULL) + return NULL; + + rd_lock(&(klninfo->stat_lock)); + slaves = klninfo->status[0].kline.ws.slavecount; + for (dev = 0; dev <= slaves; dev++) { + + float fTemp = cvtKlnToC(klninfo->status[dev].kline.ws.temp); + sprintf(buf, "Temp %d", dev); + root = api_add_temp(root, buf, &fTemp, true); + + double dClk = (double)K_HASHCLOCK(klninfo->cfg[dev].kline.cfg.hashclock); + sprintf(buf, "Clock %d", dev); + root = api_add_freq(root, buf, &dClk, true); + + unsigned int iFan = (unsigned int)100 * klninfo->cfg[dev].kline.cfg.fantarget / 255; + sprintf(buf, "Fan Percent %d", dev); + root = api_add_int(root, buf, (int *)(&iFan), true); + + iFan = 0; + if (klninfo->status[dev].kline.ws.fanspeed > 0) + iFan = (unsigned int)TACH_FACTOR / klninfo->status[dev].kline.ws.fanspeed; + sprintf(buf, "Fan RPM %d", dev); + root = api_add_int(root, buf, (int *)(&iFan), true); + + if (klninfo->devinfo[dev].chipstats != NULL) { + char data[2048]; + char one[32]; + int n; + + sprintf(buf, "Nonces / Chip %d", dev); + data[0] = '\0'; + for (n = 0; n < klninfo->status[dev].kline.ws.chipcount; n++) { + snprintf(one, sizeof(one), "%07d ", klninfo->devinfo[dev].chipstats[n]); + strcat(data, one); + } + root = api_add_string(root, buf, data, true); + + sprintf(buf, "Errors / Chip %d", dev); + data[0] = '\0'; + for (n = 0; n < klninfo->status[dev].kline.ws.chipcount; n++) { + snprintf(one, sizeof(one), "%07d ", klninfo->devinfo[dev].chipstats[n + klninfo->status[dev].kline.ws.chipcount]); + strcat(data, one); + } + root = api_add_string(root, buf, data, true); + } + } + + root = api_add_uint64(root, "Hash Count", &(klninfo->hashcount), true); + root = api_add_uint64(root, "Error Count", &(klninfo->errorcount), true); + root = api_add_uint64(root, "Noise Count", &(klninfo->noisecount), true); + + root = api_add_int(root, "KLine Limit", &(klninfo->kline_count), true); + root = api_add_int(root, "KLine Used", &(klninfo->used_count), true); + + root = api_add_elapsed(root, "KQue Delay Count", &(klninfo->delay_count), true); + root = api_add_elapsed(root, "KQue Delay Total", &(klninfo->delay_total), true); + root = api_add_elapsed(root, "KQue Delay Min", &(klninfo->delay_min), true); + root = api_add_elapsed(root, "KQue Delay Max", &(klninfo->delay_max), true); + double avg; + if (klninfo->delay_count == 0) + avg = 0; + else + avg = klninfo->delay_total / klninfo->delay_count; + root = api_add_diff(root, "KQue Delay Avg", &avg, true); + + root = api_add_elapsed(root, "KQue Nonce Count", &(klninfo->nonce_count), true); + root = api_add_elapsed(root, "KQue Nonce Total", &(klninfo->nonce_total), true); + root = api_add_elapsed(root, "KQue Nonce Min", &(klninfo->nonce_min), true); + root = api_add_elapsed(root, "KQue Nonce Max", &(klninfo->nonce_max), true); + if (klninfo->nonce_count == 0) + avg = 0; + else + avg = klninfo->nonce_total / klninfo->nonce_count; + root = api_add_diff(root, "KQue Nonce Avg", &avg, true); + + root = api_add_int(root, "WQue Size", &(klninfo->wque_size), true); + root = api_add_int(root, "WQue Cleared", &(klninfo->wque_cleared), true); + + rd_unlock(&(klninfo->stat_lock)); + + return root; +} + +struct device_drv klondike_drv = { + .drv_id = DRIVER_klondike, + .dname = "Klondike", + .name = "KLN", + .drv_detect = klondike_detect, + .get_api_stats = klondike_api_stats, + .get_statline_before = get_klondike_statline_before, + .get_stats = klondike_get_stats, + .identify_device = klondike_identify, + .thread_prepare = klondike_thread_prepare, + .thread_init = klondike_thread_init, + .hash_work = hash_queued_work, + .scanwork = klondike_scanwork, + .queue_full = klondike_queue_full, + .flush_work = klondike_flush_work, + .thread_shutdown = klondike_shutdown, + .thread_enable = klondike_thread_enable +}; diff --git a/driver-knc.c b/driver-knc.c new file mode 100644 index 0000000..564d27d --- /dev/null +++ b/driver-knc.c @@ -0,0 +1,865 @@ +/* + * cgminer driver for KnCminer devices + * + * Copyright 2014 KnCminer + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "logging.h" +#include "miner.h" +#include "knc-transport.h" +#include "knc-asic.h" + +#define MAX_ASICS 6 +#define DIES_PER_ASIC 4 +#define MAX_CORES_PER_DIE 360 +#define WORKS_PER_CORE 3 + +#define CORE_ERROR_LIMIT 30 +#define CORE_ERROR_INTERVAL 30 +#define CORE_ERROR_DISABLE_TIME 5*60 +#define CORE_SUBMIT_MIN_TIME 2 +#define CORE_TIMEOUT 20 +#define SCAN_ADJUST_RANGE 32 + +static struct timeval now; +static const struct timeval core_check_interval = { + CORE_ERROR_INTERVAL, 0 +}; +static const struct timeval core_disable_interval = { + CORE_ERROR_DISABLE_TIME, 0 +}; +static const struct timeval core_submit_interval = { + CORE_SUBMIT_MIN_TIME, 0 +}; +static const struct timeval core_timeout_interval = { + CORE_TIMEOUT, 0 +}; + +struct knc_die; + +struct knc_core_state { + int generation; + int core; + int coreid; + struct knc_die *die; + struct { + int slot; + struct work *work; + } workslot[WORKS_PER_CORE]; /* active, next */ + int transfer_stamp; + struct knc_report report; + struct { + int slot; + uint32_t nonce; + } last_nonce; + uint32_t works; + uint32_t shares; + uint32_t errors; + uint32_t completed; + int last_slot; + uint32_t errors_now; + struct timeval disabled_until; + struct timeval hold_work_until; + struct timeval timeout; + bool inuse; +}; + +struct knc_state; + +struct knc_die { + int channel; + int die; + int version; + int cores; + struct knc_state *knc; + struct knc_core_state *core; +}; + +#define MAX_SPI_SIZE (4096) +#define MAX_SPI_RESPONSES (MAX_SPI_SIZE / (2 + 4 + 1 + 1 + 1 + 4)) +#define MAX_SPI_MESSAGE (128) +#define KNC_SPI_BUFFERS (3) + +struct knc_state { + struct cgpu_info *cgpu; + void *ctx; + int generation; /* work/block generation, incremented on each flush invalidating older works */ + int dies; + struct knc_die die[MAX_ASICS*DIES_PER_ASIC]; + int cores; + int scan_adjust; + int startup; + /* Statistics */ + uint64_t shares; /* diff1 shares reported by hardware */ + uint64_t works; /* Work units submitted */ + uint64_t completed; /* Work units completed */ + uint64_t errors; /* Hardware & communication errors */ + struct timeval next_error_interval; + /* End of statistics */ + /* SPI communications thread */ + pthread_mutex_t spi_qlock; /* SPI queue status lock */ + struct thr_info spi_thr; /* SPI I/O thread */ + pthread_cond_t spi_qcond; /* SPI queue change wakeup */ + struct knc_spi_buffer { + enum { + KNC_SPI_IDLE=0, + KNC_SPI_PENDING, + KNC_SPI_DONE + } state; + int size; + uint8_t txbuf[MAX_SPI_SIZE]; + uint8_t rxbuf[MAX_SPI_SIZE]; + int responses; + struct knc_spi_response { + int request_length; + int response_length; + enum { + KNC_UNKNOWN = 0, + KNC_NO_RESPONSE, + KNC_SETWORK, + KNC_REPORT, + KNC_INFO + } type; + struct knc_core_state *core; + uint32_t data; + int offset; + } response_info[MAX_SPI_RESPONSES]; + } spi_buffer[KNC_SPI_BUFFERS]; + int send_buffer; + int read_buffer; + int send_buffer_count; + int read_buffer_count; + /* end SPI thread */ + + /* Do not add anything below here!! core[] must be last */ + struct knc_core_state core[]; +}; + +int opt_knc_device_idx = 0; +int opt_knc_device_bus = -1; +char *knc_log_file = NULL; + +static void *knc_spi(void *thr_data) +{ + struct cgpu_info *cgpu = thr_data; + struct knc_state *knc = cgpu->device_data; + int buffer = 0; + + pthread_mutex_lock(&knc->spi_qlock); + while (!cgpu->shutdown) { + int this_buffer = buffer; + while (knc->spi_buffer[buffer].state != KNC_SPI_PENDING && !cgpu->shutdown) + pthread_cond_wait(&knc->spi_qcond, &knc->spi_qlock); + pthread_mutex_unlock(&knc->spi_qlock); + if (cgpu->shutdown) + return NULL; + + knc_trnsp_transfer(knc->ctx, knc->spi_buffer[buffer].txbuf, knc->spi_buffer[buffer].rxbuf, knc->spi_buffer[buffer].size); + + buffer += 1; + if (buffer >= KNC_SPI_BUFFERS) + buffer = 0; + + pthread_mutex_lock(&knc->spi_qlock); + knc->spi_buffer[this_buffer].state = KNC_SPI_DONE; + pthread_cond_signal(&knc->spi_qcond); + } + pthread_mutex_unlock(&knc->spi_qlock); + return NULL; +} + +static void knc_process_responses(struct thr_info *thr); + +static void knc_flush(struct thr_info *thr) +{ + struct cgpu_info *cgpu = thr->cgpu; + struct knc_state *knc = cgpu->device_data; + struct knc_spi_buffer *buffer = &knc->spi_buffer[knc->send_buffer]; + if (buffer->state == KNC_SPI_IDLE && buffer->size > 0) { + pthread_mutex_lock(&knc->spi_qlock); + buffer->state = KNC_SPI_PENDING; + pthread_cond_signal(&knc->spi_qcond); + knc->send_buffer += 1; + knc->send_buffer_count += 1; + if (knc->send_buffer >= KNC_SPI_BUFFERS) + knc->send_buffer = 0; + buffer = &knc->spi_buffer[knc->send_buffer]; + /* Block for SPI to finish a transfer if all buffers are busy */ + while (buffer->state == KNC_SPI_PENDING) { + applog(LOG_DEBUG, "KnC: SPI buffer full (%d), waiting for SPI thread", buffer->responses); + pthread_cond_wait(&knc->spi_qcond, &knc->spi_qlock); + } + pthread_mutex_unlock(&knc->spi_qlock); + } + knc_process_responses(thr); +} + +static void knc_sync(struct thr_info *thr) +{ + struct cgpu_info *cgpu = thr->cgpu; + struct knc_state *knc = cgpu->device_data; + struct knc_spi_buffer *buffer = &knc->spi_buffer[knc->send_buffer]; + int sent = 0; + pthread_mutex_lock(&knc->spi_qlock); + if (buffer->state == KNC_SPI_IDLE && buffer->size > 0) { + buffer->state = KNC_SPI_PENDING; + pthread_cond_signal(&knc->spi_qcond); + knc->send_buffer += 1; + knc->send_buffer_count += 1; + if (knc->send_buffer >= KNC_SPI_BUFFERS) + knc->send_buffer = 0; + sent = 1; + } + int prev_buffer = knc->send_buffer - 1; + if (prev_buffer < 0) + prev_buffer = KNC_SPI_BUFFERS - 1; + buffer = &knc->spi_buffer[prev_buffer]; + while (buffer->state == KNC_SPI_PENDING) + pthread_cond_wait(&knc->spi_qcond, &knc->spi_qlock); + pthread_mutex_unlock(&knc->spi_qlock); + + int pending = knc->send_buffer - knc->read_buffer; + if (pending <= 0) + pending += KNC_SPI_BUFFERS; + pending -= 1 - sent; + applog(LOG_INFO, "KnC: sync %d pending buffers", pending); + knc_process_responses(thr); +} + +static void knc_transfer(struct thr_info *thr, struct knc_core_state *core, int request_length, uint8_t *request, int response_length, int response_type, uint32_t data) +{ + struct cgpu_info *cgpu = thr->cgpu; + struct knc_state *knc = cgpu->device_data; + struct knc_spi_buffer *buffer = &knc->spi_buffer[knc->send_buffer]; + /* FPGA control, request header, request body/response, CRC(4), ACK(1), EXTRA(3) */ + int msglen = 2 + MAX(request_length, 4 + response_length ) + 4 + 1 + 3; + if (buffer->size + msglen > MAX_SPI_SIZE || buffer->responses >= MAX_SPI_RESPONSES) { + applog(LOG_INFO, "KnC: SPI buffer sent, %d messages %d bytes", buffer->responses, buffer->size); + knc_flush(thr); + buffer = &knc->spi_buffer[knc->send_buffer]; + } + struct knc_spi_response *response_info = &buffer->response_info[buffer->responses]; + buffer->responses++; + response_info->offset = buffer->size; + response_info->type = response_type; + response_info->request_length = request_length; + response_info->response_length = response_length; + response_info->core = core; + response_info->data = data; + buffer->size = knc_prepare_transfer(buffer->txbuf, buffer->size, MAX_SPI_SIZE, core->die->channel, request_length, request, response_length); +} + +static int knc_transfer_stamp(struct knc_state *knc) +{ + return knc->send_buffer_count; +} + +static int knc_transfer_completed(struct knc_state *knc, int stamp) +{ + /* signed delta math, counter wrap OK */ + return (int)(knc->read_buffer_count - stamp) >= 1; +} + +static bool knc_detect_one(void *ctx) +{ + /* Scan device for ASICs */ + int channel, die, cores = 0, core; + struct cgpu_info *cgpu; + struct knc_state *knc; + struct knc_die_info die_info[MAX_ASICS][DIES_PER_ASIC]; + + memset(die_info, 0, sizeof(die_info)); + + /* Send GETINFO to each die to detect if it is usable */ + for (channel = 0; channel < MAX_ASICS; channel++) { + if (!knc_trnsp_asic_detect(ctx, channel)) + continue; + for (die = 0; die < DIES_PER_ASIC; die++) { + if (knc_detect_die(ctx, channel, die, &die_info[channel][die]) == 0) + cores += die_info[channel][die].cores; + } + } + + if (!cores) { + applog(LOG_NOTICE, "no KnCminer cores found"); + return false; + } + + applog(LOG_ERR, "Found a KnC miner with %d cores", cores); + + cgpu = calloc(1, sizeof(*cgpu)); + knc = calloc(1, sizeof(*knc) + cores * sizeof(struct knc_core_state)); + if (!cgpu || !knc) { + applog(LOG_ERR, "KnC miner detected, but failed to allocate memory"); + return false; + } + + knc->cgpu = cgpu; + knc->ctx = ctx; + knc->generation = 1; + + /* Index all cores */ + int dies = 0; + cores = 0; + struct knc_core_state *pcore = knc->core; + for (channel = 0; channel < MAX_ASICS; channel++) { + for (die = 0; die < DIES_PER_ASIC; die++) { + if (die_info[channel][die].cores) { + knc->die[dies].channel = channel; + knc->die[dies].die = die; + knc->die[dies].version = die_info[channel][die].version; + knc->die[dies].cores = die_info[channel][die].cores; + knc->die[dies].core = pcore; + knc->die[dies].knc = knc; + for (core = 0; core < knc->die[dies].cores; core++) { + knc->die[dies].core[core].die = &knc->die[dies]; + knc->die[dies].core[core].core = core; + } + cores += knc->die[dies].cores; + pcore += knc->die[dies].cores; + dies++; + } + } + } + for (core = 0; core < cores; core++) + knc->core[core].coreid = core; + knc->dies = dies; + knc->cores = cores; + knc->startup = 2; + + cgpu->drv = &knc_drv; + cgpu->name = "KnCminer"; + cgpu->threads = 1; + + cgpu->device_data = knc; + + pthread_mutex_init(&knc->spi_qlock, NULL); + pthread_cond_init(&knc->spi_qcond, NULL); + if (thr_info_create(&knc->spi_thr, NULL, knc_spi, (void *)cgpu)) { + applog(LOG_ERR, "%s%i: SPI thread create failed", + cgpu->drv->name, cgpu->device_id); + free(cgpu); + free(knc); + return false; + } + + add_cgpu(cgpu); + + return true; +} + +/* Probe devices and register with add_cgpu */ +void knc_detect(bool __maybe_unused hotplug) +{ + void *ctx = knc_trnsp_new(opt_knc_device_idx); + + if (ctx != NULL) { + if (!knc_detect_one(ctx)) + knc_trnsp_free(ctx); + } +} + +/* Core helper functions */ +static int knc_core_hold_work(struct knc_core_state *core) +{ + return timercmp(&core->hold_work_until, &now, >); +} + +static int knc_core_has_work(struct knc_core_state *core) +{ + int i; + for (i = 0; i < WORKS_PER_CORE; i++) { + if (core->workslot[i].slot > 0) + return true; + } + return false; +} + +static int knc_core_need_work(struct knc_core_state *core) +{ + return !knc_core_hold_work(core) && !core->workslot[1].work && !core->workslot[2].work; +} + +static int knc_core_disabled(struct knc_core_state *core) +{ + return timercmp(&core->disabled_until, &now, >); +} + +static int _knc_core_next_slot(struct knc_core_state *core) +{ + /* Avoid slot #0 and #15. #0 is "no work assigned" and #15 is seen on bad cores */ + int slot = core->last_slot + 1; + if (slot >= 15) + slot = 1; + core->last_slot = slot; + return slot; +} + +static bool knc_core_slot_busy(struct knc_core_state *core, int slot) +{ + if (slot == core->report.active_slot) + return true; + if (slot == core->report.next_slot) + return true; + int i; + for (i = 0; i < WORKS_PER_CORE; i++) { + if (slot == core->workslot[i].slot) + return true; + } + return false; +} + +static int knc_core_next_slot(struct knc_core_state *core) +{ + int slot; + do slot = _knc_core_next_slot(core); + while (knc_core_slot_busy(core, slot)); + return slot; +} + +static void knc_core_failure(struct knc_core_state *core) +{ + core->errors++; + core->errors_now++; + core->die->knc->errors++; + if (knc_core_disabled(core)) + return; + if (core->errors_now > CORE_ERROR_LIMIT) { + applog(LOG_ERR, "KnC: %d.%d.%d disabled for %d seconds due to repeated hardware errors", + core->die->channel, core->die->die, core->core, core_disable_interval.tv_sec); + timeradd(&now, &core_disable_interval, &core->disabled_until); + } +} + +static int knc_core_handle_nonce(struct thr_info *thr, struct knc_core_state *core, int slot, uint32_t nonce) +{ + int i; + if (!slot) + return; + core->last_nonce.slot = slot; + core->last_nonce.nonce = nonce; + if (core->die->knc->startup) + return; + for (i = 0; i < WORKS_PER_CORE; i++) { + if (slot == core->workslot[i].slot && core->workslot[i].work) { + applog(LOG_INFO, "KnC: %d.%d.%d found nonce %08x", core->die->channel, core->die->die, core->core, nonce); + if (submit_nonce(thr, core->workslot[i].work, nonce)) { + /* Good share */ + core->shares++; + core->die->knc->shares++; + /* This core is useful. Ignore any errors */ + core->errors_now = 0; + } else { + applog(LOG_INFO, "KnC: %d.%d.%d hwerror nonce %08x", core->die->channel, core->die->die, core->core, nonce); + /* Bad share */ + knc_core_failure(core); + } + } + } +} + +static int knc_core_process_report(struct thr_info *thr, struct knc_core_state *core, uint8_t *response) +{ + struct knc_report *report = &core->report; + knc_decode_report(response, report, core->die->version); + bool had_event = false; + + applog(LOG_DEBUG, "KnC %d.%d.%d: Process report %d %d(%d) / %d %d %d", core->die->channel, core->die->die, core->core, report->active_slot, report->next_slot, report->next_state, core->workslot[0].slot, core->workslot[1].slot, core->workslot[2].slot); + int n; + for (n = 0; n < KNC_NONCES_PER_REPORT; n++) { + if (report->nonce[n].slot < 0) + break; + if (core->last_nonce.slot == report->nonce[n].slot && core->last_nonce.nonce == report->nonce[n].nonce) + break; + } + while(n-- > 0) { + knc_core_handle_nonce(thr, core, report->nonce[n].slot, report->nonce[n].nonce); + } + + if (report->active_slot && core->workslot[0].slot != report->active_slot) { + had_event = true; + applog(LOG_INFO, "KnC: New work on %d.%d.%d, %d %d / %d %d %d", core->die->channel, core->die->die, core->core, report->active_slot, report->next_slot, core->workslot[0].slot, core->workslot[1].slot, core->workslot[2].slot); + /* Core switched to next work */ + if (core->workslot[0].work) { + core->die->knc->completed++; + core->completed++; + applog(LOG_INFO, "KnC: Work completed on core %d.%d.%d!", core->die->channel, core->die->die, core->core); + free_work(core->workslot[0].work); + } + core->workslot[0] = core->workslot[1]; + core->workslot[1].work = NULL; + core->workslot[1].slot = -1; + + /* or did it switch directly to pending work? */ + if (report->active_slot == core->workslot[2].slot) { + applog(LOG_INFO, "KnC: New work on %d.%d.%d, %d %d %d %d (pending)", core->die->channel, core->die->die, core->core, report->active_slot, core->workslot[0].slot, core->workslot[1].slot, core->workslot[2].slot); + if (core->workslot[0].work) + free_work(core->workslot[0].work); + core->workslot[0] = core->workslot[2]; + core->workslot[2].work = NULL; + core->workslot[2].slot = -1; + } + } + + if (report->next_state && core->workslot[2].slot > 0 && (core->workslot[2].slot == report->next_slot || report->next_slot == -1)) { + had_event = true; + applog(LOG_INFO, "KnC: Accepted work on %d.%d.%d, %d %d %d %d (pending)", core->die->channel, core->die->die, core->core, report->active_slot, core->workslot[0].slot, core->workslot[1].slot, core->workslot[2].slot); + /* core accepted next work */ + if (core->workslot[1].work) + free_work(core->workslot[1].work); + core->workslot[1] = core->workslot[2]; + core->workslot[2].work = NULL; + core->workslot[2].slot = -1; + } + + if (core->workslot[2].work && knc_transfer_completed(core->die->knc, core->transfer_stamp)) { + had_event = true; + applog(LOG_INFO, "KnC: Setwork failed on core %d.%d.%d?", core->die->channel, core->die->die, core->core); + free_work(core->workslot[2].work); + core->workslot[2].slot = -1; + } + + if (had_event) + applog(LOG_INFO, "KnC: Exit report on %d.%d.%d, %d %d / %d %d %d", core->die->channel, core->die->die, core->core, report->active_slot, report->next_slot, core->workslot[0].slot, core->workslot[1].slot, core->workslot[2].slot); + + return 0; +} + +static void knc_process_responses(struct thr_info *thr) +{ + struct cgpu_info *cgpu = thr->cgpu; + struct knc_state *knc = cgpu->device_data; + struct knc_spi_buffer *buffer = &knc->spi_buffer[knc->read_buffer]; + while (buffer->state == KNC_SPI_DONE) { + int i; + for (i = 0; i < buffer->responses; i++) { + struct knc_spi_response *response_info = &buffer->response_info[i]; + uint8_t *rxbuf = &buffer->rxbuf[response_info->offset]; + struct knc_core_state *core = response_info->core; + int status = knc_decode_response(rxbuf, response_info->request_length, &rxbuf, response_info->response_length); + /* Invert KNC_ACCEPTED to simplify logics below */ + if (response_info->type == KNC_SETWORK && !KNC_IS_ERROR(status)) + status ^= KNC_ACCEPTED; + if (core->die->version != KNC_VERSION_JUPITER && status != 0) { + applog(LOG_ERR, "KnC %d.%d.%d: Communication error (%x / %d)", core->die->channel, core->die->die, core->core, status, i); + if (status == KNC_ACCEPTED) { + /* Core refused our work vector. Likely out of sync. Reset it */ + core->inuse = false; + } + knc_core_failure(core); + } + switch(response_info->type) { + case KNC_REPORT: + case KNC_SETWORK: + /* Should we care about failed SETWORK explicit? Or simply handle it by next state not loaded indication in reports? */ + knc_core_process_report(thr, core, rxbuf); + break; + } + } + + buffer->state = KNC_SPI_IDLE; + buffer->responses = 0; + buffer->size = 0; + knc->read_buffer += 1; + knc->read_buffer_count += 1; + if (knc->read_buffer >= KNC_SPI_BUFFERS) + knc->read_buffer = 0; + buffer = &knc->spi_buffer[knc->read_buffer]; + } +} + +static int knc_core_send_work(struct thr_info *thr, struct knc_core_state *core, struct work *work, bool clean) +{ + struct knc_state *knc = core->die->knc; + struct cgpu_info *cgpu = knc->cgpu; + int request_length = 4 + 1 + 6*4 + 3*4 + 8*4; + uint8_t request[request_length]; + int response_length = 1 + 1 + (1 + 4) * 5; + uint8_t response[response_length]; + + int slot = knc_core_next_slot(core); + if (slot < 0) + goto error; + + applog(LOG_INFO, "KnC setwork%s %d.%d.%d = %d, %d %d / %d %d %d", clean ? " CLEAN" : "", core->die->channel, core->die->die, core->core, slot, core->report.active_slot, core->report.next_slot, core->workslot[0].slot, core->workslot[1].slot, core->workslot[2].slot); + if (!clean && !knc_core_need_work(core)) + goto error; + + switch(core->die->version) { + case KNC_VERSION_JUPITER: + if (clean) { + /* Double halt to get rid of any previous queued work */ + request_length = knc_prepare_jupiter_halt(request, core->die->die, core->core); + knc_transfer(thr, core, request_length, request, 0, KNC_NO_RESPONSE, 0); + knc_transfer(thr, core, request_length, request, 0, KNC_NO_RESPONSE, 0); + } + request_length = knc_prepare_jupiter_setwork(request, core->die->die, core->core, slot, work); + knc_transfer(thr, core, request_length, request, 0, KNC_NO_RESPONSE, 0); + break; + case KNC_VERSION_NEPTUNE: + request_length = knc_prepare_neptune_setwork(request, core->die->die, core->core, slot, work, clean); + knc_transfer(thr, core, request_length, request, response_length, KNC_SETWORK, slot); + break; + default: + goto error; + } + + core->workslot[2].work = work; + core->workslot[2].slot = slot; + core->works++; + core->die->knc->works++; + core->transfer_stamp = knc_transfer_stamp(knc); + core->inuse = true; + + timeradd(&now, &core_submit_interval, &core->hold_work_until); + timeradd(&now, &core_timeout_interval, &core->timeout); + + return 0; + +error: + applog(LOG_INFO, "KnC: %d.%d.%d Failed to setwork (%d)", + core->die->channel, core->die->die, core->core, core->errors_now); + knc_core_failure(core); + free_work(work); + return -1; +} + +static int knc_core_request_report(struct thr_info *thr, struct knc_core_state *core) +{ + struct knc_state *knc = core->die->knc; + struct cgpu_info *cgpu = knc->cgpu; + int request_length = 4; + uint8_t request[request_length]; + int response_length = 1 + 1 + (1 + 4) * 5; + uint8_t response[response_length]; + + applog(LOG_DEBUG, "KnC: %d.%d.%d Request report", core->die->channel, core->die->die, core->core); + + request_length = knc_prepare_report(request, core->die->die, core->core); + + switch(core->die->version) { + case KNC_VERSION_JUPITER: + response_length = 1 + 1 + (1 + 4); + knc_transfer(thr, core, request_length, request, response_length, KNC_REPORT, 0); + return 0; + case KNC_VERSION_NEPTUNE: + knc_transfer(thr, core, request_length, request, response_length, KNC_REPORT, 0); + return 0; + } + +error: + applog(LOG_INFO, "KnC: Failed to scan work report"); + knc_core_failure(core); + return -1; +} + +/* return value is number of nonces that have been checked since + * previous call + */ +static int64_t knc_scanwork(struct thr_info *thr) +{ +#define KNC_COUNT_UNIT shares + struct cgpu_info *cgpu = thr->cgpu; + struct knc_state *knc = cgpu->device_data; + int64_t ret = 0; + uint32_t last_count = knc->KNC_COUNT_UNIT; + + applog(LOG_DEBUG, "KnC running scanwork"); + + gettimeofday(&now, NULL); + + knc_trnsp_periodic_check(knc->ctx); + + int i; + + knc_process_responses(thr); + + if (timercmp(&knc->next_error_interval, &now, >)) { + /* Reset hw error limiter every check interval */ + timeradd(&now, &core_check_interval, &knc->next_error_interval); + for (i = 0; i < knc->cores; i++) { + struct knc_core_state *core = &knc->core[i]; + core->errors_now = 0; + } + } + + for (i = 0; i < knc->cores; i++) { + struct knc_core_state *core = &knc->core[i]; + bool clean = !core->inuse; + if (knc_core_disabled(core)) + continue; + if (core->generation != knc->generation) { + applog(LOG_INFO, "KnC %d.%d.%d flush gen=%d/%d", core->die->channel, core->die->die, core->core, core->generation, knc->generation); + /* clean set state, forget everything */ + int slot; + for (slot = 0; slot < WORKS_PER_CORE; slot ++) { + if (core->workslot[slot].work) + free_work(core->workslot[slot].work); + core->workslot[slot].slot = -1; + } + core->hold_work_until = now; + core->generation = knc->generation; + } else if (timercmp(&core->timeout, &now, <=) && (core->workslot[0].slot > 0 || core->workslot[1].slot > 0 || core->workslot[2].slot > 0)) { + applog(LOG_ERR, "KnC %d.%d.%d timeout", core->die->channel, core->die->die, core->core, core->generation, knc->generation); + clean = true; + } + if (!knc_core_has_work(core)) + clean = true; + if (core->workslot[0].slot < 0 && core->workslot[1].slot < 0 && core->workslot[2].slot < 0) + clean = true; + if (i % SCAN_ADJUST_RANGE == knc->scan_adjust) + clean = true; + if ((knc_core_need_work(core) || clean) && !knc->startup) { + struct work *work = get_work(thr, thr->id); + knc_core_send_work(thr, core, work, clean); + } else { + knc_core_request_report(thr, core); + } + } + /* knc->startup delays initial work submission until we have had chance to query all cores on their current status, to avoid slot number collisions with earlier run */ + if (knc->startup) + knc->startup--; + else if (knc->scan_adjust < SCAN_ADJUST_RANGE) + knc->scan_adjust++; + + knc_flush(thr); + + return (int64_t)(knc->KNC_COUNT_UNIT - last_count) * 0x100000000UL; +} + +static void knc_flush_work(struct cgpu_info *cgpu) +{ + struct knc_state *knc = cgpu->device_data; + + applog(LOG_INFO, "KnC running flushwork"); + + knc->generation++; + knc->scan_adjust=0; + if (!knc->generation) + knc->generation++; +} + +static void knc_zero_stats(struct cgpu_info *cgpu) +{ + int core; + struct knc_state *knc = cgpu->device_data; + for (core = 0; core < knc->cores; core++) { + knc->shares = 0; + knc->completed = 0; + knc->works = 0; + knc->errors = 0; + knc->core[core].works = 0; + knc->core[core].errors = 0; + knc->core[core].shares = 0; + knc->core[core].completed = 0; + } +} + +static struct api_data *knc_api_stats(struct cgpu_info *cgpu) +{ + struct knc_state *knc = cgpu->device_data; + struct api_data *root = NULL; + unsigned int cursize; + int asic, core, n; + char label[256]; + + root = api_add_int(root, "dies", &knc->dies, 1); + root = api_add_int(root, "cores", &knc->cores, 1); + root = api_add_uint64(root, "shares", &knc->shares, 1); + root = api_add_uint64(root, "works", &knc->works, 1); + root = api_add_uint64(root, "completed", &knc->completed, 1); + root = api_add_uint64(root, "errors", &knc->errors, 1); + + /* Active cores */ + int active = knc->cores; + for (core = 0; core < knc->cores; core++) { + if (knc_core_disabled(&knc->core[core])) + active -= 1; + } + root = api_add_int(root, "active", &active, 1); + + /* Per ASIC/die data */ + for (n = 0; n < knc->dies; n++) { + struct knc_die *die = &knc->die[n]; + +#define knc_api_die_string(name, value) do { \ + snprintf(label, sizeof(label), "%d.%d.%s", die->channel, die->die, name); \ + root = api_add_string(root, label, value, 1); \ + } while(0) +#define knc_api_die_int(name, value) do { \ + snprintf(label, sizeof(label), "%d.%d.%s", die->channel, die->die, name); \ + uint64_t v = value; \ + root = api_add_uint64(root, label, &v, 1); \ + } while(0) + + /* Model */ + { + char *model = "?"; + switch(die->version) { + case KNC_VERSION_JUPITER: + model = "Jupiter"; + break; + case KNC_VERSION_NEPTUNE: + model = "Neptune"; + break; + } + knc_api_die_string("model", model); + knc_api_die_int("cores", die->cores); + } + + /* Core based stats */ + { + int active = 0; + uint64_t errors = 0; + uint64_t shares = 0; + uint64_t works = 0; + uint64_t completed = 0; + char coremap[die->cores+1]; + + for (core = 0; core < die->cores; core++) { + coremap[core] = knc_core_disabled(&die->core[core]) ? '0' : '1'; + works += die->core[core].works; + shares += die->core[core].shares; + errors += die->core[core].errors; + completed += die->core[core].completed; + } + coremap[die->cores] = '\0'; + knc_api_die_int("errors", errors); + knc_api_die_int("shares", shares); + knc_api_die_int("works", works); + knc_api_die_int("completed", completed); + knc_api_die_string("coremap", coremap); + } + } + + return root; +} + +struct device_drv knc_drv = { + .drv_id = DRIVER_knc, + .dname = "KnCminer Neptune", + .name = "KnC", + .drv_detect = knc_detect, + .hash_work = hash_driver_work, + .flush_work = knc_flush_work, + .scanwork = knc_scanwork, + .zero_stats = knc_zero_stats, + .get_api_stats = knc_api_stats, +}; diff --git a/driver-minion.c b/driver-minion.c new file mode 100644 index 0000000..52e3442 --- /dev/null +++ b/driver-minion.c @@ -0,0 +1,5380 @@ +/* + * Copyright 2013-2014 Andrew Smith - BlackArrow Ltd + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#include "config.h" +#include "compat.h" +#include "miner.h" +#include "klist.h" +#include +#include + +#ifndef LINUX +static void minion_detect(__maybe_unused bool hotplug) +{ +} +#else + +#include +#include +#include +#include +#include +#include +#include + +// Define this to 1 to enable interrupt code and enable no_nonce +#define ENABLE_INT_NONO 0 + +// Define this to 1 if compiling on RockChip and not on RPi +#define MINION_ROCKCHIP 0 + +// The code is always in - this just decides if it does it +static bool minreread = false; + +#if MINION_ROCKCHIP == 1 +#define MINION_POWERCYCLE_GPIO 173 +#define MINION_CHIP_OFF "1" +#define MINION_CHIP_ON "0" +#define MINION_CHIP_DELAY 100 +#endif + +// Power cycle if the xff_list is full and the tail is less than +// this long ago +#define MINION_POWER_TIME 60 + +/* + * Use pins for board selection + * If disabled, it will test chips just as 'pin 0' + * but never do any gpio - the equivalent of the previous 'no pins' code + */ +static bool usepins = false; + +#define MINION_PAGE_SIZE 4096 + +#define BCM2835_BASE 0x20000000 +#define BCM2835_GPIO_BASE (BCM2835_BASE + 0x200000) + +#define BCM2835_GPIO_SET0 0x001c // GPIO Pin Output Set 0 +#define BCM2835_GPIO_CLR0 0x0028 // GPIO Pin Output Clear 0 + +#define BCM2835_GPIO_FSEL0 0x0000 + +#define BCM2835_GPIO_FSEL_INPUT 0b000 +#define BCM2835_GPIO_FSEL_OUTPUT 0b001 +#define BCM2835_GPIO_FSEL_MASK 0b111 + +#define BCM2835_PIN_HIGH 0x1 +#define BCM2835_PIN_LOW 0x0 + +static const char *minion_memory = "/dev/mem"; +static int minion_memory_addr = BCM2835_GPIO_BASE; + +#define MINION_SPI_BUS 0 +#define MINION_SPI_CHIP 0 + +#if MINION_ROCKCHIP == 0 +#define MINION_SPI_SPEED 8000000 +#else +#define MINION_SPI_SPEED 500000 +#endif +#define MINION_SPI_BUFSIZ 1024 + +static struct minion_select_pins { + int pin; + int wpi; + char *name; + int bcm; // this is what we use +} minionPins[] = { + { 24, 10, "CE0", 8, }, + { 26, 11, "CE1", 7, }, + { 16, 4, "GPIO4", 23, }, + { 22, 6, "GPIO6", 25, }, + { 12, 1, "GPIO1", 18, }, + { 18, 5, "GPIO5", 24, }, + { 11, 0, "GPIO0", 17, }, + { 13, 2, "GPIO2", 27, }, + { 15, 3, "GPIO3", 22, }, + { 7, 7, "GPIO7", 4, } + +/* The rest on the RPi + { 3, 8, "SDA", 2, } + { 5, 9, "SCL", 3, } + { 19, 12, "MOSI", 10, } + { 21, 13, "MISO", 9, } + { 23, 14, "SCLK", 11, } + { 8, 15, "TxD", 14, } + { 10, 16, "RxD", 15, } +*/ +}; + +/* + * uS delays for GPIO pin access + */ +#define MINION_PIN_BEFORE cgsleep_us(33) +#define MINION_PIN_SLEEP cgsleep_us(133) +#define MINION_PIN_AFTER + +#define MINION_PIN_COUNT (sizeof(minionPins)/ \ + sizeof(struct minion_select_pins)) + +#define CHIP_PIN(_chip) (minioninfo->chip_pin[_chip]) + +#define MINION_MIN_CHIP 0 +#define MINION_MAX_CHIP 11 + +#define MINION_CHIP_PER_PIN (1 + MINION_MAX_CHIP - MINION_MIN_CHIP) + +#define MINION_CHIPS (MINION_PIN_COUNT * MINION_CHIP_PER_PIN) +#define MINION_CORES 99 +#define FAKE_CORE MINION_CORES + +/* + * TODO: These will need adjusting for final hardware + * Look them up and calculate them? + */ +#define MINION_QUE_MAX 64 +#define MINION_QUE_HIGH 48 +#define MINION_QUE_SEND 16 +#define MINION_QUE_LOW 8 + +#define MINION_FFL " - from %s %s() line %d" +#define MINION_FFL_HERE __FILE__, __func__, __LINE__ +#define MINION_FFL_PASS file, func, line +#define MINION_FFL_ARGS __maybe_unused const char *file, \ + __maybe_unused const char *func, \ + __maybe_unused const int line + +#define minion_txrx(_task) _minion_txrx(minioncgpu, minioninfo, _task, MINION_FFL_HERE) + +#define MINION_SYS_REGS 0x00 +#define MINION_CORE_REGS 0x10 +#define MINION_RES_BUF 0x20 +#define MINION_CMD_QUE 0x30 +#define MINION_NONCE_RANGES 0x70 + +#define DATA_SIZ (sizeof(uint32_t)) + +// All SYS data sizes are DATA_SIZ +#define MINION_SYS_CHIP_SIG 0x00 +#define MINION_SYS_CHIP_STA 0x01 +#define MINION_SYS_SPI_LED 0x02 +#define MINION_SYS_TEMP_CTL 0x03 +#define MINION_SYS_FREQ_CTL 0x04 +#define MINION_SYS_NONCE_LED 0x05 +#define MINION_SYS_MISC_CTL 0x06 +#define MINION_SYS_RSTN_CTL 0x07 +#define MINION_SYS_INT_ENA 0x08 +#define MINION_SYS_INT_CLR 0x09 +#define MINION_SYS_INT_STA 0x0a +#define MINION_SYS_FIFO_STA 0x0b +#define MINION_SYS_QUE_TRIG 0x0c +#define MINION_SYS_BUF_TRIG 0x0d +#define MINION_SYS_IDLE_CNT 0x0e + +// How many 32 bit reports make up all the cores - 99 cores = 4 reps +#define MINION_CORE_REPS (int)((((MINION_CORES-1) >> 5) & 0xff) + 1) + +// All SYS data sizes are DATA_SIZ +#define MINION_SYS_SIZ DATA_SIZ + +// Header Pin 18 = GPIO5 = BCM 24 +#define MINION_GPIO_RESULT_INT_PIN 24 +// RockChip is pin 172 ... + +#define MINION_GPIO_SYS "/sys/class/gpio" +#define MINION_GPIO_ENA "/export" +#define MINION_GPIO_ENA_VAL "%d" +#define MINION_GPIO_DIS "/unexport" +#define MINION_GPIO_PIN "/gpio%d" +#define MINION_GPIO_DIR "/direction" +#define MINION_GPIO_DIR_READ "in" +#define MINION_GPIO_DIR_WRITE "out" +#define MINION_GPIO_EDGE "/edge" +#define MINION_GPIO_EDGE_NONE "none" +#define MINION_GPIO_EDGE_RISING "rising" +#define MINION_GPIO_EDGE_FALLING "falling" +#define MINION_GPIO_EDGE_BOTH "both" +#define MINION_GPIO_ACT "/active_low" +#define MINION_GPIO_ACT_LO "1" +#define MINION_GPIO_ACT_HI "0" +#define MINION_GPIO_VALUE "/value" + +#define MINION_RESULT_INT 0x01 +#define MINION_RESULT_FULL_INT 0x02 +#define MINION_CMD_INT 0x04 +#define MINION_CMD_FULL_INT 0x08 +#define MINION_TEMP_LOW_INT 0x10 +#define MINION_TEMP_HI_INT 0x20 +#define MINION_ALL_INT MINION_RESULT_INT | \ + MINION_RESULT_FULL_INT | \ + MINION_CMD_INT | \ + MINION_CMD_FULL_INT | \ + MINION_TEMP_LOW_INT | \ + MINION_TEMP_HI_INT + +#define RSTN_CTL_RESET_CORES 0x01 +#define RSTN_CTL_FLUSH_RESULTS 0x02 +#define RSTN_CTL_FLUSH_CMD_QUEUE 0x04 +#define RSTN_CTL_SPI_SW_RSTN 0x08 +#define RSTN_CTL_SHA_MGR_RESET 0x10 + +// Init +#define SYS_RSTN_CTL_INIT (RSTN_CTL_RESET_CORES | \ + RSTN_CTL_FLUSH_RESULTS | \ + RSTN_CTL_FLUSH_CMD_QUEUE | \ + RSTN_CTL_SPI_SW_RSTN | \ + RSTN_CTL_SHA_MGR_RESET) + +// Block change +#define SYS_RSTN_CTL_FLUSH (RSTN_CTL_RESET_CORES | \ + RSTN_CTL_SPI_SW_RSTN | \ + RSTN_CTL_FLUSH_CMD_QUEUE) + +#if ENABLE_INT_NONO +// enable 'no nonce' report +#define SYS_MISC_CTL_DEFAULT 0x04 +#else +#define SYS_MISC_CTL_DEFAULT 0x00 +#endif + +// Temperature returned by MINION_SYS_CHIP_STA 0x01 STA_TEMP() +#define MINION_TEMP_40 0 +#define MINION_TEMP_60 1 +#define MINION_TEMP_80 3 +#define MINION_TEMP_100 7 +#define MINION_TEMP_OVER 15 + +static const char *min_temp_40 = "<40"; +static const char *min_temp_60 = "40-60"; +static const char *min_temp_80 = "60-80"; +static const char *min_temp_100 = "80-100"; +static const char *min_temp_over = ">100"; +static const char *min_temp_invalid = "?"; + +/* + * Temperature for MINION_SYS_TEMP_CTL 0x03 temp_thres [0:3] + * i.e. it starts at 120 and goes up in steps of 5 to 160 + */ +#define MINION_TEMP_CTL_MIN 1 +#define MINION_TEMP_CTL_MAX 9 +#define MINION_TEMP_CTL_BITS 0x0f +#define MINION_TEMP_CTL_DEF 135 +#define MINION_TEMP_CTL_STEP 5 +#define MINION_TEMP_CTL_MIN_VALUE 120 +#define MINION_TEMP_CTL_MAX_VALUE (MINION_TEMP_CTL_MIN_VALUE + \ + (MINION_TEMP_CTL_STEP * \ + (MINION_TEMP_CTL_MAX - MINION_TEMP_CTL_MIN))) +#define MINION_TEMP_DISABLE "disable" +#define MINION_TEMP_CTL_DISABLE -1 +#define MINION_TEMP_CTL_DISABLE_VALUE 0x20 + +// CORE data size is DATA_SIZ +#define MINION_CORE_ENA0_31 0x10 +#define MINION_CORE_ENA32_63 0x11 +#define MINION_CORE_ENA64_95 0x12 +#define MINION_CORE_ENA96_98 0x13 +#define MINION_CORE_ACT0_31 0x14 +#define MINION_CORE_ACT32_63 0x15 +#define MINION_CORE_ACT64_95 0x16 +#define MINION_CORE_ACT96_98 0x17 + +// All CORE data sizes are DATA_SIZ +#define MINION_CORE_SIZ DATA_SIZ + +#define MINION_CORE_ALL "all" + +// RES data size is minion_result +#define MINION_RES_DATA 0x20 +#define MINION_RES_PEEK 0x21 + +// QUE data size is minion_que +#define MINION_QUE_0 0x30 +#define MINION_QUE_R 0x31 + +// RANGE data sizes are DATA_SIZ +#define MINION_NONCE_START 0x70 +#define MINION_NONCE_RANGE 0x71 + +// This must be >= max txsiz + max rxsiz +#define MINION_BUFSIZ 1024 + +#define u8tou32(_c, _off) (((uint8_t *)(_c))[(_off)+0] + \ + ((uint8_t *)(_c))[(_off)+1] * 0x100 + \ + ((uint8_t *)(_c))[(_off)+2] * 0x10000 + \ + ((uint8_t *)(_c))[(_off)+3] * 0x1000000 ) + +#define MINION_ADDR_WRITE 0x7f +#define MINION_ADDR_READ 0x80 + +#define READ_ADDR(_reg) ((_reg) | MINION_ADDR_READ) +#define WRITE_ADDR(_reg) ((_reg) & MINION_ADDR_WRITE) + +#define IS_ADDR_READ(_reg) (((_reg) & MINION_ADDR_READ) == MINION_ADDR_READ) +#define IS_ADDR_WRITE(_reg) (((_reg) & MINION_ADDR_READ) == 0) + +#define SET_HEAD_WRITE(_h, _reg) ((_h)->reg) = WRITE_ADDR(_reg) +#define SET_HEAD_READ(_h, _reg) ((_h)->reg) = READ_ADDR(_reg) +#define SET_HEAD_SIZ(_h, _siz) \ + do { \ + ((_h)->siz)[0] = (uint8_t)((_siz) & 0xff); \ + ((_h)->siz)[1] = (uint8_t)(((_siz) & 0xff00) >> 8); \ + } while (0) + +struct minion_header { + uint8_t chipid; + uint8_t reg; + uint8_t siz[2]; + uint8_t data[4]; // placeholder +}; + +#define HSIZE() (sizeof(struct minion_header) - 4) + +#define MINION_NOCHIP_SIG 0x00000000 +#define MINION_NOCHIP_SIG2 0xffffffff +#define MINION_CHIP_SIG 0xb1ac8a44 + +/* + * Number of times to try and get the SIG with each chip, + * if the chip returns neither of the above values + * TODO: maybe need some reset between tries, to handle a shift value? + */ +#define MINION_SIG_TRIES 3 + +/* + * TODO: Finding these means the chip is there - but how to fix it? + * The extra &'s are to ensure there is no sign bit issue since + * the sign bit carry in a C bit-shift is compiler dependent + */ +#define MINION_CHIP_SIG_SHIFT1 (((MINION_CHIP_SIG & 0x0000ffff) << 16) & 0xffff0000) +#define MINION_CHIP_SIG_SHIFT2 (((MINION_CHIP_SIG & 0x00ffffff) << 8) & 0xffffff00) +#define MINION_CHIP_SIG_SHIFT3 (((MINION_CHIP_SIG & 0xffffff00) >> 8) & 0x00ffffff) +#define MINION_CHIP_SIG_SHIFT4 (((MINION_CHIP_SIG & 0xffff0000) >> 16) & 0x0000ffff) + +#define MINION_SPI_LED_ON 0xa5a5 +#define MINION_SPI_LED_OFF 0x0 + +// Time since first nonce/last reset before turning on the LED +#define MINION_LED_TEST_TIME 600 + +#define MINION_FREQ_MIN 100 +#define MINION_FREQ_DEF 1200 +#define MINION_FREQ_MAX 1400 +#define MINION_FREQ_FACTOR 100 +#define MINION_FREQ_RESET_STEP MINION_FREQ_FACTOR +#define MINION_FREQ_FACTOR_MIN 1 +#define MINION_FREQ_FACTOR_MAX 14 + +static uint32_t minion_freq[] = { + 0x0, + 0x205032, // 1 = 100Mhz + 0x203042, // 2 = 200Mhz + 0x20204B, // 3 = 300Mhz + 0x201042, // 4 = 400Mhz + 0x201053, // 5 = 500Mhz + 0x200032, // 6 = 600Mhz + 0x20003A, // 7 = 700Mhz + 0x200042, // 8 = 800Mhz + 0x20004B, // 9 = 900Mhz + 0x200053, // 10 = 1000Mhz + 0x21005B, // 11 = 1100Mhz + 0x210064, // 12 = 1200Mhz + 0x21006C, // 13 = 1300Mhz + 0x210074 // 14 = 1400Mhz +}; + +// When hash rate falls below this in the history hash rate, reset it +#define MINION_RESET_PERCENT 75.0 +// When hash rate falls below this after the longer test time +#define MINION_RESET2_PERCENT 85.0 + +// After the above resets, delay sending work for: +#define MINION_RESET_DELAY_s 0.088 + +#define STA_TEMP(_sta) ((uint16_t)((_sta)[3] & 0x1f)) +#define STA_CORES(_sta) ((uint16_t)((_sta)[2])) +#define STA_FREQ(_sta) ((uint32_t)((_sta)[1]) * 0x100 + (uint32_t)((_sta)[0])) + +// Randomly between 1s and 2s per chip +#define MINION_STATS_UPDATE_TIME_mS 1000 +#define MINION_STATS_UPDATE_RAND_mS 1000 + +// Don't report it more than once every ... 5s +#define MINION_IDLE_MESSAGE_ms 5000 + +struct minion_status { + uint16_t temp; + uint16_t cores; + uint32_t freq; + uint32_t quework; + uint32_t chipwork; + uint32_t realwork; // FIFO_STA + struct timeval last; + bool overheat; + bool islow; + bool tohigh; + int lowcount; + uint32_t overheats; + struct timeval lastoverheat; + struct timeval lastrecover; + double overheattime; + uint32_t tempsent; + uint32_t idle; + uint32_t last_rpt_idle; + struct timeval idle_rpt; + struct timeval first_nonce; + uint64_t from_first_good; +}; + +#define ENABLE_CORE(_core, _n) ((_core[_n >> 3]) |= (1 << (_n % 8))) +#define CORE_IDLE(_core, _n) ((_core[_n >> 3]) & (1 << (_n % 8))) + +#define FIFO_RES(_fifo, _off) ((_fifo)[(_off) + 0]) +#define FIFO_CMD(_fifo, _off) ((_fifo)[(_off) + 1]) + +#define RES_GOLD(_res) ((((_res)->status[3]) & 0x80) == 0) +#define RES_CHIPID(_res) (((_res)->status[3]) & 0x1f) +#define RES_CORE(_res) ((_res)->status[2]) +#define RES_TASK(_res) ((int)((_res)->status[1]) * 0x100 + (int)((_res)->status[0])) +#define RES_NONCE(_res) u8tou32((_res)->nonce, 0) + +/* + * This is only valid since we avoid using task_id 0 for work + * However, it isn't really necessary since we only request + * the number of results the result buffer says it has + * However, it is a simple failsafe + */ +#define IS_RESULT(_res) ((_res)->status[1] || (_res)->status[0]) + +struct minion_result { + uint8_t status[DATA_SIZ]; + uint8_t nonce[DATA_SIZ]; +}; + +#define MINION_RES_DATA_SIZ sizeof(struct minion_result) + +/* + * (MINION_SPI_BUFSIZ - HSIZE()) / MINION_RES_DATA_SIZ + * less a little bit to round it out + */ +#define MINION_MAX_RES 120 + +#define MIDSTATE_BYTES 32 +#define MERKLE7_OFFSET 64 +#define MERKLE_BYTES 12 + +#define MINION_MAX_TASK_ID 0xffff + +struct minion_que { + uint8_t task_id[2]; + uint8_t reserved[2]; + uint8_t midstate[MIDSTATE_BYTES]; + uint8_t merkle7[DATA_SIZ]; + uint8_t ntime[DATA_SIZ]; + uint8_t bits[DATA_SIZ]; +}; + +/* + * Max time to wait before checking the task list + * Required, since only urgent tasks trigger an immediate check + * TODO: ? for 2TH/s + */ +#define MINION_TASK_mS 8 + +/* + * Max time to wait before checking the result list for nonces + * This can be long since it's only a failsafe + * cgsem_post is always sent if there are nonces ready to check + */ +#define MINION_NONCE_mS 888 + +// Number of results to make a GPIO interrupt +//#define MINION_RESULT_INT_SIZE 1 +#define MINION_RESULT_INT_SIZE 2 + +/* + * Max time to wait before checking for results + * The interrupt doesn't occur until MINION_RESULT_INT_SIZE results are found + * See comment in minion_spi_reply() at poll() + */ +#define MINION_REPLY_mS 88 + +/* + * Max time to wait before returning the amount of work done + * A result interrupt will send a trigger for this also + * See comment in minion_scanwork() + * This avoids the cgminer master work loop spinning doing nothing + */ +#define MINION_SCAN_mS 88 + +// *** Work lists: generated, queued for a chip, sent to chip +typedef struct work_item { + struct work *work; + uint32_t task_id; + struct timeval sent; + int nonces; + bool urgent; + bool stale; // if stale, don't decrement que/chipwork when discarded + bool rolled; + int errors; // uncertain since the error could mean task_id is wrong + struct timeval created; // when work was generated + uint64_t ioseq; +} WORK_ITEM; + +#define ALLOC_WORK_ITEMS 4096 +#define LIMIT_WORK_ITEMS 0 + +// *** Task queue ready to be sent +typedef struct task_item { + uint64_t tid; + uint8_t chip; + bool write; + uint8_t address; + uint32_t task_id; + uint32_t wsiz; + uint32_t osiz; + uint32_t rsiz; + uint8_t wbuf[MINION_BUFSIZ]; + uint8_t obuf[MINION_BUFSIZ]; + uint8_t rbuf[MINION_BUFSIZ]; + int reply; + bool urgent; + uint8_t work_state; + struct work *work; + K_ITEM *witem; + uint64_t ioseq; +} TASK_ITEM; + +#define ALLOC_TASK_ITEMS 256 +#define LIMIT_TASK_ITEMS 0 + +// *** Results queue ready to be checked +typedef struct res_item { + int chip; + int core; + uint32_t task_id; + uint32_t nonce; + struct timeval when; + /* + * Only once per task_id if no nonces were found + * Sent with core = 0 + * However, currently it always sends it at the end of every task + * TODO: code assumes it doesn't - change later when we + * see what the final hardware does (minor code performance gain) + */ + bool no_nonce; + // If we requested the result twice: + bool another; + uint32_t task_id2; + uint32_t nonce2; +} RES_ITEM; + +#define ALLOC_RES_ITEMS 256 +#define LIMIT_RES_ITEMS 0 + +// *** Per chip nonce history +typedef struct hist_item { + struct timeval when; +} HIST_ITEM; + +#define ALLOC_HIST_ITEMS 4096 +#define LIMIT_HIST_ITEMS 0 + +// How much history to keep (5min) +#define MINION_HISTORY_s 300 +// History required to decide a reset at MINION_FREQ_DEF Mhz +#define MINION_RESET_s 10 +// How many times to reset before changing Freq +// This doesn't include the secondary higher % check +#define MINION_RESET_COUNT 6 + +// To enable the 2nd check +static bool second_check = true; +// Longer time lapse to expect the higher % +// This intercepts a slow GHs drop earlier +#define MINION_RESET2_s 60 + +#if (MINION_RESET_s > MINION_HISTORY_s) +#error "MINION_RESET_s can't be greater than MINION_HISTORY_s" +#endif + +#define FREQ_DELAY(freq) ((float)(MINION_RESET_s * MINION_FREQ_DEF) / (freq)) + +#if (MINION_RESET2_s > MINION_HISTORY_s) +#error "MINION_RESET2_s can't be greater than MINION_HISTORY_s" +#endif + +// FREQ2_DELAY(MINION_FREQ_MIN) = FREQ2_FACTOR * MINION_RESET2_s +#define FREQ2_FACTOR 1.5 + +#define FREQ2_DELAY(freq) ((1.0 + (float)((freq - MINION_FREQ_DEF) * (1 - FREQ2_FACTOR)) / \ + (float)(MINION_FREQ_DEF - MINION_FREQ_MIN)) * MINION_RESET2_s) + +#if (MINION_RESET2_s <= MINION_RESET_s) +#error "MINION_RESET2_s must be greater than MINION_RESET_s" +#endif + +/* If there was no reset for this long, clear the reset history + * (except the last one) since this means the current clock is ok + * with rare resets */ +#define MINION_CLR_s 300 + +#if (MINION_CLR_s <= MINION_RESET2_s) +#error "MINION_CLR_s must be greater than MINION_RESET2_s" +#endif + +// History must be always generated for the reset check +#define MINION_MAX_RESET_CHECK 2 + +/* Floating point reset settings required for the code to work properly + * Basically: RESET2 must be after RESET and CLR must be after RESET2 */ +static void define_test() +{ + float test; + + if (MINION_RESET2_PERCENT <= MINION_RESET_PERCENT) { + quithere(1, "MINION_RESET2_PERCENT=%f must be " + "> MINION_RESET_PERCENT=%f", + MINION_RESET2_PERCENT, MINION_RESET_PERCENT); + } + + test = FREQ_DELAY(MINION_FREQ_MIN); + if (test >= MINION_HISTORY_s) { + quithere(1, "FREQ_DELAY(MINION_FREQ_MIN)=%f must be " + "< MINION_HISTORY_s=%d", + test, MINION_HISTORY_s); + } + + if (MINION_CLR_s <= test) { + quithere(1, "MINION_CLR_s=%d must be > " + "FREQ_DELAY(MINION_FREQ_MIN)=%f", + MINION_CLR_s, test); + } + + if (FREQ2_FACTOR <= 1.0) + quithere(1, "FREQ2_FACTOR=%f must be > 1.0", FREQ2_FACTOR); + + + test = FREQ2_DELAY(MINION_FREQ_MIN); + if (test >= MINION_HISTORY_s) { + quithere(1, "FREQ2_DELAY(MINION_FREQ_MIN)=%f must be " + "< MINION_HISTORY_s=%d", + test, MINION_HISTORY_s); + } + + if (MINION_CLR_s <= test) { + quithere(1, "MINION_CLR_s=%d must be > " + "FREQ2_DELAY(MINION_FREQ_MIN)=%f", + MINION_CLR_s, test); + } +} + +// *** Chip freq/MHs performance history +typedef struct perf_item { + double elapsed; + uint64_t nonces; + uint32_t freq; + double ghs; + struct timeval when; +} PERF_ITEM; + +#define ALLOC_PERF_ITEMS 128 +#define LIMIT_PERF_ITEMS 0 + +// *** 0xff error history +typedef struct xff_item { + time_t when; +} XFF_ITEM; + +#define ALLOC_XFF_ITEMS 100 +#define LIMIT_XFF_ITEMS 100 + +#define DATA_WORK(_item) ((WORK_ITEM *)(_item->data)) +#define DATA_TASK(_item) ((TASK_ITEM *)(_item->data)) +#define DATA_RES(_item) ((RES_ITEM *)(_item->data)) +#define DATA_HIST(_item) ((HIST_ITEM *)(_item->data)) +#define DATA_PERF(_item) ((PERF_ITEM *)(_item->data)) +#define DATA_XFF(_item) ((XFF_ITEM *)(_item->data)) + +// Set this to 1 to enable iostats processing +// N.B. it slows down mining +#define DO_IO_STATS 0 + +#if DO_IO_STATS +#define IO_STAT_NOW(_tv) cgtime(_tv) +#define IO_STAT_STORE(_sta, _fin, _lsta, _lfin, _tsd, _buf, _siz, _reply, _ioc) \ + do { \ + double _diff, _ldiff, _lwdiff, _1time; \ + int _off; \ + _diff = us_tdiff(_fin, _sta); \ + _ldiff = us_tdiff(_lfin, _lsta); \ + _lwdiff = us_tdiff(_sta, _lsta); \ + _1time = us_tdiff(_tsd, _lfin); \ + _off = (int)(_buf[1]) + (_reply >= 0 ? 0 : 0x100); \ + minioninfo->summary.count++; \ + minioninfo->summary.tsd += _1time; \ + minioninfo->iostats[_off].count++; \ + minioninfo->iostats[_off].tsd += _1time; \ + if (_diff <= 0) { \ + minioninfo->summary.zero_delay++; \ + minioninfo->iostats[_off].zero_delay++; \ + } else { \ + minioninfo->summary.total_delay += _diff; \ + if (minioninfo->summary.max_delay < _diff) \ + minioninfo->summary.max_delay = _diff; \ + if (minioninfo->summary.min_delay == 0 || \ + minioninfo->summary.min_delay > _diff) \ + minioninfo->summary.min_delay = _diff; \ + minioninfo->iostats[_off].total_delay += _diff; \ + if (minioninfo->iostats[_off].max_delay < _diff) \ + minioninfo->iostats[_off].max_delay = _diff; \ + if (minioninfo->iostats[_off].min_delay == 0 || \ + minioninfo->iostats[_off].min_delay > _diff) \ + minioninfo->iostats[_off].min_delay = _diff; \ + } \ + if (_ldiff <= 0) { \ + minioninfo->summary.zero_dlock++; \ + minioninfo->iostats[_off].zero_dlock++; \ + } else { \ + minioninfo->summary.total_dlock += _ldiff; \ + if (minioninfo->summary.max_dlock < _ldiff) \ + minioninfo->summary.max_dlock = _ldiff; \ + if (minioninfo->summary.min_dlock == 0 || \ + minioninfo->summary.min_dlock > _ldiff) \ + minioninfo->summary.min_dlock = _ldiff; \ + minioninfo->iostats[_off].total_dlock += _ldiff; \ + if (minioninfo->iostats[_off].max_dlock < _ldiff) \ + minioninfo->iostats[_off].max_dlock = _ldiff; \ + if (minioninfo->iostats[_off].min_dlock == 0 || \ + minioninfo->iostats[_off].min_dlock > _ldiff) \ + minioninfo->iostats[_off].min_dlock = _ldiff; \ + } \ + minioninfo->summary.total_dlwait += _lwdiff; \ + minioninfo->iostats[_off].total_dlwait += _lwdiff; \ + if (_siz == 0) { \ + minioninfo->summary.zero_bytes++; \ + minioninfo->iostats[_off].zero_bytes++; \ + } else { \ + minioninfo->summary.total_bytes += _siz; \ + if (minioninfo->summary.max_bytes < _siz) \ + minioninfo->summary.max_bytes = _siz; \ + if (minioninfo->summary.min_bytes == 0 || \ + minioninfo->summary.min_bytes > _siz) \ + minioninfo->summary.min_bytes = _siz; \ + minioninfo->iostats[_off].total_bytes += _siz; \ + if (minioninfo->iostats[_off].max_bytes < _siz) \ + minioninfo->iostats[_off].max_bytes = _siz; \ + if (minioninfo->iostats[_off].min_bytes == 0 || \ + minioninfo->iostats[_off].min_bytes > _siz) \ + minioninfo->iostats[_off].min_bytes = _siz; \ + } \ + } while (0); + +typedef struct iostat { + uint64_t count; // total ioctl() + + double total_delay; // total elapsed ioctl() + double min_delay; + double max_delay; + uint64_t zero_delay; // how many had <= 0 delay + + // Above but including locking + double total_dlock; + double min_dlock; + double max_dlock; + uint64_t zero_dlock; + + // Total time waiting to get lock + double total_dlwait; + + // these 3 fields are ignored for now since all are '1' + uint64_t total_ioc; // SPI_IOC_MESSAGE(x) + uint64_t min_ioc; + uint64_t max_ioc; + + uint64_t total_bytes; // ioctl() bytes + uint64_t min_bytes; + uint64_t max_bytes; + uint64_t zero_bytes; // how many had siz == 0 + + double tsd; // total doing one extra cgtime() each time +} IOSTAT; +#else +#define IO_STAT_NOW(_tv) +#define IO_STAT_STORE(_sta, _fin, _lsta, _lfin, _tsd, _buf, _siz, _reply, _ioc) +#endif + +static double time_bands[] = { 0.1, 0.5, 1.0, 2.0, 4.0, 8.0, 16.0, 32.0 }; +#define TIME_BANDS ((int)(sizeof(time_bands)/sizeof(double))) + +struct minion_info { + struct thr_info *thr; + struct thr_info spiw_thr; + struct thr_info spir_thr; + struct thr_info res_thr; + + pthread_mutex_t spi_lock; + pthread_mutex_t sta_lock; + + cgsem_t task_ready; + cgsem_t nonce_ready; + cgsem_t scan_work; + + volatile unsigned *gpio; + + int spifd; + char gpiointvalue[64]; + int gpiointfd; + + // I/O or seconds + bool spi_reset_io; + int spi_reset_count; + time_t last_spi_reset; + uint64_t spi_resets; + + // TODO: need to track disabled chips - done? + int chips; + bool has_chip[MINION_CHIPS]; + int init_temp[MINION_CHIPS]; + uint8_t init_cores[MINION_CHIPS][DATA_SIZ*MINION_CORE_REPS]; + + uint8_t chipid[MINION_CHIPS]; // Chip Number + int chip_pin[MINION_CHIPS]; + + uint64_t ioseq; + uint32_t next_task_id; + + // Stats + uint64_t chip_nonces[MINION_CHIPS]; + uint64_t chip_nononces[MINION_CHIPS]; + uint64_t chip_good[MINION_CHIPS]; + uint64_t chip_bad[MINION_CHIPS]; + uint64_t chip_err[MINION_CHIPS]; + uint64_t chip_dup[MINION_CHIPS]; + uint64_t core_good[MINION_CHIPS][MINION_CORES+1]; + uint64_t core_bad[MINION_CHIPS][MINION_CORES+1]; + + uint32_t chip_core_ena[MINION_CORE_REPS][MINION_CHIPS]; + uint32_t chip_core_act[MINION_CORE_REPS][MINION_CHIPS]; + + struct minion_status chip_status[MINION_CHIPS]; + + uint64_t interrupts; + uint64_t result_interrupts; + uint64_t command_interrupts; + char last_interrupt[64]; + + pthread_mutex_t nonce_lock; + uint64_t new_nonces; + + uint64_t ok_nonces; + uint64_t untested_nonces; + uint64_t tested_nonces; + + uint64_t work_unrolled; + uint64_t work_rolled; + + uint64_t spi_errors; + uint64_t fifo_spi_errors[MINION_CHIPS]; + uint64_t res_spi_errors[MINION_CHIPS]; + uint64_t use_res2[MINION_CHIPS]; + + uint64_t tasks_failed[MINION_CHIPS]; + uint64_t tasks_recovered[MINION_CHIPS]; + uint64_t nonces_failed[MINION_CHIPS]; + uint64_t nonces_recovered[MINION_CHIPS]; + struct timeval last_reset[MINION_CHIPS]; + double do_reset[MINION_CHIPS]; + bool flag_reset[MINION_CHIPS]; + + // Work items + K_LIST *wfree_list; + K_STORE *wwork_list; + K_STORE *wstale_list; + K_STORE *wque_list[MINION_CHIPS]; + K_STORE *wchip_list[MINION_CHIPS]; + uint64_t wwork_flushed; + uint64_t wque_flushed; + uint64_t wchip_staled; + + // Task list + K_LIST *tfree_list; + K_STORE *task_list; + K_STORE *treply_list; + + uint64_t next_tid; + + // Nonce replies + K_LIST *rfree_list; + K_STORE *rnonce_list; + + struct timeval last_did; + + // Nonce history + K_LIST *hfree_list; + K_STORE *hchip_list[MINION_CHIPS]; + + int history_gen; + struct timeval chip_chk; + struct timeval chip_rpt; + double history_ghs[MINION_CHIPS]; + // Point in history for MINION_RESET_s + int reset_time[MINION_CHIPS]; + K_ITEM *reset_mark[MINION_CHIPS]; + int reset_count[MINION_CHIPS]; + // Point in history for MINION_RESET2_s + int reset2_time[MINION_CHIPS]; + K_ITEM *reset2_mark[MINION_CHIPS]; + int reset2_count[MINION_CHIPS]; + + // Performance history + K_LIST *pfree_list; + K_STORE *p_list[MINION_CHIPS]; + + // 0xff history + K_LIST *xfree_list; + K_STORE *xff_list; + time_t last_power_cycle; + uint64_t power_cycles; + time_t last_xff; + uint64_t xffs; + uint64_t last_displayed_xff; + + // Gets reset to zero each time it is used in reporting + int res_err_count[MINION_CHIPS]; + +#if DO_IO_STATS + // Total + IOSTAT summary; + + // Two for each command plus wasted extras i.e. direct/fast lookup + // No error uses 0x0 to 0xff, error uses 0x100 to 0x1ff + IOSTAT iostats[0x200]; +#endif + + // Stats on how long work is waiting to move from wwork_list to wque_list + uint64_t que_work; + double que_time; + double que_min; + double que_max; + uint64_t que_bands[TIME_BANDS+1]; + + // From wwork_list to txrx + uint64_t wt_work; + double wt_time; + double wt_min; + double wt_max; + uint64_t wt_bands[TIME_BANDS+1]; + + bool lednow[MINION_CHIPS]; + bool setled[MINION_CHIPS]; + + // When changing the frequency don't modify 'anything' + bool changing[MINION_CHIPS]; + int init_freq[MINION_CHIPS]; + int want_freq[MINION_CHIPS]; + uint32_t freqsent[MINION_CHIPS]; + struct timeval lastfreq[MINION_CHIPS]; + int freqms[MINION_CHIPS]; + + bool initialised; +}; + +#if MINION_ROCKCHIP == 1 +static bool minion_toggle_gpio(struct cgpu_info *minioncgpu, int gpionum) +{ + struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data); + char pindir[64], ena[64], pin[8], dir[64]; + char gpiointvalue[64]; + struct stat st; + int file, err, chip; + ssize_t ret; + + snprintf(pindir, sizeof(pindir), MINION_GPIO_SYS MINION_GPIO_PIN, gpionum); + memset(&st, 0, sizeof(st)); + + if (stat(pindir, &st) == 0) { // already exists + if (!S_ISDIR(st.st_mode)) { + applog(LOG_ERR, "%s: failed1 to enable GPIO pin %d" + " - not a directory", + minioncgpu->drv->dname, gpionum); + return false; + } + } else { + snprintf(ena, sizeof(ena), MINION_GPIO_SYS MINION_GPIO_ENA); + file = open(ena, O_WRONLY | O_SYNC); + if (file == -1) { + applog(LOG_ERR, "%s: failed2 to export GPIO pin %d (%d)" + " - you need to be root?", + minioncgpu->drv->dname, + gpionum, errno); + return false; + } + snprintf(pin, sizeof(pin), MINION_GPIO_ENA_VAL, gpionum); + ret = write(file, pin, (size_t)strlen(pin)); + if (ret != (ssize_t)strlen(pin)) { + if (ret < 0) + err = errno; + else + err = (int)ret; + close(file); + applog(LOG_ERR, "%s: failed3 to export GPIO pin %d (%d:%d)", + minioncgpu->drv->dname, + gpionum, err, (int)strlen(pin)); + return false; + } + close(file); + + // Check again if it exists + memset(&st, 0, sizeof(st)); + if (stat(pindir, &st) != 0) { + applog(LOG_ERR, "%s: failed4 to export GPIO pin %d (%d)", + minioncgpu->drv->dname, + gpionum, errno); + return false; + } + } + + // Set the pin attributes + // Direction + snprintf(dir, sizeof(dir), MINION_GPIO_SYS MINION_GPIO_PIN MINION_GPIO_DIR, gpionum); + file = open(dir, O_WRONLY | O_SYNC); + if (file == -1) { + applog(LOG_ERR, "%s: failed5 to configure GPIO pin %d (%d)" + " - you need to be root?", + minioncgpu->drv->dname, + gpionum, errno); + return false; + } + ret = write(file, MINION_GPIO_DIR_WRITE, sizeof(MINION_GPIO_DIR_WRITE)-1); + if (ret != sizeof(MINION_GPIO_DIR_WRITE)-1) { + if (ret < 0) + err = errno; + else + err = (int)ret; + close(file); + applog(LOG_ERR, "%s: failed6 to configure GPIO pin %d (%d:%d)", + minioncgpu->drv->dname, gpionum, + err, (int)sizeof(MINION_GPIO_DIR_WRITE)-1); + return false; + } + close(file); + + // Open it + snprintf(gpiointvalue, sizeof(gpiointvalue), + MINION_GPIO_SYS MINION_GPIO_PIN MINION_GPIO_VALUE, + gpionum); + int fd = open(gpiointvalue, O_WRONLY); + if (fd == -1) { + applog(LOG_ERR, "%s: failed7 to access GPIO pin %d (%d)", + minioncgpu->drv->dname, + gpionum, errno); + return false; + } + + ret = write(fd, MINION_CHIP_OFF, sizeof(MINION_CHIP_OFF)-1); + if (ret != sizeof(MINION_CHIP_OFF)-1) { + close(fd); + applog(LOG_ERR, "%s: failed8 to toggle off GPIO pin %d (%d:%d)", + minioncgpu->drv->dname, + gpionum, (int)ret, errno); + return false; + } + + cgsleep_ms(MINION_CHIP_DELAY); + + ret = write(fd, MINION_CHIP_ON, sizeof(MINION_CHIP_ON)-1); + if (ret != sizeof(MINION_CHIP_OFF)-1) { + close(fd); + applog(LOG_ERR, "%s: failed9 to toggle on GPIO pin %d (%d:%d)", + minioncgpu->drv->dname, + gpionum, (int)ret, errno); + return false; + } + + close(fd); + minioninfo->last_power_cycle = time(NULL); + minioninfo->power_cycles++; + // Reset all chip led counters + for (chip = 0; chip < (int)MINION_CHIPS; chip++) { + if (minioninfo->has_chip[chip]) + minioninfo->chip_status[chip].first_nonce.tv_sec = 0L; + } + return true; +} +#endif + +static void ready_work(struct cgpu_info *minioncgpu, struct work *work, bool rolled) +{ + struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data); + K_ITEM *item = NULL; + + K_WLOCK(minioninfo->wfree_list); + + item = k_unlink_head(minioninfo->wfree_list); + + DATA_WORK(item)->work = work; + DATA_WORK(item)->task_id = 0; + memset(&(DATA_WORK(item)->sent), 0, sizeof(DATA_WORK(item)->sent)); + DATA_WORK(item)->nonces = 0; + DATA_WORK(item)->urgent = false; + DATA_WORK(item)->rolled = rolled; + DATA_WORK(item)->errors = 0; + cgtime(&(DATA_WORK(item)->created)); + + k_add_head(minioninfo->wwork_list, item); + + K_WUNLOCK(minioninfo->wfree_list); +} + +static bool oldest_nonce(struct cgpu_info *minioncgpu, int *chip, int *core, uint32_t *task_id, + uint32_t *nonce, bool *no_nonce, struct timeval *when, + bool *another, uint32_t *task_id2, uint32_t *nonce2) +{ + struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data); + K_ITEM *item = NULL; + bool found = false; + + K_WLOCK(minioninfo->rnonce_list); + + item = k_unlink_tail(minioninfo->rnonce_list); + if (item) { + found = true; + *chip = DATA_RES(item)->chip; + *core = DATA_RES(item)->core; + *task_id = DATA_RES(item)->task_id; + *nonce = DATA_RES(item)->nonce; + *no_nonce = DATA_RES(item)->no_nonce; + memcpy(when, &(DATA_RES(item)->when), sizeof(*when)); + *another = DATA_RES(item)->another; + *task_id2 = DATA_RES(item)->task_id2; + *nonce2 = DATA_RES(item)->nonce2; + + k_free_head(minioninfo->rfree_list, item); + } + + K_WUNLOCK(minioninfo->rnonce_list); + + return found; +} + +static const char *addr2txt(uint8_t addr) +{ + switch (addr) { + case READ_ADDR(MINION_SYS_CHIP_SIG): + return "RChipSig"; + case READ_ADDR(MINION_SYS_CHIP_STA): + return "RChipSta"; + case WRITE_ADDR(MINION_SYS_SPI_LED): + return "WLed"; + case WRITE_ADDR(MINION_SYS_MISC_CTL): + return "WMiscCtrl"; + case WRITE_ADDR(MINION_SYS_RSTN_CTL): + return "WResetCtrl"; + case READ_ADDR(MINION_SYS_FIFO_STA): + return "RFifoSta"; + case READ_ADDR(MINION_CORE_ENA0_31): + return "RCoreEna0-31"; + case WRITE_ADDR(MINION_CORE_ENA0_31): + return "WCoreEna0-31"; + case READ_ADDR(MINION_CORE_ENA32_63): + return "RCoreEna32-63"; + case WRITE_ADDR(MINION_CORE_ENA32_63): + return "WCoreEna32-63"; + case READ_ADDR(MINION_CORE_ENA64_95): + return "RCoreEna64-95"; + case WRITE_ADDR(MINION_CORE_ENA64_95): + return "WCoreEna64-95"; + case READ_ADDR(MINION_CORE_ENA96_98): + return "RCoreEna96-98"; + case WRITE_ADDR(MINION_CORE_ENA96_98): + return "WCoreEna96-98"; + case READ_ADDR(MINION_CORE_ACT0_31): + return "RCoreAct0-31"; + case READ_ADDR(MINION_CORE_ACT32_63): + return "RCoreAct32-63"; + case READ_ADDR(MINION_CORE_ACT64_95): + return "RCoreAct64-95"; + case READ_ADDR(MINION_CORE_ACT96_98): + return "RCoreAct96-98"; + case READ_ADDR(MINION_RES_DATA): + return "RResData"; + case READ_ADDR(MINION_RES_PEEK): + return "RResPeek"; + case WRITE_ADDR(MINION_QUE_0): + return "WQueWork"; + case READ_ADDR(MINION_NONCE_START): + return "RNonceStart"; + case WRITE_ADDR(MINION_NONCE_START): + return "WNonceStart"; + case READ_ADDR(MINION_NONCE_RANGE): + return "RNonceRange"; + case WRITE_ADDR(MINION_NONCE_RANGE): + return "WNonceRange"; + case READ_ADDR(MINION_SYS_INT_STA): + return "RIntSta"; + case WRITE_ADDR(MINION_SYS_INT_ENA): + return "WIntEna"; + case WRITE_ADDR(MINION_SYS_INT_CLR): + return "WIntClear"; + case WRITE_ADDR(MINION_SYS_BUF_TRIG): + return "WResTrigger"; + case WRITE_ADDR(MINION_SYS_QUE_TRIG): + return "WCmdTrigger"; + case READ_ADDR(MINION_SYS_TEMP_CTL): + return "RTempCtrl"; + case WRITE_ADDR(MINION_SYS_TEMP_CTL): + return "WTempCtrl"; + case READ_ADDR(MINION_SYS_FREQ_CTL): + return "RFreqCtrl"; + case WRITE_ADDR(MINION_SYS_FREQ_CTL): + return "WFreqCtrl"; + case READ_ADDR(MINION_SYS_IDLE_CNT): + return "RIdleCnt"; + } + + // gcc warning if this is in default: + if (IS_ADDR_READ(addr)) + return "RUnhandled"; + else + return "WUnhandled"; +} + +// For display_ioctl() +#define IOCTRL_LOG LOG_WARNING + +// For all other debug so it can easily be switched always on +#define MINION_LOG LOG_DEBUG + +// For task corruption logging +#define MINTASK_LOG LOG_DEBUG + +// Set to 1 for debug +#define MINION_SHOW_IO 0 + +#define DATA_ALL 2048 +#define DATA_OFF 512 + +#if MINION_SHOW_IO +static void display_ioctl(int reply, uint32_t osiz, uint8_t *obuf, uint32_t rsiz, uint8_t *rbuf) +{ + struct minion_result *res; + const char *name, *dir, *ex; + char buf[4096]; + int i, rescount; + + name = addr2txt(obuf[1]); + + if (IS_ADDR_READ(obuf[1])) + dir = "from"; + else + dir = "to"; + + buf[0] = '\0'; + ex = ""; + + switch (obuf[1]) { + case READ_ADDR(MINION_SYS_CHIP_SIG): + case READ_ADDR(MINION_SYS_CHIP_STA): + break; + case WRITE_ADDR(MINION_SYS_SPI_LED): + case WRITE_ADDR(MINION_SYS_MISC_CTL): + case WRITE_ADDR(MINION_SYS_RSTN_CTL): + if (osiz > HSIZE()) { + ex = " wrote "; + __bin2hex(buf, obuf + HSIZE(), osiz - HSIZE()); + } else + ex = " wrote nothing"; + break; + default: + if (IS_ADDR_WRITE(obuf[1])) { + if (osiz > HSIZE()) { + ex = " wrote "; + __bin2hex(buf, obuf + HSIZE(), osiz - HSIZE()); + } else + ex = " wrote nothing"; + } + break; + } + + if (reply < 0) { + applog(IOCTRL_LOG, "%s %s chipid %d osiz %d%s%s", + name, dir, (int)obuf[0], (int)osiz, ex, buf); + applog(IOCTRL_LOG, " reply was error %d", reply); + } else { + if (IS_ADDR_WRITE(obuf[1])) { + applog(IOCTRL_LOG, "%s %s chipid %d osiz %d%s%s", + name, dir, (int)obuf[0], (int)osiz, ex, buf); + applog(IOCTRL_LOG, " write ret was %d", reply); + } else { + switch (obuf[1]) { + case READ_ADDR(MINION_RES_DATA): + rescount = (int)((float)rsiz / (float)MINION_RES_DATA_SIZ); + applog(IOCTRL_LOG, "%s %s chipid %d osiz %d%s%s", + name, dir, (int)obuf[0], (int)osiz, ex, buf); + for (i = 0; i < rescount; i++) { + res = (struct minion_result *)(rbuf + osiz - rsiz + (i * MINION_RES_DATA_SIZ)); + if (!IS_RESULT(res)) { + applog(IOCTRL_LOG, " %s reply %d of %d - none", name, i+1, rescount); + } else { + __bin2hex(buf, res->nonce, DATA_SIZ); + applog(IOCTRL_LOG, " %s reply %d of %d %d(%d) was task 0x%04x" + " chipid %d core %d gold %s nonce 0x%s", + name, i+1, rescount, reply, rsiz, + RES_TASK(res), + (int)RES_CHIPID(res), + (int)RES_CORE(res), + (int)RES_GOLD(res) ? "Y" : "N", + buf); + } + } + break; + case READ_ADDR(MINION_SYS_CHIP_SIG): + case READ_ADDR(MINION_SYS_CHIP_STA): + default: + applog(IOCTRL_LOG, "%s %s chipid %d osiz %d%s%s", + name, dir, (int)obuf[0], (int)osiz, ex, buf); + __bin2hex(buf, rbuf + osiz - rsiz, rsiz); + applog(IOCTRL_LOG, " %s reply %d(%d) was %s", name, reply, rsiz, buf); + break; + } + } + } +} +#endif + +#define MINION_UNEXPECTED_TASK -999 +#define MINION_OVERSIZE_TASK -998 + +static void set_pin(struct minion_info *minioninfo, int pin, bool on) +{ + volatile uint32_t *paddr; + uint32_t value; + int bcm; + + bcm = minionPins[pin].bcm; + + paddr = minioninfo->gpio + ((on ? BCM2835_GPIO_SET0 : BCM2835_GPIO_CLR0) / 4) + (bcm / 10); + + value = 1 << (bcm % 32); + + *paddr = value; + *paddr = value; +} + +static void init_pins(struct minion_info *minioninfo) +{ + int pin; + + // Initialise all pins high as required + MINION_PIN_BEFORE; + for (pin = 0; pin < (int)MINION_PIN_COUNT; pin++) { + set_pin(minioninfo, pin, true); + MINION_PIN_SLEEP; + } +} + +#define EXTRA_LOG_IO 0 + +static bool minion_init_spi(struct cgpu_info *minioncgpu, struct minion_info *minioninfo, int bus, int chip, bool reset); + +static int __do_ioctl(struct cgpu_info *minioncgpu, struct minion_info *minioninfo, + int pin, uint8_t *obuf, uint32_t osiz, uint8_t *rbuf, + uint32_t rsiz, uint64_t *ioseq, MINION_FFL_ARGS) +{ + struct spi_ioc_transfer tran; + bool fail = false, powercycle = false, show = false; + double lastshow, total; + K_ITEM *xitem; + time_t now; + int ret; +#if MINION_SHOW_IO + char dataw[DATA_ALL], datar[DATA_ALL]; +#endif + +#if DO_IO_STATS + struct timeval sta, fin, lsta, lfin, tsd; +#endif + + if ((int)osiz > MINION_BUFSIZ) + quitfrom(1, file, func, line, "%s() invalid osiz %u > %d (chip=%d reg=0x%02x)", + __func__, osiz, MINION_BUFSIZ, (int)(obuf[0]), obuf[1]); + + if (rsiz >= osiz) + quitfrom(1, file, func, line, "%s() invalid rsiz %u >= osiz %u (chip=%u reg=0x%02x)", + __func__, rsiz, osiz, (int)(obuf[0]), obuf[1]); + + memset(&obuf[0] + osiz - rsiz, 0xff, rsiz); + +#if MINION_SHOW_IO + // if the a5/5a outside the data change, it means data overrun or corruption + memset(dataw, 0xa5, sizeof(dataw)); + memset(datar, 0x5a, sizeof(datar)); + memcpy(&dataw[DATA_OFF], &obuf[0], osiz); + + char *buf = bin2hex((unsigned char *)&(dataw[DATA_OFF]), osiz); + applog(IOCTRL_LOG, "*** %s() pin %d cid %d sending %02x %02x %s %02x %02x", + __func__, pin, (int)(dataw[DATA_OFF]), + dataw[0], dataw[DATA_OFF-1], buf, + dataw[DATA_OFF+osiz], dataw[DATA_ALL-1]); + free(buf); +#endif + + memset((char *)rbuf, 0x00, osiz); + +// cgsleep_ms(5); // TODO: a delay ... based on the last command? But subtract elapsed + // i.e. do any commands need a delay after the I/O has completed before the next I/O? + + memset(&tran, 0, sizeof(tran)); + if (osiz < MINION_SPI_BUFSIZ) + tran.len = osiz; + else + return MINION_OVERSIZE_TASK; + + tran.delay_usecs = opt_minion_spiusec; + tran.speed_hz = MINION_SPI_SPEED; + +#if MINION_SHOW_IO + tran.tx_buf = (uintptr_t)&(dataw[DATA_OFF]); + tran.rx_buf = (uintptr_t)&(datar[DATA_OFF]); +#else + tran.tx_buf = (uintptr_t)obuf; + tran.rx_buf = (uintptr_t)rbuf; +#endif + + IO_STAT_NOW(&lsta); + mutex_lock(&(minioninfo->spi_lock)); + if (usepins) { + // Pin low for I/O + MINION_PIN_BEFORE; + set_pin(minioninfo, pin, false); + MINION_PIN_SLEEP; + } + IO_STAT_NOW(&sta); + ret = ioctl(minioninfo->spifd, SPI_IOC_MESSAGE(1), (void *)&tran); + *ioseq = minioninfo->ioseq++; + IO_STAT_NOW(&fin); + if (usepins) { + MINION_PIN_AFTER; + // Pin back high after I/O + set_pin(minioninfo, pin, true); + } + now = time(NULL); + if (ret >= 0 && rbuf[0] == 0xff && rbuf[ret-1] == 0xff && + (obuf[1] == READ_ADDR(MINION_RES_DATA) || obuf[1] == READ_ADDR(MINION_SYS_FIFO_STA))) { + int i; + fail = true; + for (i = 1; i < ret-2; i++) { + if (rbuf[i] != 0xff) { + fail = false; + break; + } + } + if (fail) { + powercycle = show = false; + minioninfo->xffs++; + minioninfo->last_xff = now; + + if (minioninfo->xfree_list->count > 0) + xitem = k_unlink_head(minioninfo->xfree_list); + else + xitem = k_unlink_tail(minioninfo->xff_list); + DATA_XFF(xitem)->when = now; + if (!minioninfo->xff_list->head) + show = true; + else { + // if !changing and xff_list is full + if (!minioninfo->changing[obuf[0]] && + minioninfo->xfree_list->count == 0) { + total = DATA_XFF(xitem)->when - + DATA_XFF(minioninfo->xff_list->tail)->when; + if (total <= MINION_POWER_TIME) { + powercycle = true; + // Discard the history + k_list_transfer_to_head(minioninfo->xff_list, + minioninfo->xfree_list); + k_add_head(minioninfo->xfree_list, xitem); + xitem = NULL; + } + } + + if (!powercycle) { + lastshow = DATA_XFF(xitem)->when - + DATA_XFF(minioninfo->xff_list->head)->when; + show = (lastshow >= 5); + } + } + if (xitem) + k_add_head(minioninfo->xff_list, xitem); + +#if MINION_ROCKCHIP == 1 + if (powercycle) + minion_toggle_gpio(minioncgpu, MINION_POWERCYCLE_GPIO); +#endif + minion_init_spi(minioncgpu, minioninfo, 0, 0, true); + } + } else if (minioninfo->spi_reset_count) { + if (minioninfo->spi_reset_io) { + if (*ioseq > 0 && (*ioseq % minioninfo->spi_reset_count) == 0) + minion_init_spi(minioncgpu, minioninfo, 0, 0, true); + } else { + if (minioninfo->last_spi_reset == 0) + minioninfo->last_spi_reset = now; + else { + if ((now - minioninfo->last_spi_reset) >= minioninfo->spi_reset_count) + minion_init_spi(minioncgpu, minioninfo, 0, 0, true); + minioninfo->last_spi_reset = now; + } + } + } + if (opt_minion_spidelay) + cgsleep_ms(opt_minion_spidelay); + mutex_unlock(&(minioninfo->spi_lock)); + IO_STAT_NOW(&lfin); + IO_STAT_NOW(&tsd); + + IO_STAT_STORE(&sta, &fin, &lsta, &lfin, &tsd, obuf, osiz, ret, 1); + + if (fail) { + if (powercycle) { + applog(LOG_ERR, "%s%d: power cycle ioctl %"PRIu64" (%"PRIu64")", + minioncgpu->drv->name, minioncgpu->device_id, *ioseq, + minioninfo->xffs - minioninfo->last_displayed_xff); + minioninfo->last_displayed_xff = minioninfo->xffs; + } else if (show) { + char *what = "unk"; + switch (obuf[1]) { + case READ_ADDR(MINION_RES_DATA): + what = "nonce"; + break; + case READ_ADDR(MINION_SYS_FIFO_STA): + what = "fifo"; + break; + } + applog(LOG_ERR, "%s%d: reset ioctl %"PRIu64" %s all 0xff (%"PRIu64")", + minioncgpu->drv->name, minioncgpu->device_id, + *ioseq, what, minioninfo->xffs - minioninfo->last_displayed_xff); + minioninfo->last_displayed_xff = minioninfo->xffs; + } + } + +#if MINION_SHOW_IO + if (ret > 0) { + buf = bin2hex((unsigned char *)&(datar[DATA_OFF]), ret); + applog(IOCTRL_LOG, "*** %s() reply %d = pin %d cid %d %02x %02x %s %02x %02x", + __func__, ret, pin, (int)(dataw[DATA_OFF]), + datar[0], datar[DATA_OFF-1], buf, + datar[DATA_OFF+osiz], datar[DATA_ALL-1]); + free(buf); + } else + applog(LOG_ERR, "*** %s() reply = %d", __func__, ret); + + memcpy(&rbuf[0], &datar[DATA_OFF], osiz); + + display_ioctl(ret, osiz, (uint8_t *)(&dataw[DATA_OFF]), rsiz, (uint8_t *)(&datar[DATA_OFF])); +#endif +#if EXTRA_LOG_IO + if (obuf[1] == READ_ADDR(MINION_RES_PEEK) || + obuf[1] == READ_ADDR(MINION_RES_DATA) || + obuf[1] == READ_ADDR(MINION_SYS_FIFO_STA)) { + char *uf1, *uf2, c; + uf1 = bin2hex(obuf, DATA_SIZ); + uf2 = bin2hex(rbuf, (size_t)ret); + switch (obuf[1]) { + case READ_ADDR(MINION_RES_PEEK): + c = 'P'; + break; + case READ_ADDR(MINION_RES_DATA): + c = 'D'; + break; + case READ_ADDR(MINION_SYS_FIFO_STA): + c = 'F'; + break; + } + applog(LOG_WARNING, "*** ioseq %"PRIu64" cmd %c %s rep %.8s %s", + *ioseq, c, uf1, uf2, uf2+8); + free(uf2); + free(uf1); + } + if (obuf[1] == WRITE_ADDR(MINION_QUE_0)) { + char *uf; + uf = bin2hex(obuf, osiz); + applog(LOG_WARNING, "*** ioseq %"PRIu64" work %s", + *ioseq, uf); + free(uf); + } +#endif + return ret; +} + +#if 1 +#define do_ioctl(_pin, _obuf, _osiz, _rbuf, _rsiz, _ioseq) \ + __do_ioctl(minioncgpu, minioninfo, _pin, _obuf, _osiz, _rbuf, \ + _rsiz, _ioseq, MINION_FFL_HERE) +#else +#define do_ioctl(_pin, _obuf, _osiz, _rbuf, _rsiz, _ioseq) \ + _do_ioctl(minioninfo, _pin, _obuf, _osiz, _rbuf, \ + _rsiz, _ioseq, MINION_FFL_HERE) +// This sends an expected to work, SPI command before each SPI command +static int _do_ioctl(struct minion_info *minioninfo, int pin, uint8_t *obuf, uint32_t osiz, uint8_t *rbuf, uint32_t rsiz, uint64_t *ioseq, MINION_FFL_ARGS) +{ + struct minion_header *head; + uint8_t buf1[MINION_BUFSIZ]; + uint8_t buf2[MINION_BUFSIZ]; + uint32_t siz; + + head = (struct minion_header *)buf1; + head->chipid = 1; // Needs to be set to a valid chip + head->reg = READ_ADDR(MINION_SYS_FIFO_STA); + SET_HEAD_SIZ(head, DATA_SIZ); + siz = HSIZE() + DATA_SIZ; + __do_ioctl(minioncgpu, minioninfo, pin, buf1, siz, buf2, MINION_CORE_SIZ, ioseq, MINION_FFL_PASS); + + return __do_ioctl(minioncgpu, minioninfo, pin, obuf, osiz, rbuf, rsiz, ioseq, MINION_FFL_PASS); +} +#endif + +static bool _minion_txrx(struct cgpu_info *minioncgpu, struct minion_info *minioninfo, TASK_ITEM *task, MINION_FFL_ARGS) +{ + struct minion_header *head; + + head = (struct minion_header *)(task->obuf); + head->chipid = minioninfo->chipid[task->chip]; + if (task->write) + SET_HEAD_WRITE(head, task->address); + else + SET_HEAD_READ(head, task->address); + + SET_HEAD_SIZ(head, task->wsiz + task->rsiz); + + if (task->wsiz) + memcpy(&(head->data[0]), task->wbuf, task->wsiz); + task->osiz = HSIZE() + task->wsiz + task->rsiz; + + task->reply = do_ioctl(CHIP_PIN(task->chip), task->obuf, task->osiz, task->rbuf, task->rsiz, + &(task->ioseq)); + if (task->reply < 0) { + applog(LOG_ERR, "%s%d: chip=%d ioctl failed reply=%d err=%d" MINION_FFL, + minioncgpu->drv->name, minioncgpu->device_id, + task->chip, task->reply, errno, MINION_FFL_PASS); + } else if (task->reply < (int)(task->osiz)) { + applog(LOG_ERR, "%s%d: chip=%d ioctl failed to write %d only wrote %d (err=%d)" MINION_FFL, + minioncgpu->drv->name, minioncgpu->device_id, + task->chip, (int)(task->osiz), task->reply, errno, MINION_FFL_PASS); + } + + return (task->reply >= (int)(task->osiz)); +} + +// Only for DATA_SIZ commands +static int build_cmd(struct cgpu_info *minioncgpu, struct minion_info *minioninfo, int chip, uint8_t reg, uint8_t *rbuf, uint32_t rsiz, uint8_t *data) +{ + struct minion_header *head; + uint8_t wbuf[MINION_BUFSIZ]; + uint32_t wsiz; + uint64_t ioseq; + int reply; + + head = (struct minion_header *)wbuf; + head->chipid = minioninfo->chipid[chip]; + head->reg = reg; + SET_HEAD_SIZ(head, DATA_SIZ); + + head->data[0] = data[0]; + head->data[1] = data[1]; + head->data[2] = data[2]; + head->data[3] = data[3]; + + wsiz = HSIZE() + DATA_SIZ; + reply = do_ioctl(CHIP_PIN(chip), wbuf, wsiz, rbuf, rsiz, &ioseq); + + if (reply != (int)wsiz) { + applog(LOG_ERR, "%s: chip %d %s returned %d (should be %d)", + minioncgpu->drv->dname, chip, + addr2txt(head->reg), + reply, (int)wsiz); + } + + return reply; +} + +static void set_freq(struct cgpu_info *minioncgpu, struct minion_info *minioninfo, int chip, int freq) +{ + uint8_t rbuf[MINION_BUFSIZ]; + uint8_t data[4]; + uint32_t value; + __maybe_unused int reply; + + freq /= MINION_FREQ_FACTOR; + if (freq < MINION_FREQ_FACTOR_MIN) + freq = MINION_FREQ_FACTOR_MIN; + if (freq > MINION_FREQ_FACTOR_MAX) + freq = MINION_FREQ_FACTOR_MAX; + value = minion_freq[freq]; + data[0] = (uint8_t)(value & 0xff); + data[1] = (uint8_t)(((value & 0xff00) >> 8) & 0xff); + data[2] = (uint8_t)(((value & 0xff0000) >> 16) & 0xff); + data[3] = (uint8_t)(((value & 0xff000000) >> 24) & 0xff); + + minioninfo->freqsent[chip] = value; + + reply = build_cmd(minioncgpu, minioninfo, + chip, WRITE_ADDR(MINION_SYS_FREQ_CTL), + rbuf, 0, data); + + cgtime(&(minioninfo->lastfreq[chip])); + applog(LOG_DEBUG, "%s%i: chip %d freq %d sec %d usec %d", + minioncgpu->drv->name, minioncgpu->device_id, + chip, freq, + (int)(minioninfo->lastfreq[chip].tv_sec) % 10, + (int)(minioninfo->lastfreq[chip].tv_usec)); + + // Reset all this info on chip reset or freq change + minioninfo->reset_time[chip] = (int)FREQ_DELAY(minioninfo->init_freq[chip]); + if (second_check) + minioninfo->reset2_time[chip] = (int)FREQ2_DELAY(minioninfo->init_freq[chip]); + + minioninfo->chip_status[chip].first_nonce.tv_sec = 0L; + + // Discard chip history (if there is any) + if (minioninfo->hfree_list) { + K_WLOCK(minioninfo->hfree_list); + k_list_transfer_to_head(minioninfo->hchip_list[chip], minioninfo->hfree_list); + minioninfo->reset_mark[chip] = NULL; + minioninfo->reset_count[chip] = 0; + K_WUNLOCK(minioninfo->hfree_list); + } +} + +static void init_chip(struct cgpu_info *minioncgpu, struct minion_info *minioninfo, int chip) +{ + uint8_t rbuf[MINION_BUFSIZ]; + uint8_t data[4]; + __maybe_unused int reply; + int choice; + + // Complete chip reset + data[0] = 0x00; + data[1] = 0x00; + data[2] = 0xa5; + data[3] = 0xf5; + + reply = build_cmd(minioncgpu, minioninfo, + chip, WRITE_ADDR(MINION_SYS_RSTN_CTL), + rbuf, 0, data); + + // Default reset + data[0] = SYS_RSTN_CTL_INIT; + data[1] = 0x00; + data[2] = 0x00; + data[3] = 0x00; + + reply = build_cmd(minioncgpu, minioninfo, + chip, WRITE_ADDR(MINION_SYS_RSTN_CTL), + rbuf, 0, data); + + // Default initialisation + data[0] = SYS_MISC_CTL_DEFAULT; + data[1] = 0x00; + data[2] = 0x00; + data[3] = 0x00; + + reply = build_cmd(minioncgpu, minioninfo, + chip, WRITE_ADDR(MINION_SYS_MISC_CTL), + rbuf, 0, data); + + // Set chip frequency + choice = minioninfo->init_freq[chip]; + if (choice < MINION_FREQ_MIN) + choice = MINION_FREQ_MIN; + if (choice > MINION_FREQ_MAX) + choice = MINION_FREQ_MAX; + minioninfo->init_freq[chip] = choice; + set_freq(minioncgpu, minioninfo, chip, choice); + + // Set temp threshold + choice = minioninfo->init_temp[chip]; + if (choice == MINION_TEMP_CTL_DISABLE) + choice = MINION_TEMP_CTL_DISABLE_VALUE; + else { + if (choice < MINION_TEMP_CTL_MIN_VALUE || choice > MINION_TEMP_CTL_MAX_VALUE) + choice = MINION_TEMP_CTL_DEF; + choice -= MINION_TEMP_CTL_MIN_VALUE; + choice /= MINION_TEMP_CTL_STEP; + choice += MINION_TEMP_CTL_MIN; + if (choice < MINION_TEMP_CTL_MIN) + choice = MINION_TEMP_CTL_MIN; + if (choice > MINION_TEMP_CTL_MAX) + choice = MINION_TEMP_CTL_MAX; + } + data[0] = (uint8_t)choice; + data[1] = 0; + data[2] = 0; + data[3] = 0; + + minioninfo->chip_status[chip].tempsent = choice; + + reply = build_cmd(minioncgpu, minioninfo, + chip, WRITE_ADDR(MINION_SYS_TEMP_CTL), + rbuf, 0, data); +} + +static void enable_chip_cores(struct cgpu_info *minioncgpu, struct minion_info *minioninfo, int chip) +{ + uint8_t rbuf[MINION_BUFSIZ]; + uint8_t data[4]; + __maybe_unused int reply; + int rep, i; + + for (i = 0; i < 4; i++) + data[i] = minioninfo->init_cores[chip][i]; + + reply = build_cmd(minioncgpu, minioninfo, + chip, WRITE_ADDR(MINION_CORE_ENA0_31), + rbuf, 0, data); + + for (i = 0; i < 4; i++) + data[i] = minioninfo->init_cores[chip][i+4]; + + reply = build_cmd(minioncgpu, minioninfo, + chip, WRITE_ADDR(MINION_CORE_ENA32_63), + rbuf, 0, data); + + for (i = 0; i < 4; i++) + data[i] = minioninfo->init_cores[chip][i+8]; + + reply = build_cmd(minioncgpu, minioninfo, + chip, WRITE_ADDR(MINION_CORE_ENA64_95), + rbuf, 0, data); + + for (i = 0; i < 4; i++) + data[i] = minioninfo->init_cores[chip][i+12]; + + reply = build_cmd(minioncgpu, minioninfo, + chip, WRITE_ADDR(MINION_CORE_ENA96_98), + rbuf, 0, data); + +/* Below is for testing - disabled/use default + // 1/3 range for each of the 3 cores +// data[0] = 0x55; +// data[1] = 0x55; +// data[2] = 0x55; +// data[3] = 0x55; + + // quicker replies +// data[0] = 0x05; +// data[1] = 0x05; +// data[2] = 0x05; +// data[3] = 0x05; + + // 0x00000100 at 20MH/s per core = 336TH/s if 1 nonce per work item + // 0x00001000 = 21.0TH/s - so well above 2TH/s + // 0x00002000 = 10.5TH/s - above 2TH/s + // speed test + data[0] = 0x00; + data[1] = 0x01; + data[2] = 0x00; + data[3] = 0x00; +// data[3] = 0x20; // slow it down for other testing + + // 2 cores +// data[0] = 0xff; +// data[1] = 0xff; +// data[2] = 0xff; +// data[3] = 0x7f; + + reply = build_cmd(minioncgpu, minioninfo, + chip, WRITE_ADDR(MINION_NONCE_RANGE), + rbuf, 0, data); + + // find lots more nonces in a short time on my test data + // i.e. emulate a MUCH higher hash rate on SPI and work + // generation/testing + // Current test data (same repeated 10 times) has nonce 0x05e0ed6d + data[0] = 0x00; + data[1] = 0xed; + data[2] = 0xe0; + data[3] = 0x05; + + reply = build_cmd(minioncgpu, minioninfo, + chip, WRITE_ADDR(MINION_NONCE_START), + rbuf, 0, data); +*/ + + // store the core ena state + for (rep = 0; rep < MINION_CORE_REPS; rep++) { + data[0] = 0x0; + data[1] = 0x0; + data[2] = 0x0; + data[3] = 0x0; + + reply = build_cmd(minioncgpu, minioninfo, + chip, READ_ADDR(MINION_CORE_ENA0_31 + rep), + rbuf, MINION_CORE_SIZ, data); + + minioninfo->chip_core_ena[rep][chip] = *((uint32_t *)&(rbuf[HSIZE()])); + } + + // store the core active state + for (rep = 0; rep < MINION_CORE_REPS; rep++) { + data[0] = 0x0; + data[1] = 0x0; + data[2] = 0x0; + data[3] = 0x0; + + reply = build_cmd(minioncgpu, minioninfo, + chip, READ_ADDR(MINION_CORE_ACT0_31 + rep), + rbuf, MINION_CORE_SIZ, data); + + minioninfo->chip_core_act[rep][chip] = *((uint32_t *)&(rbuf[HSIZE()])); + } +} + +#if ENABLE_INT_NONO +static void enable_interrupt(struct cgpu_info *minioncgpu, struct minion_info *minioninfo, int chip) +{ + uint8_t rbuf[MINION_BUFSIZ]; + uint8_t data[4]; + __maybe_unused int reply; + + data[0] = MINION_RESULT_INT_SIZE; + data[1] = 0x00; + data[2] = 0x00; + data[3] = 0x00; + + reply = build_cmd(minioncgpu, minioninfo, + chip, WRITE_ADDR(MINION_SYS_BUF_TRIG), + rbuf, 0, data); + +// data[0] = MINION_QUE_MAX; // spaces available ... i.e. empty +// data[0] = MINION_QUE_LOW; // spaces in use + data[0] = MINION_QUE_MAX - MINION_QUE_LOW; // spaces available + data[1] = 0x00; + data[2] = 0x00; + data[3] = 0x00; + + reply = build_cmd(minioncgpu, minioninfo, + chip, WRITE_ADDR(MINION_SYS_QUE_TRIG), + rbuf, 0, data); + +// data[0] = MINION_RESULT_INT; + data[0] = MINION_RESULT_INT | MINION_CMD_INT; + data[1] = 0x00; + data[2] = 0x00; + data[3] = 0x00; + + reply = build_cmd(minioncgpu, minioninfo, + chip, WRITE_ADDR(MINION_SYS_INT_ENA), + rbuf, 0, data); +} +#endif + +static void minion_detect_one(struct cgpu_info *minioncgpu, struct minion_info *minioninfo, int pin, int chipid) +{ + struct minion_header *head; + uint8_t wbuf[MINION_BUFSIZ]; + uint8_t rbuf[MINION_BUFSIZ]; + uint32_t wsiz, rsiz; + int reply, tries, newchip; + uint64_t ioseq; + bool ok; + + head = (struct minion_header *)wbuf; + head->chipid = chipid; + rsiz = MINION_SYS_SIZ; + SET_HEAD_READ(head, MINION_SYS_CHIP_SIG); + SET_HEAD_SIZ(head, rsiz); + wsiz = HSIZE() + rsiz; + + tries = 0; + ok = false; + do { + reply = do_ioctl(pin, wbuf, wsiz, rbuf, rsiz, &ioseq); + + if (reply == (int)(wsiz)) { + uint32_t sig = u8tou32(rbuf, wsiz - rsiz); + + if (sig == MINION_CHIP_SIG) { + newchip = (minioninfo->chips)++; + minioninfo->has_chip[newchip] = true; + minioninfo->chipid[newchip] = chipid; + minioninfo->chip_pin[newchip] = pin; + ok = true; + } else { + if (sig == MINION_CHIP_SIG_SHIFT1 || + sig == MINION_CHIP_SIG_SHIFT2 || + sig == MINION_CHIP_SIG_SHIFT3 || + sig == MINION_CHIP_SIG_SHIFT4) { + applog(LOG_WARNING, "%s: pin %d chipid %d detect offset got" + " 0x%08x wanted 0x%08x", + minioncgpu->drv->dname, pin, chipid, + sig, MINION_CHIP_SIG); + } else { + if (sig == MINION_NOCHIP_SIG || + sig == MINION_NOCHIP_SIG2) // Assume no chip + ok = true; + else { + applog(LOG_ERR, "%s: pin %d chipid %d detect failed" + " got 0x%08x wanted 0x%08x", + minioncgpu->drv->dname, pin, + chipid, sig, MINION_CHIP_SIG); + } + } + } + } else { + applog(LOG_ERR, "%s: pin %d chipid %d reply %d ignored should be %d", + minioncgpu->drv->dname, pin, chipid, reply, (int)(wsiz)); + } + } while (!ok && ++tries <= MINION_SIG_TRIES); + + if (!ok) { + applog(LOG_ERR, "%s: pin %d chipid %d - detect failure status", + minioncgpu->drv->dname, pin, chipid); + } +} + +// Simple detect - just check each chip for the signature +static void minion_detect_chips(struct cgpu_info *minioncgpu, struct minion_info *minioninfo) +{ + int pin, chipid, chip; + int pinend, start_freq, want_freq, freqms; + +#if MINION_ROCKCHIP == 1 + minion_toggle_gpio(minioncgpu, MINION_POWERCYCLE_GPIO); + cgsleep_ms(100); +#endif + + if (usepins) { + init_pins(minioninfo); + pinend = (int)MINION_PIN_COUNT; + } else + pinend = 1; + + for (pin = 0; pin < pinend; pin++) { + for (chipid = MINION_MIN_CHIP; chipid <= MINION_MAX_CHIP; chipid++) { + minion_detect_one(minioncgpu, minioninfo, pin, chipid); + } + } + + if (minioninfo->chips) { + for (chip = 0; chip < (int)MINION_CHIPS; chip++) { + if (minioninfo->has_chip[chip]) { + want_freq = minioninfo->init_freq[chip]; + start_freq = want_freq * opt_minion_freqpercent / 100; + start_freq -= (start_freq % MINION_FREQ_FACTOR); + if (start_freq < MINION_FREQ_MIN) + start_freq = MINION_FREQ_MIN; + minioninfo->want_freq[chip] = want_freq; + minioninfo->init_freq[chip] = start_freq; + if (start_freq != want_freq) { + freqms = opt_minion_freqchange; + freqms /= ((want_freq - start_freq) / MINION_FREQ_FACTOR); + if (freqms < 0) + freqms = -freqms; + minioninfo->freqms[chip] = freqms; + minioninfo->changing[chip] = true; + } + init_chip(minioncgpu, minioninfo, chip); + enable_chip_cores(minioncgpu, minioninfo, chip); + } + } + +#if ENABLE_INT_NONO + // After everything is ready + for (chip = 0; chip < MINION_CHIPS; chip++) + if (minioninfo->has_chip[chip]) + enable_interrupt(minioncgpu, minioninfo, chip); +#endif + } +} + +static const char *minion_modules[] = { +#if MINION_ROCKCHIP == 0 + "i2c-dev", + "i2c-bcm2708", + "spidev", + "spi-bcm2708", +#endif + NULL +}; + +static struct { + int request; + int value; +} minion_ioc[] = { + { SPI_IOC_RD_MODE, 0 }, + { SPI_IOC_WR_MODE, 0 }, + { SPI_IOC_RD_BITS_PER_WORD, 8 }, + { SPI_IOC_WR_BITS_PER_WORD, 8 }, + { SPI_IOC_RD_MAX_SPEED_HZ, MINION_SPI_SPEED }, + { SPI_IOC_WR_MAX_SPEED_HZ, MINION_SPI_SPEED }, + { -1, -1 } +}; + +static bool minion_init_spi(struct cgpu_info *minioncgpu, struct minion_info *minioninfo, int bus, int chip, bool reset) +{ + int i, err, data; + char buf[64]; + + if (reset) { + // TODO: maybe slow it down? + close(minioninfo->spifd); + if (opt_minion_spisleep) + cgsleep_ms(opt_minion_spisleep); + minioninfo->spifd = open(minioncgpu->device_path, O_RDWR); + if (minioninfo->spifd < 0) + goto bad_out; + minioninfo->spi_resets++; +// minioninfo->chip_status[chip].first_nonce.tv_sec = 0L; + } else { + for (i = 0; minion_modules[i]; i++) { + snprintf(buf, sizeof(buf), "modprobe %s", minion_modules[i]); + err = system(buf); + if (err) { + applog(LOG_ERR, "%s: failed to modprobe %s (%d) - you need to be root?", + minioncgpu->drv->dname, + minion_modules[i], err); + goto bad_out; + } + } + + snprintf(buf, sizeof(buf), "/dev/spidev%d.%d", bus, chip); + minioninfo->spifd = open(buf, O_RDWR); + if (minioninfo->spifd < 0) { + applog(LOG_ERR, "%s: failed to open spidev (%d)", + minioncgpu->drv->dname, + errno); + goto bad_out; + } + + minioncgpu->device_path = strdup(buf); + } + + for (i = 0; minion_ioc[i].value != -1; i++) { + data = minion_ioc[i].value; + err = ioctl(minioninfo->spifd, minion_ioc[i].request, (void *)&data); + if (err < 0) { + applog(LOG_ERR, "%s: failed ioctl configuration (%d) (%d)", + minioncgpu->drv->dname, + i, errno); + goto close_out; + } + } + + return true; + +close_out: + close(minioninfo->spifd); + minioninfo->spifd = 0; + free(minioncgpu->device_path); + minioncgpu->device_path = NULL; + +bad_out: + return false; +} + +static bool minion_setup_chip_select(struct cgpu_info *minioncgpu, struct minion_info *minioninfo) +{ + volatile uint32_t *paddr; + uint32_t mask, value, mem; + int count, memfd, pin, bcm; + + memfd = open(minion_memory, O_RDWR | O_SYNC); + if (memfd < 0) { + applog(LOG_ERR, "%s: failed open %s (%d)", + minioncgpu->drv->dname, + minion_memory, errno); + return false; + } + + minioninfo->gpio = (volatile unsigned *)mmap(NULL, MINION_PAGE_SIZE, + PROT_READ | PROT_WRITE, + MAP_SHARED, memfd, + minion_memory_addr); + if (minioninfo->gpio == MAP_FAILED) { + close(memfd); + applog(LOG_ERR, "%s: failed mmap gpio (%d)", + minioncgpu->drv->dname, + errno); + return false; + } + + close(memfd); + + for (pin = 0; pin < (int)MINION_PIN_COUNT; pin++) { + bcm = minionPins[pin].bcm; + + paddr = minioninfo->gpio + (BCM2835_GPIO_FSEL0 / 4) + (bcm / 10); + + // Set each pin to be an output pin + mask = BCM2835_GPIO_FSEL_MASK << ((bcm % 10) * 3); + value = BCM2835_GPIO_FSEL_OUTPUT << ((bcm % 10) * 3); + + // Read settings + mem = *paddr; + *paddr; + + mem = (mem & ~mask) | (value & mask); + + // Write appended setting + *paddr = mem; + *paddr = mem; + + count++; + } + + if (count == 0) + return false; + else + return true; +} + +#if ENABLE_INT_NONO +static bool minion_init_gpio_interrupt(struct cgpu_info *minioncgpu, struct minion_info *minioninfo) +{ + char pindir[64], ena[64], pin[8], dir[64], edge[64], act[64]; + struct stat st; + int file, err; + ssize_t ret; + + snprintf(pindir, sizeof(pindir), MINION_GPIO_SYS MINION_GPIO_PIN, + MINION_GPIO_RESULT_INT_PIN); + memset(&st, 0, sizeof(st)); + + if (stat(pindir, &st) == 0) { // already exists + if (!S_ISDIR(st.st_mode)) { + applog(LOG_ERR, "%s: failed1 to enable GPIO pin %d interrupt" + " - not a directory", + minioncgpu->drv->dname, + MINION_GPIO_RESULT_INT_PIN); + return false; + } + } else { + snprintf(ena, sizeof(ena), MINION_GPIO_SYS MINION_GPIO_ENA); + file = open(ena, O_WRONLY | O_SYNC); + if (file == -1) { + applog(LOG_ERR, "%s: failed2 to enable GPIO pin %d interrupt (%d)" + " - you need to be root?", + minioncgpu->drv->dname, + MINION_GPIO_RESULT_INT_PIN, + errno); + return false; + } + snprintf(pin, sizeof(pin), MINION_GPIO_ENA_VAL, MINION_GPIO_RESULT_INT_PIN); + ret = write(file, pin, (size_t)strlen(pin)); + if (ret != (ssize_t)strlen(pin)) { + if (ret < 0) + err = errno; + else + err = (int)ret; + close(file); + applog(LOG_ERR, "%s: failed3 to enable GPIO pin %d interrupt (%d:%d)", + minioncgpu->drv->dname, + MINION_GPIO_RESULT_INT_PIN, + err, (int)strlen(pin)); + return false; + } + close(file); + + // Check again if it exists + memset(&st, 0, sizeof(st)); + if (stat(pindir, &st) != 0) { + applog(LOG_ERR, "%s: failed4 to enable GPIO pin %d interrupt (%d)", + minioncgpu->drv->dname, + MINION_GPIO_RESULT_INT_PIN, + errno); + return false; + } + } + + // Set the pin attributes + // Direction + snprintf(dir, sizeof(dir), MINION_GPIO_SYS MINION_GPIO_PIN MINION_GPIO_DIR, + MINION_GPIO_RESULT_INT_PIN); + file = open(dir, O_WRONLY | O_SYNC); + if (file == -1) { + applog(LOG_ERR, "%s: failed5 to enable GPIO pin %d interrupt (%d)" + " - you need to be root?", + minioncgpu->drv->dname, + MINION_GPIO_RESULT_INT_PIN, + errno); + return false; + } + ret = write(file, MINION_GPIO_DIR_READ, (size_t)strlen(MINION_GPIO_DIR_READ)); + if (ret != (ssize_t)strlen(MINION_GPIO_DIR_READ)) { + if (ret < 0) + err = errno; + else + err = (int)ret; + close(file); + applog(LOG_ERR, "%s: failed6 to enable GPIO pin %d interrupt (%d:%d)", + minioncgpu->drv->dname, + MINION_GPIO_RESULT_INT_PIN, + err, (int)strlen(MINION_GPIO_DIR_READ)); + return false; + } + close(file); + + // Edge + snprintf(edge, sizeof(edge), MINION_GPIO_SYS MINION_GPIO_PIN MINION_GPIO_EDGE, + MINION_GPIO_RESULT_INT_PIN); + file = open(edge, O_WRONLY | O_SYNC); + if (file == -1) { + applog(LOG_ERR, "%s: failed7 to enable GPIO pin %d interrupt (%d)", + minioncgpu->drv->dname, + MINION_GPIO_RESULT_INT_PIN, + errno); + return false; + } + ret = write(file, MINION_GPIO_EDGE_RISING, (size_t)strlen(MINION_GPIO_EDGE_RISING)); + if (ret != (ssize_t)strlen(MINION_GPIO_EDGE_RISING)) { + if (ret < 0) + err = errno; + else + err = (int)ret; + close(file); + applog(LOG_ERR, "%s: failed8 to enable GPIO pin %d interrupt (%d:%d)", + minioncgpu->drv->dname, + MINION_GPIO_RESULT_INT_PIN, + err, (int)strlen(MINION_GPIO_EDGE_RISING)); + return false; + } + close(file); + + // Active + snprintf(act, sizeof(act), MINION_GPIO_SYS MINION_GPIO_PIN MINION_GPIO_ACT, + MINION_GPIO_RESULT_INT_PIN); + file = open(act, O_WRONLY | O_SYNC); + if (file == -1) { + applog(LOG_ERR, "%s: failed9 to enable GPIO pin %d interrupt (%d)", + minioncgpu->drv->dname, + MINION_GPIO_RESULT_INT_PIN, + errno); + return false; + } + ret = write(file, MINION_GPIO_ACT_HI, (size_t)strlen(MINION_GPIO_ACT_HI)); + if (ret != (ssize_t)strlen(MINION_GPIO_ACT_HI)) { + if (ret < 0) + err = errno; + else + err = (int)ret; + close(file); + applog(LOG_ERR, "%s: failed10 to enable GPIO pin %d interrupt (%d:%d)", + minioncgpu->drv->dname, + MINION_GPIO_RESULT_INT_PIN, + err, (int)strlen(MINION_GPIO_ACT_HI)); + return false; + } + close(file); + + // Setup fd access to Value + snprintf(minioninfo->gpiointvalue, sizeof(minioninfo->gpiointvalue), + MINION_GPIO_SYS MINION_GPIO_PIN MINION_GPIO_VALUE, + MINION_GPIO_RESULT_INT_PIN); + minioninfo->gpiointfd = open(minioninfo->gpiointvalue, O_RDONLY); + if (minioninfo->gpiointfd == -1) { + applog(LOG_ERR, "%s: failed11 to enable GPIO pin %d interrupt (%d)", + minioncgpu->drv->dname, + MINION_GPIO_RESULT_INT_PIN, + errno); + return false; + } + + return true; +} +#endif + +// Default meaning all cores +static void default_all_cores(uint8_t *cores) +{ + int i; + + // clear all bits + for (i = 0; i < (int)(DATA_SIZ * MINION_CORE_REPS); i++) + cores[i] = 0x00; + + // enable (only) all cores + for (i = 0; i < MINION_CORES; i++) + ENABLE_CORE(cores, i); +} + +static void minion_process_options(struct minion_info *minioninfo) +{ + int last_freq, last_temp; + char *freq, *temp, *core, *comma, *buf, *plus, *minus; + uint8_t last_cores[DATA_SIZ*MINION_CORE_REPS]; + int i, core1, core2; + bool cleared; + + if (opt_minion_spireset && *opt_minion_spireset) { + bool is_io = true; + int val; + + switch (tolower(*opt_minion_spireset)) { + case 'i': + is_io = true; + break; + case 's': + is_io = false; + break; + default: + applog(LOG_WARNING, "ERR: Invalid SPI reset '%s'", + opt_minion_spireset); + goto skip; + } + val = atoi(opt_minion_spireset+1); + if (val < 0 || val > 9999) { + applog(LOG_WARNING, "ERR: Invalid SPI reset '%s'", + opt_minion_spireset); + } else { + minioninfo->spi_reset_io = is_io; + minioninfo->spi_reset_count = val; + minioninfo->last_spi_reset = time(NULL); + } + } +skip: + last_freq = MINION_FREQ_DEF; + if (opt_minion_freq && *opt_minion_freq) { + buf = freq = strdup(opt_minion_freq); + comma = strchr(freq, ','); + if (comma) + *(comma++) = '\0'; + + for (i = 0; i < (int)MINION_CHIPS; i++) { + if (freq && isdigit(*freq)) { + last_freq = (int)(round((double)atoi(freq) / (double)MINION_FREQ_FACTOR)) * MINION_FREQ_FACTOR; + if (last_freq < MINION_FREQ_MIN) + last_freq = MINION_FREQ_MIN; + if (last_freq > MINION_FREQ_MAX) + last_freq = MINION_FREQ_MAX; + + freq = comma; + if (comma) { + comma = strchr(freq, ','); + if (comma) + *(comma++) = '\0'; + } + } + minioninfo->init_freq[i] = last_freq; + } + free(buf); + } + + last_temp = MINION_TEMP_CTL_DEF; + if (opt_minion_temp && *opt_minion_temp) { + buf = temp = strdup(opt_minion_temp); + comma = strchr(temp, ','); + if (comma) + *(comma++) = '\0'; + + for (i = 0; i < (int)MINION_CHIPS; i++) { + if (temp) { + if (isdigit(*temp)) { + last_temp = atoi(temp); + last_temp -= (last_temp % MINION_TEMP_CTL_STEP); + if (last_temp < MINION_TEMP_CTL_MIN_VALUE) + last_temp = MINION_TEMP_CTL_MIN_VALUE; + if (last_temp > MINION_TEMP_CTL_MAX_VALUE) + last_temp = MINION_TEMP_CTL_MAX_VALUE; + } else { + if (strcasecmp(temp, MINION_TEMP_DISABLE) == 0) + last_temp = MINION_TEMP_CTL_DISABLE; + } + + temp = comma; + if (comma) { + comma = strchr(temp, ','); + if (comma) + *(comma++) = '\0'; + } + } + minioninfo->init_temp[i] = last_temp; + } + free(buf); + } + + default_all_cores(&(last_cores[0])); + // default to all cores until we find valid data + cleared = false; + if (opt_minion_cores && *opt_minion_cores) { + buf = core = strdup(opt_minion_cores); + comma = strchr(core, ','); + if (comma) + *(comma++) = '\0'; + + for (i = 0; i < (int)MINION_CHIPS; i++) { + // default to previous until we find valid data + cleared = false; + if (core) { + plus = strchr(core, '+'); + if (plus) + *(plus++) = '\0'; + while (core) { + minus = strchr(core, '-'); + if (minus) + *(minus++) = '\0'; + if (isdigit(*core)) { + core1 = atoi(core); + if (core1 >= 0 && core1 < MINION_CORES) { + if (!minus) { + if (!cleared) { + memset(last_cores, 0, sizeof(last_cores)); + cleared = true; + } + ENABLE_CORE(last_cores, core1); + } else { + core2 = atoi(minus); + if (core2 >= core1) { + if (core2 >= MINION_CORES) + core2 = MINION_CORES - 1; + while (core1 <= core2) { + if (!cleared) { + memset(last_cores, 0, + sizeof(last_cores)); + cleared = true; + } + ENABLE_CORE(last_cores, core1); + core1++; + } + } + } + } + } else { + if (strcasecmp(core, MINION_CORE_ALL) == 0) + default_all_cores(&(last_cores[0])); + } + core = plus; + if (plus) { + plus = strchr(core, '+'); + if (plus) + *(plus++) = '\0'; + } + } + core = comma; + if (comma) { + comma = strchr(core, ','); + if (comma) + *(comma++) = '\0'; + } + } + memcpy(&(minioninfo->init_cores[i][0]), &(last_cores[0]), sizeof(last_cores)); + } + free(buf); + } +} + +static void minion_detect(bool hotplug) +{ + struct cgpu_info *minioncgpu = NULL; + struct minion_info *minioninfo = NULL; + char buf[512]; + size_t off; + int i; + + if (hotplug) + return; + + define_test(); + + minioncgpu = calloc(1, sizeof(*minioncgpu)); + if (unlikely(!minioncgpu)) + quithere(1, "Failed to calloc minioncgpu"); + + minioncgpu->drv = &minion_drv; + minioncgpu->deven = DEV_ENABLED; + minioncgpu->threads = 1; + + minioninfo = calloc(1, sizeof(*minioninfo)); // everything '0' + if (unlikely(!minioninfo)) + quithere(1, "Failed to calloc minioninfo"); + minioncgpu->device_data = (void *)minioninfo; + + if (!minion_init_spi(minioncgpu, minioninfo, MINION_SPI_BUS, MINION_SPI_CHIP, false)) + goto unalloc; + +#if ENABLE_INT_NONO + if (!minion_init_gpio_interrupt(minioncgpu, minioninfo)) + goto unalloc; +#endif + + + if (usepins) { + if (!minion_setup_chip_select(minioncgpu, minioninfo)) + goto unalloc; + } + + mutex_init(&(minioninfo->spi_lock)); + mutex_init(&(minioninfo->sta_lock)); + + for (i = 0; i < (int)MINION_CHIPS; i++) { + minioninfo->init_freq[i] = MINION_FREQ_DEF; + minioninfo->init_temp[i] = MINION_TEMP_CTL_DEF; + default_all_cores(&(minioninfo->init_cores[i][0])); + } + + minion_process_options(minioninfo); + + applog(LOG_WARNING, "%s: checking for chips ...", minioncgpu->drv->dname); + + minion_detect_chips(minioncgpu, minioninfo); + + buf[0] = '\0'; + for (i = 0; i < (int)MINION_CHIPS; i++) { + if (minioninfo->has_chip[i]) { + off = strlen(buf); + snprintf(buf + off, sizeof(buf) - off, " %d:%d/%d", + i, minioninfo->chip_pin[i], (int)(minioninfo->chipid[i])); + } + } + + applog(LOG_WARNING, "%s: found %d chip%s:%s", + minioncgpu->drv->dname, minioninfo->chips, + (minioninfo->chips == 1) ? "" : "s", buf); + + if (minioninfo->chips == 0) + goto cleanup; + + if (!add_cgpu(minioncgpu)) + goto cleanup; + + mutex_init(&(minioninfo->nonce_lock)); + + minioninfo->wfree_list = k_new_list("Work", sizeof(WORK_ITEM), + ALLOC_WORK_ITEMS, LIMIT_WORK_ITEMS, true); + minioninfo->wwork_list = k_new_store(minioninfo->wfree_list); + minioninfo->wstale_list = k_new_store(minioninfo->wfree_list); + // Initialise them all in case we later decide to enable chips + for (i = 0; i < (int)MINION_CHIPS; i++) { + minioninfo->wque_list[i] = k_new_store(minioninfo->wfree_list); + minioninfo->wchip_list[i] = k_new_store(minioninfo->wfree_list); + } + + minioninfo->tfree_list = k_new_list("Task", sizeof(TASK_ITEM), + ALLOC_TASK_ITEMS, LIMIT_TASK_ITEMS, true); + minioninfo->task_list = k_new_store(minioninfo->tfree_list); + minioninfo->treply_list = k_new_store(minioninfo->tfree_list); + + minioninfo->rfree_list = k_new_list("Reply", sizeof(RES_ITEM), + ALLOC_RES_ITEMS, LIMIT_RES_ITEMS, true); + minioninfo->rnonce_list = k_new_store(minioninfo->rfree_list); + + minioninfo->history_gen = MINION_MAX_RESET_CHECK; + minioninfo->hfree_list = k_new_list("History", sizeof(HIST_ITEM), + ALLOC_HIST_ITEMS, LIMIT_HIST_ITEMS, true); + for (i = 0; i < (int)MINION_CHIPS; i++) + minioninfo->hchip_list[i] = k_new_store(minioninfo->hfree_list); + + minioninfo->pfree_list = k_new_list("Performance", sizeof(PERF_ITEM), + ALLOC_PERF_ITEMS, LIMIT_PERF_ITEMS, true); + for (i = 0; i < (int)MINION_CHIPS; i++) + minioninfo->p_list[i] = k_new_store(minioninfo->pfree_list); + + minioninfo->xfree_list = k_new_list("0xff", sizeof(XFF_ITEM), + ALLOC_XFF_ITEMS, LIMIT_XFF_ITEMS, true); + minioninfo->xff_list = k_new_store(minioninfo->xfree_list); + + cgsem_init(&(minioninfo->task_ready)); + cgsem_init(&(minioninfo->nonce_ready)); + cgsem_init(&(minioninfo->scan_work)); + + minioninfo->initialised = true; + + dupalloc(minioncgpu, 10); + + return; + +cleanup: + close(minioninfo->gpiointfd); + close(minioninfo->spifd); + mutex_destroy(&(minioninfo->sta_lock)); + mutex_destroy(&(minioninfo->spi_lock)); +unalloc: + free(minioninfo); + free(minioncgpu); +} + +static char *minion_api_set(struct cgpu_info *minioncgpu, char *option, char *setting, char *replybuf) +{ + struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data); + int chip, val; + char *colon; + + if (strcasecmp(option, "help") == 0) { + sprintf(replybuf, "reset: chip 0-%d freq: 0-%d:%d-%d " + "ledcount: 0-100 ledlimit: 0-200 " + "spidelay: 0-9999 spireset i|s0-9999 " + "spisleep: 0-9999", + minioninfo->chips - 1, + minioninfo->chips - 1, + MINION_FREQ_MIN, MINION_FREQ_MAX); + return replybuf; + } + + if (strcasecmp(option, "reset") == 0) { + if (!setting || !*setting) { + sprintf(replybuf, "missing chip to reset"); + return replybuf; + } + + chip = atoi(setting); + if (chip < 0 || chip >= minioninfo->chips) { + sprintf(replybuf, "invalid reset: chip '%s' valid range 0-%d", + setting, + minioninfo->chips); + return replybuf; + } + + if (!minioninfo->has_chip[chip]) { + sprintf(replybuf, "unable to reset chip %d - chip disabled", + chip); + return replybuf; + } + minioninfo->flag_reset[chip] = true; + return NULL; + } + + // This sets up a freq step up/down to the given freq without a reset + if (strcasecmp(option, "freq") == 0) { + if (!setting || !*setting) { + sprintf(replybuf, "missing chip:freq"); + return replybuf; + } + + colon = strchr(setting, ':'); + if (!colon) { + sprintf(replybuf, "missing ':' for chip:freq"); + return replybuf; + } + + *(colon++) = '\0'; + if (!*colon) { + sprintf(replybuf, "missing freq in chip:freq"); + return replybuf; + } + + chip = atoi(setting); + if (chip < 0 || chip >= minioninfo->chips) { + sprintf(replybuf, "invalid freq: chip '%s' valid range 0-%d", + setting, + minioninfo->chips); + return replybuf; + } + + if (!minioninfo->has_chip[chip]) { + sprintf(replybuf, "unable to modify chip %d - chip not enabled", + chip); + return replybuf; + } + + val = atoi(colon); + if (val < MINION_FREQ_MIN || val > MINION_FREQ_MAX) { + sprintf(replybuf, "invalid freq: '%s' valid range %d-%d", + setting, + MINION_FREQ_MIN, MINION_FREQ_MAX); + return replybuf; + } + + int want_freq = val - (val % MINION_FREQ_FACTOR); + int start_freq = minioninfo->init_freq[chip]; + int freqms; + + if (want_freq != start_freq) { + minioninfo->changing[chip] = false; + freqms = opt_minion_freqchange; + freqms /= ((want_freq - start_freq) / MINION_FREQ_FACTOR); + if (freqms < 0) + freqms = -freqms; + minioninfo->freqms[chip] = freqms; + minioninfo->want_freq[chip] = want_freq; + cgtime(&(minioninfo->lastfreq[chip])); + minioninfo->changing[chip] = true; + } + + return NULL; + } + + if (strcasecmp(option, "ledcount") == 0) { + if (!setting || !*setting) { + sprintf(replybuf, "missing ledcount value"); + return replybuf; + } + + val = atoi(setting); + if (val < 0 || val > 100) { + sprintf(replybuf, "invalid ledcount: '%s' valid range 0-100", + setting); + return replybuf; + } + + opt_minion_ledcount = val; + return NULL; + } + + if (strcasecmp(option, "ledlimit") == 0) { + if (!setting || !*setting) { + sprintf(replybuf, "missing ledlimit value"); + return replybuf; + } + + val = atoi(setting); + if (val < 0 || val > 200) { + sprintf(replybuf, "invalid ledlimit: GHs '%s' valid range 0-200", + setting); + return replybuf; + } + + opt_minion_ledlimit = val; + return NULL; + } + + if (strcasecmp(option, "spidelay") == 0) { + if (!setting || !*setting) { + sprintf(replybuf, "missing spidelay value"); + return replybuf; + } + + val = atoi(setting); + if (val < 0 || val > 9999) { + sprintf(replybuf, "invalid spidelay: ms '%s' valid range 0-9999", + setting); + return replybuf; + } + + opt_minion_spidelay = val; + return NULL; + } + + if (strcasecmp(option, "spireset") == 0) { + bool is_io = true; + + if (!setting || !*setting) { + sprintf(replybuf, "missing spireset value"); + return replybuf; + } + + switch (tolower(*setting)) { + case 'i': + is_io = true; + break; + case 's': + is_io = false; + break; + default: + sprintf(replybuf, "invalid spireset: '%s' must start with i or s", + setting); + return replybuf; + } + val = atoi(setting+1); + if (val < 0 || val > 9999) { + sprintf(replybuf, "invalid spireset: %c '%s' valid range 0-9999", + *setting, setting+1); + return replybuf; + } + + minioninfo->spi_reset_io = is_io; + minioninfo->spi_reset_count = val; + minioninfo->last_spi_reset = time(NULL); + + return NULL; + } + + if (strcasecmp(option, "spisleep") == 0) { + if (!setting || !*setting) { + sprintf(replybuf, "missing spisleep value"); + return replybuf; + } + + val = atoi(setting); + if (val < 0 || val > 9999) { + sprintf(replybuf, "invalid spisleep: ms '%s' valid range 0-9999", + setting); + return replybuf; + } + + opt_minion_spisleep = val; + return NULL; + } + + if (strcasecmp(option, "spiusec") == 0) { + if (!setting || !*setting) { + sprintf(replybuf, "missing spiusec value"); + return replybuf; + } + + val = atoi(setting); + if (val < 0 || val > 9999) { + sprintf(replybuf, "invalid spiusec: '%s' valid range 0-9999", + setting); + return replybuf; + } + + opt_minion_spiusec = val; + return NULL; + } + + sprintf(replybuf, "Unknown option: %s", option); + return replybuf; +} + +static void minion_identify(__maybe_unused struct cgpu_info *minioncgpu) +{ + // flash a led +} + +/* + * SPI/ioctl write thread + * Non urgent work is to keep the queue full + * Urgent work is when an LP occurs (or the queue is empty/low) + */ +static void *minion_spi_write(void *userdata) +{ + struct cgpu_info *minioncgpu = (struct cgpu_info *)userdata; + struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data); + K_ITEM *item, *tail, *task, *work; + TASK_ITEM *titem; + + applog(MINION_LOG, "%s%i: SPI writing...", + minioncgpu->drv->name, minioncgpu->device_id); + + // Wait until we're ready + while (minioncgpu->shutdown == false) { + if (minioninfo->initialised) { + break; + } + cgsleep_ms(1); // asap to start mining + } + + // TODO: combine all urgent into a single I/O? + // Then combine all state 1 for the same chip into a single I/O ? + // (then again for state 2?) + while (minioncgpu->shutdown == false) { + item = NULL; + K_WLOCK(minioninfo->task_list); + tail = minioninfo->task_list->tail; + if (tail) { + // Find first urgent item + item = tail; + while (item && !(DATA_TASK(item)->urgent)) + item = item->prev; + + // No urgent items, just do the tail + if (!item) + item = tail; + + k_unlink_item(minioninfo->task_list, item); + } + K_WUNLOCK(minioninfo->task_list); + + if (item) { + bool do_txrx = true; + bool store_reply = true; + struct timeval now; + double howlong; + int i; + + titem = DATA_TASK(item); + + switch (titem->address) { + // TODO: case MINION_SYS_TEMP_CTL: + // TODO: case MINION_SYS_FREQ_CTL: + case READ_ADDR(MINION_SYS_CHIP_STA): + case WRITE_ADDR(MINION_SYS_SPI_LED): + case WRITE_ADDR(MINION_SYS_RSTN_CTL): + case WRITE_ADDR(MINION_SYS_INT_CLR): + case READ_ADDR(MINION_SYS_IDLE_CNT): + case READ_ADDR(MINION_CORE_ENA0_31): + case READ_ADDR(MINION_CORE_ENA32_63): + case READ_ADDR(MINION_CORE_ENA64_95): + case READ_ADDR(MINION_CORE_ENA96_98): + case READ_ADDR(MINION_CORE_ACT0_31): + case READ_ADDR(MINION_CORE_ACT32_63): + case READ_ADDR(MINION_CORE_ACT64_95): + case READ_ADDR(MINION_CORE_ACT96_98): + store_reply = false; + break; + case WRITE_ADDR(MINION_QUE_0): +//applog(LOG_ERR, "%s%i: ZZZ send task_id 0x%04x - chip %d", minioncgpu->drv->name, minioncgpu->device_id, titem->task_id, titem->chip); + store_reply = false; + break; + default: + do_txrx = false; + titem->reply = MINION_UNEXPECTED_TASK; + applog(LOG_ERR, "%s%i: Unexpected task address 0x%02x (%s)", + minioncgpu->drv->name, minioncgpu->device_id, + (unsigned int)(titem->address), + addr2txt(titem->address)); + + break; + } + + if (do_txrx) { + if (titem->witem) { + cgtime(&now); + howlong = tdiff(&now, &(DATA_WORK(titem->witem)->created)); + minioninfo->wt_work++; + minioninfo->wt_time += howlong; + if (minioninfo->wt_min == 0 || minioninfo->wt_min > howlong) + minioninfo->wt_min = howlong; + else if (minioninfo->wt_max < howlong) + minioninfo->wt_max = howlong; + for (i = 0; i < TIME_BANDS; i++) { + if (howlong < time_bands[i]) { + minioninfo->wt_bands[i]++; + break; + } + } + if (i >= TIME_BANDS) + minioninfo->wt_bands[TIME_BANDS]++; + } + + minion_txrx(titem); + + int chip = titem->chip; + switch (titem->address) { + case READ_ADDR(MINION_SYS_CHIP_STA): + if (titem->reply >= (int)(titem->osiz)) { + uint8_t *rep = &(titem->rbuf[titem->osiz - titem->rsiz]); + mutex_lock(&(minioninfo->sta_lock)); + minioninfo->chip_status[chip].temp = STA_TEMP(rep); + minioninfo->chip_status[chip].cores = STA_CORES(rep); + minioninfo->chip_status[chip].freq = STA_FREQ(rep); + mutex_unlock(&(minioninfo->sta_lock)); + + if (minioninfo->chip_status[chip].overheat) { + switch (STA_TEMP(rep)) { + case MINION_TEMP_40: + case MINION_TEMP_60: + case MINION_TEMP_80: + cgtime(&(minioninfo->chip_status[chip].lastrecover)); + minioninfo->chip_status[chip].overheat = false; + applog(LOG_WARNING, "%s%d: chip %d cooled, restarting", + minioncgpu->drv->name, + minioncgpu->device_id, + chip); + cgtime(&(minioninfo->chip_status[chip].lastrecover)); + minioninfo->chip_status[chip].overheattime += + tdiff(&(minioninfo->chip_status[chip].lastrecover), + &(minioninfo->chip_status[chip].lastoverheat)); + break; + default: + break; + } + } else { + if (opt_minion_overheat && STA_TEMP(rep) == MINION_TEMP_OVER) { + cgtime(&(minioninfo->chip_status[chip].lastoverheat)); + minioninfo->chip_status[chip].overheat = true; + applog(LOG_WARNING, "%s%d: chip %d overheated! idling", + minioncgpu->drv->name, + minioncgpu->device_id, + chip); + K_WLOCK(minioninfo->tfree_list); + task = k_unlink_head(minioninfo->tfree_list); + DATA_TASK(task)->tid = ++(minioninfo->next_tid); + DATA_TASK(task)->chip = chip; + DATA_TASK(task)->write = true; + DATA_TASK(task)->address = MINION_SYS_RSTN_CTL; + DATA_TASK(task)->task_id = 0; // ignored + DATA_TASK(task)->wsiz = MINION_SYS_SIZ; + DATA_TASK(task)->rsiz = 0; + DATA_TASK(task)->wbuf[0] = SYS_RSTN_CTL_FLUSH; + DATA_TASK(task)->wbuf[1] = 0; + DATA_TASK(task)->wbuf[2] = 0; + DATA_TASK(task)->wbuf[3] = 0; + DATA_TASK(task)->urgent = true; + k_add_head(minioninfo->task_list, task); + K_WUNLOCK(minioninfo->tfree_list); + minioninfo->chip_status[chip].overheats++; + } + } + } + break; + case READ_ADDR(MINION_SYS_IDLE_CNT): + { + uint32_t *cnt = (uint32_t *)&(titem->rbuf[titem->osiz - titem->rsiz]); + minioninfo->chip_status[chip].idle = *cnt; + } + break; + case WRITE_ADDR(MINION_SYS_RSTN_CTL): + // Do this here after it has actually been flushed + if ((titem->wbuf[0] & SYS_RSTN_CTL_FLUSH) == SYS_RSTN_CTL_FLUSH) { + int cnt = 0; + K_WLOCK(minioninfo->wwork_list); + work = minioninfo->wchip_list[chip]->head; + while (work) { + cnt++; + DATA_WORK(work)->stale = true; + work = work->next; + } + minioninfo->chip_status[chip].chipwork = 0; + minioninfo->chip_status[chip].realwork = 0; + minioninfo->wchip_staled += cnt; +#if MINION_SHOW_IO + applog(IOCTRL_LOG, "RSTN chip %d (cnt=%d) cw0=%u rw0=%u qw=%u", + chip, cnt, + minioninfo->chip_status[chip].chipwork, + minioninfo->chip_status[chip].realwork, + minioninfo->chip_status[chip].quework); +#endif + K_WUNLOCK(minioninfo->wwork_list); + } + break; + case WRITE_ADDR(MINION_QUE_0): + K_WLOCK(minioninfo->wchip_list[chip]); + k_unlink_item(minioninfo->wque_list[chip], titem->witem); + k_add_head(minioninfo->wchip_list[chip], titem->witem); + DATA_WORK(titem->witem)->ioseq = titem->ioseq; + minioninfo->chip_status[chip].quework--; + minioninfo->chip_status[chip].chipwork++; +#if MINION_SHOW_IO + applog(IOCTRL_LOG, "QUE_0 chip %d cw+1=%u rw=%u qw-1=%u", + chip, + minioninfo->chip_status[chip].chipwork, + minioninfo->chip_status[chip].realwork, + minioninfo->chip_status[chip].quework); +#endif + K_WUNLOCK(minioninfo->wchip_list[chip]); + applog(LOG_DEBUG, "%s%d: task 0x%04x sent to chip %d", + minioncgpu->drv->name, minioncgpu->device_id, + titem->task_id, chip); + break; + case READ_ADDR(MINION_CORE_ENA0_31): + case READ_ADDR(MINION_CORE_ENA32_63): + case READ_ADDR(MINION_CORE_ENA64_95): + case READ_ADDR(MINION_CORE_ENA96_98): + { + uint32_t *rep = (uint32_t *)&(titem->rbuf[titem->osiz - titem->rsiz]); + int off = titem->address - READ_ADDR(MINION_CORE_ENA0_31); + minioninfo->chip_core_ena[off][chip] = *rep; + } + break; + case READ_ADDR(MINION_CORE_ACT0_31): + case READ_ADDR(MINION_CORE_ACT32_63): + case READ_ADDR(MINION_CORE_ACT64_95): + case READ_ADDR(MINION_CORE_ACT96_98): + { + uint32_t *rep = (uint32_t *)&(titem->rbuf[titem->osiz - titem->rsiz]); + int off = titem->address - READ_ADDR(MINION_CORE_ACT0_31); + minioninfo->chip_core_act[off][chip] = *rep; + } + break; + case WRITE_ADDR(MINION_SYS_INT_CLR): + case WRITE_ADDR(MINION_SYS_SPI_LED): + break; + default: + break; + } + } + + K_WLOCK(minioninfo->treply_list); + if (store_reply) + k_add_head(minioninfo->treply_list, item); + else + k_free_head(minioninfo->tfree_list, item); + K_WUNLOCK(minioninfo->treply_list); + + /* + * Always check for the next task immediately if we just did one + * i.e. empty the task queue + */ + continue; + } + cgsem_mswait(&(minioninfo->task_ready), MINION_TASK_mS); + } + return NULL; +} + +/* + * SPI/ioctl reply thread + * ioctl done every interrupt or MINION_REPLY_mS checking for results + */ +static void *minion_spi_reply(void *userdata) +{ + struct cgpu_info *minioncgpu = (struct cgpu_info *)userdata; + struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data); + struct minion_result *result1, *result2, *use1, *use2; + K_ITEM *item; + TASK_ITEM fifo_task, res1_task, res2_task; + int chip, resoff; + bool somelow; + struct timeval now; + +#if ENABLE_INT_NONO + uint64_t ioseq; + TASK_ITEM clr_task; + struct pollfd pfd; + struct minion_header *head; + uint8_t rbuf[MINION_BUFSIZ]; + uint8_t wbuf[MINION_BUFSIZ]; + uint32_t wsiz, rsiz; + int ret, reply; + bool gotreplies = false; +#endif + + applog(MINION_LOG, "%s%i: SPI replying...", + minioncgpu->drv->name, minioncgpu->device_id); + + // Wait until we're ready + while (minioncgpu->shutdown == false) { + if (minioninfo->initialised) { + break; + } + cgsleep_ms(2); + } + + fifo_task.chip = 0; + fifo_task.write = false; + fifo_task.address = MINION_SYS_FIFO_STA; + fifo_task.wsiz = 0; + fifo_task.rsiz = MINION_SYS_SIZ; + + res1_task.chip = 0; + res1_task.write = false; + if (minreread) + res1_task.address = MINION_RES_PEEK; + else + res1_task.address = MINION_RES_DATA; + res1_task.wsiz = 0; + res1_task.rsiz = MINION_RES_DATA_SIZ; + + res2_task.chip = 0; + res2_task.write = false; + res2_task.address = MINION_RES_DATA; + res2_task.wsiz = 0; + res2_task.rsiz = MINION_RES_DATA_SIZ; + +#if ENABLE_INT_NONO + // Clear RESULT_INT after reading all results + clr_task.chip = 0; + clr_task.write = true; + clr_task.address = MINION_SYS_INT_CLR; + clr_task.wsiz = MINION_SYS_SIZ; + clr_task.rsiz = 0; + clr_task.wbuf[0] = MINION_RESULT_INT; + clr_task.wbuf[1] = 0; + clr_task.wbuf[2] = 0; + clr_task.wbuf[3] = 0; + + memset(&pfd, 0, sizeof(pfd)); + pfd.fd = minioninfo->gpiointfd; + pfd.events = POLLPRI; + + head = (struct minion_header *)wbuf; + SET_HEAD_SIZ(head, MINION_SYS_SIZ); + wsiz = HSIZE() + MINION_SYS_SIZ; + rsiz = MINION_SYS_SIZ; // for READ, use 0 for WRITE +#endif + + somelow = false; + while (minioncgpu->shutdown == false) { + for (chip = 0; chip < (int)MINION_CHIPS; chip++) { + if (minioninfo->has_chip[chip]) { + int tries = 0; + uint8_t res, cmd; + + if (minioninfo->changing[chip] && + ms_tdiff(&now, &minioninfo->lastfreq[chip]) > + minioninfo->freqms[chip]) { + int want_freq = minioninfo->want_freq[chip]; + int init_freq = minioninfo->init_freq[chip]; + + if (want_freq > init_freq) { + minioninfo->init_freq[chip] += MINION_FREQ_FACTOR; + init_freq += MINION_FREQ_FACTOR; + + set_freq(minioncgpu, minioninfo, chip, init_freq); + } else if (want_freq < init_freq) { + minioninfo->init_freq[chip] -= MINION_FREQ_FACTOR; + init_freq -= MINION_FREQ_FACTOR; + + set_freq(minioncgpu, minioninfo, chip, init_freq); + } + + if (init_freq == want_freq) + minioninfo->changing[chip] = false; + } + + while (++tries < 4) { + res = cmd = 0; + fifo_task.chip = chip; + fifo_task.reply = 0; + minion_txrx(&fifo_task); + if (fifo_task.reply <= 0) { + minioninfo->spi_errors++; + minioninfo->fifo_spi_errors[chip]++; + minioninfo->res_err_count[chip]++; + break; + } else { + if (fifo_task.reply < (int)(fifo_task.osiz)) { + char *buf = bin2hex((unsigned char *)(&(fifo_task.rbuf[fifo_task.osiz - fifo_task.rsiz])), + (int)(fifo_task.rsiz)); + applog(LOG_DEBUG, "%s%i: Chip %d Bad fifo reply (%s) size %d, should be %d", + minioncgpu->drv->name, minioncgpu->device_id, + chip, buf, + fifo_task.reply, (int)(fifo_task.osiz)); + free(buf); + minioninfo->spi_errors++; + minioninfo->fifo_spi_errors[chip]++; + minioninfo->res_err_count[chip]++; + } else { + if (fifo_task.reply > (int)(fifo_task.osiz)) { + applog(LOG_DEBUG, "%s%i: Chip %d Unexpected fifo reply size %d, " + "expected only %d", + minioncgpu->drv->name, minioncgpu->device_id, + chip, fifo_task.reply, (int)(fifo_task.osiz)); + } + res = FIFO_RES(fifo_task.rbuf, fifo_task.osiz - fifo_task.rsiz); + cmd = FIFO_CMD(fifo_task.rbuf, fifo_task.osiz - fifo_task.rsiz); + // valid reply? + if (res <= MINION_QUE_MAX && cmd <= MINION_QUE_MAX) + break; + + applog(LOG_DEBUG, "%s%i: Chip %d Bad fifo reply res %d (max is %d) " + "cmd %d (max is %d)", + minioncgpu->drv->name, minioncgpu->device_id, + chip, (int)res, MINION_QUE_MAX, + (int)cmd, MINION_QUE_MAX); + minioninfo->spi_errors++; + minioninfo->fifo_spi_errors[chip]++; + minioninfo->res_err_count[chip]++; + } + } + } + + // Give up on this chip this round + if (tries >= 4) + continue; + + K_WLOCK(minioninfo->wwork_list); + // have to just assume it's always correct since we can't verify it + minioninfo->chip_status[chip].realwork = (uint32_t)cmd; +#if MINION_SHOW_IO + applog(IOCTRL_LOG, "SetReal chip %d cw=%u rw==%u qw=%u", + chip, + minioninfo->chip_status[chip].chipwork, + minioninfo->chip_status[chip].realwork, + minioninfo->chip_status[chip].quework); +#endif + K_WUNLOCK(minioninfo->wwork_list); + + if (cmd < MINION_QUE_LOW) { + somelow = true; + // Flag it in case the count is wrong + K_WLOCK(minioninfo->wwork_list); + minioninfo->chip_status[chip].islow = true; + minioninfo->chip_status[chip].lowcount = (int)cmd; + K_WUNLOCK(minioninfo->wwork_list); + } + + /* + * Chip has results? + * You can't request results unless it says it has some. + * We don't ever directly flush the output queue while processing + * (except at startup) so the answer is always valid + * i.e. there could be more, but never less ... unless the reply was corrupt + */ + if (res > MINION_MAX_RES) { + applog(LOG_ERR, "%s%i: Large work reply chip %d res %d", + minioncgpu->drv->name, minioncgpu->device_id, chip, res); + minioninfo->spi_errors++; + minioninfo->fifo_spi_errors[chip]++; + minioninfo->res_err_count[chip]++; + res = 1; // Just read one result + } +//else +//applog(LOG_ERR, "%s%i: work reply res %d", minioncgpu->drv->name, minioncgpu->device_id, res); + uint8_t left = res; + int peeks = 0; + while (left > 0) { + res = left; + if (res > MINION_MAX_RES) + res = MINION_MAX_RES; + left -= res; +repeek: + res1_task.chip = chip; + res1_task.reply = 0; + res1_task.rsiz = res * MINION_RES_DATA_SIZ; + minion_txrx(&res1_task); + if (res1_task.reply <= 0) + break; + else { + cgtime(&now); + if (res1_task.reply < (int)MINION_RES_DATA_SIZ) { + char *buf = bin2hex((unsigned char *)(&(res1_task.rbuf[res1_task.osiz - res1_task.rsiz])), (int)(res1_task.rsiz)); + applog(LOG_ERR, "%s%i: Chip %d Bad work reply (%s) size %d, should be at least %d", + minioncgpu->drv->name, minioncgpu->device_id, + chip, buf, + res1_task.reply, (int)MINION_RES_DATA_SIZ); + free(buf); + minioninfo->spi_errors++; + minioninfo->res_spi_errors[chip]++; + minioninfo->res_err_count[chip]++; + } else { + if (res1_task.reply != (int)(res1_task.osiz)) { + applog(LOG_ERR, "%s%i: Chip %d Unexpected work reply size %d, expected %d", + minioncgpu->drv->name, minioncgpu->device_id, + chip, res1_task.reply, (int)(res1_task.osiz)); + minioninfo->spi_errors++; + minioninfo->res_spi_errors[chip]++; + minioninfo->res_err_count[chip]++; + // Can retry a PEEK without losing data + if (minreread) { + if (++peeks < 4) + goto repeek; + break; + } + } + + if (minreread) { + res2_task.chip = chip; + res2_task.reply = 0; + res2_task.rsiz = res * MINION_RES_DATA_SIZ; + minion_txrx(&res2_task); + if (res2_task.reply <= 0) { + minioninfo->spi_errors++; + minioninfo->res_spi_errors[chip]++; + minioninfo->res_err_count[chip]++; + } + } + + for (resoff = res1_task.osiz - res1_task.rsiz; resoff < (int)res1_task.osiz; resoff += MINION_RES_DATA_SIZ) { + result1 = (struct minion_result *)&(res1_task.rbuf[resoff]); + if (minreread && resoff < (int)res2_task.osiz) + result2 = (struct minion_result *)&(res2_task.rbuf[resoff]); + else + result2 = NULL; + + if (IS_RESULT(result1) || (minreread && result2 && IS_RESULT(result2))) { + K_WLOCK(minioninfo->rfree_list); + item = k_unlink_head(minioninfo->rfree_list); + K_WUNLOCK(minioninfo->rfree_list); + + if (IS_RESULT(result1)) { + use1 = result1; + if (minreread && result2 && IS_RESULT(result2)) + use2 = result2; + else + use2 = NULL; + } else { + use1 = result2; + use2 = NULL; + minioninfo->use_res2[chip]++; + } + + //DATA_RES(item)->chip = RES_CHIPID(use1); + // We can avoid any SPI transmission error of the chip number + DATA_RES(item)->chip = (uint8_t)chip; + if (minioninfo->chipid[chip] != RES_CHIPID(use1)) { + minioninfo->spi_errors++; + minioninfo->res_spi_errors[chip]++; + minioninfo->res_err_count[chip]++; + } + if (use2 && minioninfo->chipid[chip] != RES_CHIPID(use2)) { + minioninfo->spi_errors++; + minioninfo->res_spi_errors[chip]++; + minioninfo->res_err_count[chip]++; + } + DATA_RES(item)->core = RES_CORE(use1); + DATA_RES(item)->task_id = RES_TASK(use1); + DATA_RES(item)->nonce = RES_NONCE(use1); + DATA_RES(item)->no_nonce = !RES_GOLD(use1); + memcpy(&(DATA_RES(item)->when), &now, sizeof(now)); + applog(LOG_DEBUG, "%s%i: reply task_id 0x%04x" + " - chip %d - gold %d", + minioncgpu->drv->name, + minioncgpu->device_id, + RES_TASK(use1), + (int)RES_CHIPID(use1), + (int)RES_GOLD(use1)); + + if (!use2) + DATA_RES(item)->another = false; + else { + DATA_RES(item)->another = true; + DATA_RES(item)->task_id2 = RES_TASK(use2); + DATA_RES(item)->nonce2 = RES_NONCE(use2); + } +//if (RES_GOLD(use1)) +//applog(MINTASK_LOG, "%s%i: found a result chip %d core %d task 0x%04x nonce 0x%08x gold=%d", minioncgpu->drv->name, minioncgpu->device_id, DATA_RES(item)->chip, DATA_RES(item)->core, DATA_RES(item)->task_id, DATA_RES(item)->nonce, (int)RES_GOLD(use1)); + + K_WLOCK(minioninfo->rnonce_list); + k_add_head(minioninfo->rnonce_list, item); + K_WUNLOCK(minioninfo->rnonce_list); + + if (!(minioninfo->chip_status[chip].first_nonce.tv_sec)) { + cgtime(&(minioninfo->chip_status[chip].first_nonce)); + minioninfo->chip_status[chip].from_first_good = 0; + } + + cgsem_post(&(minioninfo->nonce_ready)); + } else { + minioninfo->res_err_count[chip]++; + applog(MINTASK_LOG, "%s%i: Invalid res0 task_id 0x%04x - chip %d", + minioncgpu->drv->name, minioncgpu->device_id, + RES_TASK(result1), chip); + if (minreread && result2) { + applog(MINTASK_LOG, "%s%i: Invalid res1 task_id 0x%04x - chip %d", + minioncgpu->drv->name, minioncgpu->device_id, + RES_TASK(result2), chip); + } + } + } + } + } + } + } + } + + if (somelow) + cgsem_post(&(minioninfo->scan_work)); + +#if ENABLE_INT_NONO + if (gotreplies) + minion_txrx(&clr_task); +#endif + +#if !ENABLE_INT_NONO + cgsleep_ms(MINION_REPLY_mS); +#else + // TODO: this is going to require a bit of tuning with 2TH/s mining: + // The interrupt size MINION_RESULT_INT_SIZE should be high enough to expect + // most chips to have some results but low enough to cause negligible latency + // If all chips don't have some results when an interrupt occurs, then it is a waste + // since we have to check all chips for results anyway since we don't know which one + // caused the interrupt + // MINION_REPLY_mS needs to be low enough in the case of bad luck where no chip + // finds MINION_RESULT_INT_SIZE results in a short amount of time, so we go check + // them all anyway - to avoid high latency when there are only a few results due to low luck + ret = poll(&pfd, 1, MINION_REPLY_mS); + if (ret > 0) { + bool gotres; + int c; + + minioninfo->interrupts++; + + read(minioninfo->gpiointfd, &c, 1); + +// applog(LOG_ERR, "%s%i: Interrupt2", +// minioncgpu->drv->name, +// minioncgpu->device_id); + + gotres = false; + for (chip = 0; chip < (int)MINION_CHIPS; chip++) { + if (minioninfo->has_chip[chip]) { + SET_HEAD_READ(head, MINION_SYS_INT_STA); + head->chipid = minioninfo->chipid[chip]; + reply = do_ioctl(CHIP_PIN(chip), wbuf, wsiz, rbuf, rsiz, &ioseq); + if (reply != (int)wsiz) { + applog(LOG_ERR, "%s: chip %d int status returned %d" + " (should be %d)", + minioncgpu->drv->dname, + chip, reply, (int)wsiz); + } + + snprintf(minioninfo->last_interrupt, + sizeof(minioninfo->last_interrupt), + "%d %d 0x%02x%02x%02x%02x%02x%02x%02x%02x %d %d 0x%02x %d %d", + (int)(minioninfo->interrupts), chip, + rbuf[0], rbuf[1], rbuf[2], rbuf[3], + rbuf[4], rbuf[5], rbuf[6], rbuf[7], + (int)wsiz, (int)rsiz, rbuf[wsiz - rsiz], + rbuf[wsiz - rsiz] & MINION_RESULT_INT, + rbuf[wsiz - rsiz] & MINION_CMD_INT); + + if ((rbuf[wsiz - rsiz] & MINION_RESULT_INT) != 0) { + gotres = true; + (minioninfo->result_interrupts)++; +// applog(LOG_ERR, "%s%i: chip %d got RES interrupt", +// minioncgpu->drv->name, +// minioncgpu->device_id, +// chip); + } + + if ((rbuf[wsiz - rsiz] & MINION_CMD_INT) != 0) { + // Work queue is empty + (minioninfo->command_interrupts)++; +// applog(LOG_ERR, "%s%i: chip %d got CMD interrupt", +// minioncgpu->drv->name, +// minioncgpu->device_id, +// chip); + } + +// char *tmp; +// tmp = bin2hex(rbuf, wsiz); +// applog(LOG_ERR, "%s%i: chip %d interrupt: %s", +// minioncgpu->drv->name, +// minioncgpu->device_id, +// chip, tmp); +// free(tmp); + + // Don't clear either interrupt until after send/recv + } + } + + // Doing this last means we can't miss an interrupt + if (gotres) + cgsem_post(&(minioninfo->scan_work)); + } +#endif + } + + return NULL; +} + +/* + * Find the matching work item for this chip + * Discard any older work items for this chip + */ + +enum nonce_state { + NONCE_GOOD_NONCE, + NONCE_NO_NONCE, + NONCE_DUP_NONCE, + NONCE_BAD_NONCE, + NONCE_BAD_WORK, + NONCE_NO_WORK, + NONCE_SPI_ERR +}; + +static void cleanup_older(struct cgpu_info *minioncgpu, int chip, K_ITEM *item, bool no_nonce) +{ + struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data); + K_ITEM *tail; +// bool errs; + + /* remove older ioseq work items + no_nonce means this 'item' has finished also */ + tail = minioninfo->wchip_list[chip]->tail; + while (tail && (DATA_WORK(tail)->ioseq < DATA_WORK(item)->ioseq)) { + k_unlink_item(minioninfo->wchip_list[chip], tail); + if (!(DATA_WORK(tail)->stale)) { + minioninfo->chip_status[chip].chipwork--; +#if MINION_SHOW_IO + applog(IOCTRL_LOG, "COld chip %d cw-1=%u rw=%u qw=%u", + chip, + minioninfo->chip_status[chip].chipwork, + minioninfo->chip_status[chip].realwork, + minioninfo->chip_status[chip].quework); +#endif +/* + // If it had no valid work (only errors) then it won't have been cleaned up + errs = (DATA_WORK(tail)->errors > 0); + applog(errs ? LOG_DEBUG : LOG_ERR, + applog(LOG_ERR, + "%s%i: discarded old task 0x%04x chip %d no reply errs=%d", + minioncgpu->drv->name, minioncgpu->device_id, + DATA_WORK(tail)->task_id, chip, DATA_WORK(tail)->errors); +*/ + } + applog(MINION_LOG, "%s%i: marking complete - old task 0x%04x chip %d", + minioncgpu->drv->name, minioncgpu->device_id, + DATA_WORK(tail)->task_id, chip); + if (DATA_WORK(tail)->rolled) + free_work(DATA_WORK(tail)->work); + else + work_completed(minioncgpu, DATA_WORK(tail)->work); + k_free_head(minioninfo->wfree_list, tail); + tail = minioninfo->wchip_list[chip]->tail; + } + if (no_nonce) { + if (!(DATA_WORK(item)->stale)) { + minioninfo->chip_status[chip].chipwork--; +#if MINION_SHOW_IO + applog(IOCTRL_LOG, "CONoN chip %d cw-1=%u rw=%u qw=%u", + chip, + minioninfo->chip_status[chip].chipwork, + minioninfo->chip_status[chip].realwork, + minioninfo->chip_status[chip].quework); +#endif + } + applog(MINION_LOG, "%s%i: marking complete - no_nonce task 0x%04x chip %d", + minioncgpu->drv->name, minioncgpu->device_id, + DATA_WORK(item)->task_id, chip); + if (DATA_WORK(item)->rolled) + free_work(DATA_WORK(item)->work); + else + work_completed(minioncgpu, DATA_WORK(item)->work); + } +} + +// Need to put it back in the list where it was - according to ioseq +static void restorework(struct minion_info *minioninfo, int chip, K_ITEM *item) +{ + K_ITEM *look; + + look = minioninfo->wchip_list[chip]->tail; + while (look && DATA_WORK(look)->ioseq < DATA_WORK(item)->ioseq) + look = look->prev; + if (!look) + k_add_head(minioninfo->wchip_list[chip], item); + else + k_insert_after(minioninfo->wchip_list[chip], item, look); +} + +static enum nonce_state oknonce(struct thr_info *thr, struct cgpu_info *minioncgpu, int chip, int core, + uint32_t task_id, uint32_t nonce, bool no_nonce, struct timeval *when, + bool another, uint32_t task_id2, uint32_t nonce2) +{ + struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data); + struct timeval now; + K_ITEM *item, *tail; + uint32_t min_task_id, max_task_id; +// uint64_t chip_good; + bool redo; + + // if the chip has been disabled - but we don't do that - so not possible (yet) + if (!(minioninfo->has_chip[chip])) { + minioninfo->spi_errors++; + applog(MINTASK_LOG, "%s%i: nonce error chip %d not present", + minioncgpu->drv->name, minioncgpu->device_id, chip); + return NONCE_NO_WORK; + } + + if (core < 0 || core >= MINION_CORES) { + minioninfo->spi_errors++; + minioninfo->res_spi_errors[chip]++; + minioninfo->res_err_count[chip]++; + applog(MINTASK_LOG, "%s%i: SPI nonce error invalid core %d (chip %d)", + minioncgpu->drv->name, minioncgpu->device_id, core, chip); + + // use the fake core number so we don't discard the result + core = FAKE_CORE; + } + + if (no_nonce) + minioninfo->chip_nononces[chip]++; + else + minioninfo->chip_nonces[chip]++; + + redo = false; +retry: + K_WLOCK(minioninfo->wchip_list[chip]); + item = minioninfo->wchip_list[chip]->tail; + + if (!item) { + K_WUNLOCK(minioninfo->wchip_list[chip]); + minioninfo->spi_errors++; + minioninfo->res_spi_errors[chip]++; + minioninfo->res_err_count[chip]++; + applog(MINTASK_LOG, "%s%i: chip %d has no tasks (core %d task 0x%04x)", + minioncgpu->drv->name, minioncgpu->device_id, + chip, core, (int)task_id); + if (!no_nonce) { + minioninfo->untested_nonces++; + minioninfo->chip_err[chip]++; + } + return NONCE_NO_WORK; + } + + min_task_id = DATA_WORK(item)->task_id; + while (item) { + if (DATA_WORK(item)->task_id == task_id) + break; + + item = item->prev; + } + max_task_id = DATA_WORK(minioninfo->wchip_list[chip]->head)->task_id; + + if (!item) { + K_WUNLOCK(minioninfo->wchip_list[chip]); + if (another && task_id != task_id2) { + minioninfo->tasks_failed[chip]++; + task_id = task_id2; + redo = true; + goto retry; + } + + minioninfo->spi_errors++; + minioninfo->res_spi_errors[chip]++; + minioninfo->res_err_count[chip]++; + applog(MINTASK_LOG, "%s%i: chip %d core %d unknown task 0x%04x " + "(min=0x%04x max=0x%04x no_nonce=%d)", + minioncgpu->drv->name, minioncgpu->device_id, + chip, core, (int)task_id, (int)min_task_id, + (int)max_task_id, no_nonce); + if (!no_nonce) { + minioninfo->untested_nonces++; + minioninfo->chip_err[chip]++; + } + return NONCE_BAD_WORK; + } + if (redo) + minioninfo->tasks_recovered[chip]++; + + k_unlink_item(minioninfo->wchip_list[chip], item); + if (no_nonce) { + cleanup_older(minioncgpu, chip, item, no_nonce); + k_free_head(minioninfo->wfree_list, item); + K_WUNLOCK(minioninfo->wchip_list[chip]); + return NONCE_NO_NONCE; + } + K_WUNLOCK(minioninfo->wchip_list[chip]); + + minioninfo->tested_nonces++; + + redo = false; +retest: + if (test_nonce(DATA_WORK(item)->work, nonce)) { +/* + if (isdupnonce(minioncgpu, DATA_WORK(item)->work, nonce)) { + minioninfo->chip_dup[chip]++; + applog(LOG_WARNING, " ... nonce %02x%02x%02x%02x chip %d core %d task 0x%04x", + (nonce & 0xff), ((nonce >> 8) & 0xff), + ((nonce >> 16) & 0xff), ((nonce >> 24) & 0xff), + chip, core, task_id); + K_WLOCK(minioninfo->wchip_list[chip]); + restorework(minioninfo, chip, item); + K_WUNLOCK(minioninfo->wchip_list[chip]); + return NONCE_DUP_NONCE; + } +*/ +//applog(MINTASK_LOG, "%s%i: Valid Nonce chip %d core %d task 0x%04x nonce 0x%08x", minioncgpu->drv->name, minioncgpu->device_id, chip, core, task_id, nonce); +// + submit_tested_work(thr, DATA_WORK(item)->work); + + if (redo) + minioninfo->nonces_recovered[chip]++; + + /* chip_good = */ ++(minioninfo->chip_good[chip]); + minioninfo->chip_status[chip].from_first_good++; + minioninfo->core_good[chip][core]++; + DATA_WORK(item)->nonces++; + + mutex_lock(&(minioninfo->nonce_lock)); + minioninfo->new_nonces++; + mutex_unlock(&(minioninfo->nonce_lock)); + minioninfo->ok_nonces++; + + K_WLOCK(minioninfo->wchip_list[chip]); + cleanup_older(minioncgpu, chip, item, no_nonce); + restorework(minioninfo, chip, item); + K_WUNLOCK(minioninfo->wchip_list[chip]); + + // add to history and remove old history and keep track of the 2 reset marks + int chip_tmp; + cgtime(&now); + K_WLOCK(minioninfo->hfree_list); + item = k_unlink_head(minioninfo->hfree_list); + memcpy(&(DATA_HIST(item)->when), when, sizeof(*when)); + k_add_head(minioninfo->hchip_list[chip], item); + if (minioninfo->reset_mark[chip]) + minioninfo->reset_count[chip]++; + if (second_check && minioninfo->reset2_mark[chip]) + minioninfo->reset2_count[chip]++; + + // N.B. this also corrects each reset_mark/reset_count within each hchip_list + for (chip_tmp = 0; chip_tmp < (int)MINION_CHIPS; chip_tmp++) { + tail = minioninfo->hchip_list[chip_tmp]->tail; + while (tail && tdiff(&(DATA_HIST(tail)->when), &now) > MINION_HISTORY_s) { + if (minioninfo->reset_mark[chip] == tail) { + minioninfo->reset_mark[chip] = tail->prev; + minioninfo->reset_count[chip]--; + } + if (second_check && minioninfo->reset2_mark[chip] == tail) { + minioninfo->reset2_mark[chip] = tail->prev; + minioninfo->reset2_count[chip]--; + } + tail = k_unlink_tail(minioninfo->hchip_list[chip_tmp]); + k_add_head(minioninfo->hfree_list, item); + tail = minioninfo->hchip_list[chip_tmp]->tail; + } + if (!(minioninfo->reset_mark[chip])) { + minioninfo->reset_mark[chip] = minioninfo->hchip_list[chip]->tail; + minioninfo->reset_count[chip] = minioninfo->hchip_list[chip]->count; + } + if (second_check && !(minioninfo->reset2_mark[chip])) { + minioninfo->reset2_mark[chip] = minioninfo->hchip_list[chip]->tail; + minioninfo->reset2_count[chip] = minioninfo->hchip_list[chip]->count; + } + tail = minioninfo->reset_mark[chip]; + while (tail && tdiff(&(DATA_HIST(tail)->when), &now) > minioninfo->reset_time[chip]) { + tail = minioninfo->reset_mark[chip] = tail->prev; + minioninfo->reset_count[chip]--; + } + if (second_check) { + tail = minioninfo->reset2_mark[chip]; + while (tail && tdiff(&(DATA_HIST(tail)->when), &now) > minioninfo->reset2_time[chip]) { + tail = minioninfo->reset2_mark[chip] = tail->prev; + minioninfo->reset2_count[chip]--; + } + } + } + K_WUNLOCK(minioninfo->hfree_list); + +/* + // Reset the chip after 8 nonces found + if (chip_good == 8) { + memcpy(&(minioninfo->last_reset[chip]), &now, sizeof(now)); + init_chip(minioncgpu, minioninfo, chip); + } +*/ + + return NONCE_GOOD_NONCE; + } + + if (another && nonce != nonce2) { + minioninfo->nonces_failed[chip]++; + nonce = nonce2; + redo = true; + goto retest; + } + + DATA_WORK(item)->errors++; + K_WLOCK(minioninfo->wchip_list[chip]); + restorework(minioninfo, chip, item); + K_WUNLOCK(minioninfo->wchip_list[chip]); + + minioninfo->chip_bad[chip]++; + minioninfo->core_bad[chip][core]++; + inc_hw_errors(thr); +//applog(MINTASK_LOG, "%s%i: HW ERROR chip %d core %d task 0x%04x nonce 0x%08x", minioncgpu->drv->name, minioncgpu->device_id, chip, core, task_id, nonce); + + return NONCE_BAD_NONCE; +} + +/* Check each chip how long since the last nonce + * Should normally be a fraction of a second + * so (MINION_RESET_s * 1.5) will certainly be long enough, + * but also will avoid lots of resets if there is trouble getting work + * Should be longer than MINION_RESET_s to avoid interfering with normal resets */ +static void check_last_nonce(struct cgpu_info *minioncgpu) +{ + struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data); + struct timeval now; + K_ITEM *head; + double howlong; + int chip; + + cgtime(&now); + K_RLOCK(minioninfo->hfree_list); + for (chip = 0; chip < (int)MINION_CHIPS; chip++) { + if (minioninfo->has_chip[chip] && !(minioninfo->changing[chip])) { + head = minioninfo->hchip_list[chip]->head; + if (head) { + howlong = tdiff(&now, &(DATA_HIST(head)->when)); + if (howlong > ((double)MINION_RESET_s * 1.5)) { + // Setup a reset + minioninfo->flag_reset[chip] = true; + minioninfo->do_reset[chip] = 0.0; + } + } + } + } + K_RUNLOCK(minioninfo->hfree_list); +} + +// Results checking thread +static void *minion_results(void *userdata) +{ + struct cgpu_info *minioncgpu = (struct cgpu_info *)userdata; + struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data); + struct thr_info *thr; + int chip = 0, core = 0; + uint32_t task_id = 0; + uint32_t nonce = 0; + bool no_nonce = false; + struct timeval when; + bool another; + uint32_t task_id2 = 0; + uint32_t nonce2 = 0; + int last_check; + + applog(MINION_LOG, "%s%i: Results...", + minioncgpu->drv->name, minioncgpu->device_id); + + // Wait until we're ready + while (minioncgpu->shutdown == false) { + if (minioninfo->initialised) { + break; + } + cgsleep_ms(3); + } + + thr = minioninfo->thr; + + last_check = 0; + while (minioncgpu->shutdown == false) { + if (!oldest_nonce(minioncgpu, &chip, &core, &task_id, &nonce, + &no_nonce, &when, &another, &task_id2, &nonce2)) { + check_last_nonce(minioncgpu); + last_check = 0; + cgsem_mswait(&(minioninfo->nonce_ready), MINION_NONCE_mS); + continue; + } + + oknonce(thr, minioncgpu, chip, core, task_id, nonce, no_nonce, &when, + another, task_id2, nonce2); + + // Interrupt nonce checking if low CPU and oldest_nonce() is always true + if (++last_check > 100) { + check_last_nonce(minioncgpu); + last_check = 0; + } + } + + return NULL; +} + +static void minion_flush_work(struct cgpu_info *minioncgpu) +{ + struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data); + K_ITEM *prev_unused, *task, *prev_task, *witem; + int i; + + if (minioninfo->initialised == false) + return; + + applog(MINION_LOG, "%s%i: flushing work", + minioncgpu->drv->name, minioncgpu->device_id); + + // TODO: N.B. scanwork also gets work locks - which master thread calls flush? + K_WLOCK(minioninfo->wwork_list); + + // Simply remove the whole unused wwork_list + k_list_transfer_to_head(minioninfo->wwork_list, minioninfo->wstale_list); + minioninfo->wwork_flushed += minioninfo->wstale_list->count; + + // TODO: flush/work tasks should have a block sequence number so this task removal code + // might be better implemented in minion_spi_write where each work task would + // update the block sequence number and any work tasks with an old block sequence + // number would be discarded rather than sent - minion_spi_write will also need to + // prioritise flush urgent tasks above work urgent tasks - have 3 urgent states? + // They should however be 2 seperate variables in minioninfo to reduce locking + // - flush will increment one and put it in the flush task, (and work will use that) + // minion_spi_write will check/update the other and thus not need a lock + + // No deadlock since this is the only code to get 2 locks + K_WLOCK(minioninfo->tfree_list); + task = minioninfo->task_list->tail; + while (task) { + prev_task = task->prev; + if (DATA_TASK(task)->address == WRITE_ADDR(MINION_QUE_0)) { + minioninfo->chip_status[DATA_TASK(task)->chip].quework--; +#if MINION_SHOW_IO + applog(IOCTRL_LOG, "QueFlush chip %d cw=%u rw=%u qw-1=%u", + (int)DATA_TASK(task)->chip, + minioninfo->chip_status[DATA_TASK(task)->chip].chipwork, + minioninfo->chip_status[DATA_TASK(task)->chip].realwork, + minioninfo->chip_status[DATA_TASK(task)->chip].quework); +#endif + witem = DATA_TASK(task)->witem; + k_unlink_item(minioninfo->wque_list[DATA_TASK(task)->chip], witem); + minioninfo->wque_flushed++; + if (DATA_WORK(witem)->rolled) + free_work(DATA_WORK(witem)->work); + else + work_completed(minioncgpu, DATA_WORK(witem)->work); + k_free_head(minioninfo->wfree_list, witem); + k_unlink_item(minioninfo->task_list, task); + k_free_head(minioninfo->tfree_list, task); + } + task = prev_task; + } + for (i = 0; i < (int)MINION_CHIPS; i++) { + if (minioninfo->has_chip[i]) { + // TODO: consider sending it now rather than adding to the task list? + task = k_unlink_head(minioninfo->tfree_list); + DATA_TASK(task)->tid = ++(minioninfo->next_tid); + DATA_TASK(task)->chip = i; + DATA_TASK(task)->write = true; + DATA_TASK(task)->address = MINION_SYS_RSTN_CTL; + DATA_TASK(task)->task_id = 0; // ignored + DATA_TASK(task)->wsiz = MINION_SYS_SIZ; + DATA_TASK(task)->rsiz = 0; + DATA_TASK(task)->wbuf[0] = SYS_RSTN_CTL_FLUSH; + DATA_TASK(task)->wbuf[1] = 0; + DATA_TASK(task)->wbuf[2] = 0; + DATA_TASK(task)->wbuf[3] = 0; + DATA_TASK(task)->urgent = true; + k_add_head(minioninfo->task_list, task); + } + } + K_WUNLOCK(minioninfo->tfree_list); + + K_WUNLOCK(minioninfo->wwork_list); + + // TODO: send a signal to force getting and sending new work - needs cgsem_wait in the sending thread + + // TODO: should we use this thread to do the following work? + if (minioninfo->wstale_list->count) { + // mark complete all stale unused work (oldest first) + prev_unused = minioninfo->wstale_list->tail; + while (prev_unused) { + if (DATA_WORK(prev_unused)->rolled) + free_work(DATA_WORK(prev_unused)->work); + else + work_completed(minioncgpu, DATA_WORK(prev_unused)->work); + prev_unused = prev_unused->prev; + } + + // put them back in the wfree_list + K_WLOCK(minioninfo->wfree_list); + k_list_transfer_to_head(minioninfo->wstale_list, minioninfo->wfree_list); + K_WUNLOCK(minioninfo->wfree_list); + } +} + +static void sys_chip_sta(struct cgpu_info *minioncgpu, int chip) +{ + struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data); + struct timeval now; + K_ITEM *item; + int limit, rep; + + cgtime(&now); + // No lock required since 'last' is only accessed here + if (minioninfo->chip_status[chip].last.tv_sec == 0) { + memcpy(&(minioninfo->chip_status[chip].last), &now, sizeof(now)); + } else { + limit = MINION_STATS_UPDATE_TIME_mS + + (int)(random() % MINION_STATS_UPDATE_RAND_mS); + if (ms_tdiff(&now, &(minioninfo->chip_status[chip].last)) > limit) { + memcpy(&(minioninfo->chip_status[chip].last), &now, sizeof(now)); + + K_WLOCK(minioninfo->tfree_list); + item = k_unlink_head(minioninfo->tfree_list); + DATA_TASK(item)->tid = ++(minioninfo->next_tid); + K_WUNLOCK(minioninfo->tfree_list); + + DATA_TASK(item)->chip = chip; + DATA_TASK(item)->write = false; + DATA_TASK(item)->address = READ_ADDR(MINION_SYS_CHIP_STA); + DATA_TASK(item)->task_id = 0; + DATA_TASK(item)->wsiz = 0; + DATA_TASK(item)->rsiz = MINION_SYS_SIZ; + DATA_TASK(item)->urgent = false; + + K_WLOCK(minioninfo->task_list); + k_add_head(minioninfo->task_list, item); + item = k_unlink_head(minioninfo->tfree_list); + DATA_TASK(item)->tid = ++(minioninfo->next_tid); + K_WUNLOCK(minioninfo->task_list); + + DATA_TASK(item)->chip = chip; + DATA_TASK(item)->write = false; + DATA_TASK(item)->address = READ_ADDR(MINION_SYS_IDLE_CNT); + DATA_TASK(item)->task_id = 0; + DATA_TASK(item)->wsiz = 0; + DATA_TASK(item)->rsiz = MINION_SYS_SIZ; + DATA_TASK(item)->urgent = false; + + K_WLOCK(minioninfo->task_list); + k_add_head(minioninfo->task_list, item); + K_WUNLOCK(minioninfo->task_list); + + // Get the core ena and act state + for (rep = 0; rep < MINION_CORE_REPS; rep++) { + // Ena + K_WLOCK(minioninfo->tfree_list); + item = k_unlink_head(minioninfo->tfree_list); + DATA_TASK(item)->tid = ++(minioninfo->next_tid); + K_WUNLOCK(minioninfo->tfree_list); + + DATA_TASK(item)->chip = chip; + DATA_TASK(item)->write = false; + DATA_TASK(item)->address = READ_ADDR(MINION_CORE_ENA0_31 + rep); + DATA_TASK(item)->task_id = 0; + DATA_TASK(item)->wsiz = 0; + DATA_TASK(item)->rsiz = MINION_SYS_SIZ; + DATA_TASK(item)->urgent = false; + + K_WLOCK(minioninfo->task_list); + k_add_head(minioninfo->task_list, item); + // Act + item = k_unlink_head(minioninfo->tfree_list); + DATA_TASK(item)->tid = ++(minioninfo->next_tid); + K_WUNLOCK(minioninfo->task_list); + + DATA_TASK(item)->chip = chip; + DATA_TASK(item)->write = false; + DATA_TASK(item)->address = READ_ADDR(MINION_CORE_ACT0_31 + rep); + DATA_TASK(item)->task_id = 0; + DATA_TASK(item)->wsiz = 0; + DATA_TASK(item)->rsiz = MINION_SYS_SIZ; + DATA_TASK(item)->urgent = false; + + K_WLOCK(minioninfo->task_list); + k_add_head(minioninfo->task_list, item); + K_WUNLOCK(minioninfo->task_list); + } + + if (minioninfo->lednow[chip] != minioninfo->setled[chip]) { + uint32_t led; + + minioninfo->lednow[chip] = minioninfo->setled[chip]; + if (minioninfo->lednow[chip]) + led = MINION_SPI_LED_ON; + else + led = MINION_SPI_LED_OFF; + + K_WLOCK(minioninfo->tfree_list); + item = k_unlink_head(minioninfo->tfree_list); + DATA_TASK(item)->tid = ++(minioninfo->next_tid); + K_WUNLOCK(minioninfo->tfree_list); + + DATA_TASK(item)->chip = chip; + DATA_TASK(item)->write = true; + DATA_TASK(item)->address = MINION_SYS_SPI_LED; + DATA_TASK(item)->task_id = 0; + DATA_TASK(item)->wsiz = MINION_SYS_SIZ; + DATA_TASK(item)->rsiz = 0; + DATA_TASK(item)->wbuf[0] = led & 0xff; + DATA_TASK(item)->wbuf[1] = (led >> 8) & 0xff; + DATA_TASK(item)->wbuf[2] = (led >> 16) & 0xff; + DATA_TASK(item)->wbuf[3] = (led >> 24) & 0xff; + DATA_TASK(item)->urgent = false; + + K_WLOCK(minioninfo->task_list); + k_add_head(minioninfo->task_list, item); + K_WUNLOCK(minioninfo->task_list); + } + } + } +} + +static void new_work_task(struct cgpu_info *minioncgpu, K_ITEM *witem, int chip, bool urgent, uint8_t state) +{ + struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data); + struct minion_que *que; + K_ITEM *item; + + K_WLOCK(minioninfo->tfree_list); + item = k_unlink_head(minioninfo->tfree_list); + DATA_TASK(item)->tid = ++(minioninfo->next_tid); + K_WUNLOCK(minioninfo->tfree_list); + + DATA_TASK(item)->chip = chip; + DATA_TASK(item)->write = true; + DATA_TASK(item)->address = MINION_QUE_0; + + // if threaded access to new_work_task() is added, this will need locking + // Don't use task_id 0 so that we can ignore all '0' work replies + // ... and report them as errors + if (minioninfo->next_task_id == 0) + minioninfo->next_task_id = 1; + DATA_TASK(item)->task_id = minioninfo->next_task_id; + DATA_WORK(witem)->task_id = minioninfo->next_task_id; + minioninfo->next_task_id = (minioninfo->next_task_id + 1) & MINION_MAX_TASK_ID; + + DATA_TASK(item)->urgent = urgent; + DATA_TASK(item)->work_state = state; + DATA_TASK(item)->work = DATA_WORK(witem)->work; + DATA_TASK(item)->witem = witem; + + que = (struct minion_que *)&(DATA_TASK(item)->wbuf[0]); + que->task_id[0] = DATA_TASK(item)->task_id & 0xff; + que->task_id[1] = (DATA_TASK(item)->task_id & 0xff00) >> 8; + + memcpy(&(que->midstate[0]), &(DATA_WORK(witem)->work->midstate[0]), MIDSTATE_BYTES); + memcpy(&(que->merkle7[0]), &(DATA_WORK(witem)->work->data[MERKLE7_OFFSET]), MERKLE_BYTES); + + DATA_TASK(item)->wsiz = (int)sizeof(*que); + DATA_TASK(item)->rsiz = 0; + + K_WLOCK(minioninfo->wque_list[chip]); + k_add_head(minioninfo->wque_list[chip], witem); + minioninfo->chip_status[chip].quework++; +#if MINION_SHOW_IO + applog(IOCTRL_LOG, "Que chip %d cw=%u rw=%u qw+1=%u", + chip, + minioninfo->chip_status[chip].chipwork, + minioninfo->chip_status[chip].realwork, + minioninfo->chip_status[chip].quework); +#endif + K_WUNLOCK(minioninfo->wque_list[chip]); + + K_WLOCK(minioninfo->task_list); + k_add_head(minioninfo->task_list, item); + K_WUNLOCK(minioninfo->task_list); + + if (urgent) + cgsem_post(&(minioninfo->task_ready)); + + // N.B. this will only update often enough if a chip is > ~2GH/s + if (!urgent) + sys_chip_sta(minioncgpu, chip); +} + +// TODO: stale work ... +static K_ITEM *next_work(struct minion_info *minioninfo) +{ + K_ITEM *item; + struct timeval now; + double howlong; + int i; + + K_WLOCK(minioninfo->wwork_list); + item = k_unlink_tail(minioninfo->wwork_list); + K_WUNLOCK(minioninfo->wwork_list); + if (item) { + cgtime(&now); + howlong = tdiff(&now, &(DATA_WORK(item)->created)); + minioninfo->que_work++; + minioninfo->que_time += howlong; + if (minioninfo->que_min == 0 || minioninfo->que_min > howlong) + minioninfo->que_min = howlong; + else if (minioninfo->que_max < howlong) + minioninfo->que_max = howlong; + for (i = 0; i < TIME_BANDS; i++) { + if (howlong < time_bands[i]) { + minioninfo->que_bands[i]++; + break; + } + } + if (i >= TIME_BANDS) + minioninfo->que_bands[TIME_BANDS]++; + } + + return item; +} + +static void minion_do_work(struct cgpu_info *minioncgpu) +{ + struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data); + int count, chip, j, lowcount; + TASK_ITEM fifo_task; + uint8_t state, cmd; + K_ITEM *item; +#if ENABLE_INT_NONO + K_ITEM *task; +#endif + bool islow, sentwork; + + fifo_task.chip = 0; + fifo_task.write = false; + fifo_task.address = MINION_SYS_FIFO_STA; + fifo_task.wsiz = 0; + fifo_task.rsiz = MINION_SYS_SIZ; + + // TODO: (remove this) Fake starved of work to test CMD Interrupt +// if (total_secs > 120) { +// cgsleep_ms(888); +// return; +// } + + /* + * Fill the queues as follows: + * 1) put at least 1 in each queue or if islow then add 1 + * 2) push each queue up to LOW or if count is high but islow, then add LOW-1 + * 3) push each LOW queue up to HIGH + */ + + sentwork = false; + for (state = 0; state < 3; state++) { +#define CHP 0 +//applog(LOG_ERR, "%s%i: chip %d presta %d: quew %d chw %d", minioncgpu->drv->name, minioncgpu->device_id, CHP, state, minioninfo->chip_status[CHP].quework, minioninfo->chip_status[CHP].chipwork); + for (chip = 0; chip < (int)MINION_CHIPS; chip++) + minioninfo->chip_status[chip].tohigh = false; + + for (chip = 0; chip < (int)MINION_CHIPS; chip++) { + if (minioninfo->has_chip[chip] && !minioninfo->chip_status[chip].overheat) { + struct timeval now; + double howlong; + cgtime(&now); + howlong = tdiff(&now, &(minioninfo->last_reset[chip])); + if (howlong < MINION_RESET_DELAY_s) + continue; + + int tries = 0; + while (tries++ < 4) { + cmd = 0; + fifo_task.chip = chip; + fifo_task.reply = 0; + minion_txrx(&fifo_task); + if (fifo_task.reply <= 0) { + if (fifo_task.reply < (int)(fifo_task.osiz)) { + char *buf = bin2hex((unsigned char *)(&(fifo_task.rbuf[fifo_task.osiz - fifo_task.rsiz])), + (int)(fifo_task.rsiz)); + applog(LOG_ERR, "%s%i: Chip %d Bad fifo reply (%s) size %d, should be %d", + minioncgpu->drv->name, minioncgpu->device_id, + chip, buf, + fifo_task.reply, (int)(fifo_task.osiz)); + free(buf); + minioninfo->spi_errors++; + minioninfo->fifo_spi_errors[chip]++; + minioninfo->res_err_count[chip]++; + } else { + if (fifo_task.reply > (int)(fifo_task.osiz)) { + applog(LOG_ERR, "%s%i: Chip %d Unexpected fifo reply size %d, expected only %d", + minioncgpu->drv->name, minioncgpu->device_id, + chip, fifo_task.reply, (int)(fifo_task.osiz)); + } + cmd = FIFO_CMD(fifo_task.rbuf, fifo_task.osiz - fifo_task.rsiz); + // valid reply? + if (cmd < MINION_QUE_MAX) { + K_WLOCK(minioninfo->wchip_list[chip]); + minioninfo->chip_status[chip].realwork = cmd; + K_WUNLOCK(minioninfo->wchip_list[chip]); + if (cmd <= MINION_QUE_LOW || cmd >= MINION_QUE_HIGH) { + applog(LOG_DEBUG, "%s%i: Chip %d fifo cmd %d", + minioncgpu->drv->name, + minioncgpu->device_id, + chip, (int)cmd); + } + break; + } + + applog(LOG_ERR, "%s%i: Chip %d Bad fifo reply cmd %d (max is %d)", + minioncgpu->drv->name, minioncgpu->device_id, + chip, (int)cmd, MINION_QUE_MAX); + minioninfo->spi_errors++; + minioninfo->fifo_spi_errors[chip]++; + minioninfo->res_err_count[chip]++; + } + } + } + + K_WLOCK(minioninfo->wchip_list[chip]); + count = minioninfo->chip_status[chip].quework + + minioninfo->chip_status[chip].realwork; + islow = minioninfo->chip_status[chip].islow; + minioninfo->chip_status[chip].islow = false; + lowcount = minioninfo->chip_status[chip].lowcount; + K_WUNLOCK(minioninfo->wchip_list[chip]); + + switch (state) { + case 0: + if (count == 0 || islow) { + item = next_work(minioninfo); + if (item) { + new_work_task(minioncgpu, item, chip, true, state); + sentwork = true; + applog(MINION_LOG, "%s%i: 0 task 0x%04x in chip %d list", + minioncgpu->drv->name, + minioncgpu->device_id, + DATA_WORK(item)->task_id, chip); + } else { + applog(LOG_ERR, "%s%i: chip %d urgent empty work list", + minioncgpu->drv->name, + minioncgpu->device_id, + chip); + } + } + break; + case 1: + if (count < MINION_QUE_LOW || islow) { + // do case 2: after we've done other chips + minioninfo->chip_status[chip].tohigh = true; + j = count; + if (count >= MINION_QUE_LOW) { + // islow means run a full case 1 + j = 1; + applog(LOG_ERR, "%s%i: chip %d low que (%d) with high count %d", + minioncgpu->drv->name, + minioncgpu->device_id, + chip, lowcount, count); + } + for (; j < MINION_QUE_LOW; j++) { + item = next_work(minioninfo); + if (item) { + new_work_task(minioncgpu, item, chip, false, state); + sentwork = true; + applog(MINION_LOG, "%s%i: 1 task 0x%04x in chip %d list", + minioncgpu->drv->name, + minioncgpu->device_id, + DATA_WORK(item)->task_id, chip); + } else { + applog(LOG_ERR, "%s%i: chip %d non-urgent lo " + "empty work list (count=%d)", + minioncgpu->drv->name, + minioncgpu->device_id, + chip, j); + } + } + } + break; + case 2: + if (count <= MINION_QUE_LOW || minioninfo->chip_status[chip].tohigh) { + for (j = count; j < MINION_QUE_HIGH; j++) { + item = next_work(minioninfo); + if (item) { + new_work_task(minioncgpu, item, chip, false, state); + sentwork = true; + applog(MINION_LOG, "%s%i: 2 task 0x%04x in chip %d list", + minioncgpu->drv->name, + minioncgpu->device_id, + DATA_WORK(item)->task_id, chip); + } else { + applog(LOG_DEBUG, "%s%i: chip %d non-urgent hi " + "empty work list (count=%d)", + minioncgpu->drv->name, + minioncgpu->device_id, + chip, j); + } + } + } + break; + } + } else + if (minioninfo->has_chip[chip] && minioninfo->chip_status[chip].overheat && state == 2) + sys_chip_sta(minioncgpu, chip); + } + } + + sentwork = sentwork; +#if ENABLE_INT_NONO + if (sentwork) { + // Clear CMD interrupt since we've now sent more + K_WLOCK(minioninfo->tfree_list); + task = k_unlink_head(minioninfo->tfree_list); + DATA_TASK(task)->tid = ++(minioninfo->next_tid); + DATA_TASK(task)->chip = 0; // ignored + DATA_TASK(task)->write = true; + DATA_TASK(task)->address = MINION_SYS_INT_CLR; + DATA_TASK(task)->task_id = 0; // ignored + DATA_TASK(task)->wsiz = MINION_SYS_SIZ; + DATA_TASK(task)->rsiz = 0; + DATA_TASK(task)->wbuf[0] = MINION_CMD_INT; + DATA_TASK(task)->wbuf[1] = 0; + DATA_TASK(task)->wbuf[2] = 0; + DATA_TASK(task)->wbuf[3] = 0; + DATA_TASK(task)->urgent = false; + k_add_head(minioninfo->task_list, task); + K_WUNLOCK(minioninfo->tfree_list); + } +#endif + +//applog(LOG_ERR, "%s%i: chip %d fin: quew %d chw %d", minioncgpu->drv->name, minioncgpu->device_id, CHP, minioninfo->chip_status[CHP].quework, minioninfo->chip_status[CHP].chipwork); +} + +static bool minion_thread_prepare(struct thr_info *thr) +{ + struct cgpu_info *minioncgpu = thr->cgpu; + struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data); + + minioninfo->thr = thr; + /* + * SPI/ioctl write thread + */ + if (thr_info_create(&(minioninfo->spiw_thr), NULL, minion_spi_write, (void *)minioncgpu)) { + applog(LOG_ERR, "%s%i: SPI write thread create failed", + minioncgpu->drv->name, minioncgpu->device_id); + return false; + } + pthread_detach(minioninfo->spiw_thr.pth); + + /* + * SPI/ioctl results thread + */ + if (thr_info_create(&(minioninfo->spir_thr), NULL, minion_spi_reply, (void *)minioncgpu)) { + applog(LOG_ERR, "%s%i: SPI reply thread create failed", + minioncgpu->drv->name, minioncgpu->device_id); + return false; + } + pthread_detach(minioninfo->spir_thr.pth); + + /* + * Seperate results checking thread so ioctl timing can ignore the results checking + */ + if (thr_info_create(&(minioninfo->res_thr), NULL, minion_results, (void *)minioncgpu)) { + applog(LOG_ERR, "%s%i: Results thread create failed", + minioncgpu->drv->name, minioncgpu->device_id); + return false; + } + pthread_detach(minioninfo->res_thr.pth); + + return true; +} + +static void minion_shutdown(struct thr_info *thr) +{ + struct cgpu_info *minioncgpu = thr->cgpu; + struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data); + int i; + + applog(MINION_LOG, "%s%i: shutting down", + minioncgpu->drv->name, minioncgpu->device_id); + + for (i = 0; i < (int)MINION_CHIPS; i++) + if (minioninfo->has_chip[i]) +// TODO: minion_shutdown(minioncgpu, minioninfo, i); + i = i; + + minioncgpu->shutdown = true; +} + +static bool minion_queue_full(struct cgpu_info *minioncgpu) +{ + struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data); + struct work *work, *usework; + int count, totneed, need, roll, roll_limit, chip; + bool ret, rolled; + + if (minioninfo->initialised == false) { + cgsleep_us(42); + return true; + } + + K_RLOCK(minioninfo->wwork_list); + count = minioninfo->wwork_list->count; + totneed = 0; + for (chip = 0; chip < (int)MINION_CHIPS; chip++) { + if (minioninfo->has_chip[chip] && + !minioninfo->chip_status[chip].overheat) { + totneed += MINION_QUE_HIGH; + totneed -= minioninfo->chip_status[chip].quework; + totneed -= minioninfo->chip_status[chip].realwork; + // One for the pot :) + totneed++; + } + } + K_RUNLOCK(minioninfo->wwork_list); + + if (count >= totneed) + ret = true; + else { + need = totneed - count; + /* Ensure we do enough rolling to reduce CPU + but dont roll too much to have them end up stale */ + if (need < 16) + need = 16; + work = get_queued(minioncgpu); + if (work) { + roll_limit = work->drv_rolllimit; + roll = 0; + do { + if (roll == 0) { + usework = work; + minioninfo->work_unrolled++; + rolled = false; + } else { + usework = copy_work_noffset(work, roll); + minioninfo->work_rolled++; + rolled = true; + } + ready_work(minioncgpu, usework, rolled); + } while (--need > 0 && ++roll <= roll_limit); + } else { + // Avoid a hard loop when we can't get work fast enough + cgsleep_us(42); + } + + if (need > 0) + ret = false; + else + ret = true; + } + + return ret; +} + +static void idle_report(struct cgpu_info *minioncgpu) +{ + struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data); + struct timeval now; + uint32_t idle; + int msdiff; + int chip; + + for (chip = 0; chip < (int)MINION_CHIPS; chip++) { + if (minioninfo->has_chip[chip]) { + idle = minioninfo->chip_status[chip].idle; + if (idle != minioninfo->chip_status[chip].last_rpt_idle) { + cgtime(&now); + msdiff = ms_tdiff(&now, &(minioninfo->chip_status[chip].idle_rpt)); + if (msdiff >= MINION_IDLE_MESSAGE_ms) { + memcpy(&(minioninfo->chip_status[chip].idle_rpt), &now, sizeof(now)); + applog(LOG_WARNING, + "%s%d: chip %d internal idle changed %08x", + minioncgpu->drv->name, minioncgpu->device_id, + chip, idle); + minioninfo->chip_status[chip].last_rpt_idle = idle; + } + } + } + } +} + +static void chip_report(struct cgpu_info *minioncgpu) +{ + struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data); + struct timeval now; + char buf[512]; + char res_err_msg[2]; + size_t len; + double elapsed, ghs, ghs2, expect, howlong; + char ghs2_display[64]; + K_ITEM *pitem; + int msdiff, chip; + int res_err_count; + + cgtime(&now); + if (!(minioninfo->chip_chk.tv_sec)) { + memcpy(&(minioninfo->chip_chk), &now, sizeof(now)); + memcpy(&(minioninfo->chip_rpt), &now, sizeof(now)); + return; + } + + // Always run the calculations to check chip GHs for the LED + buf[0] = '\0'; + res_err_msg[0] = '\0'; + res_err_msg[1] = '\0'; + K_RLOCK(minioninfo->hfree_list); + for (chip = 0; chip < (int)MINION_CHIPS; chip++) { + if (minioninfo->has_chip[chip]) { + len = strlen(buf); + if (minioninfo->hchip_list[chip]->count < 2) + ghs = 0.0; + else { + ghs = 0xffffffffull * (minioninfo->hchip_list[chip]->count - 1); + ghs /= 1000000000.0; + ghs /= tdiff(&now, &(DATA_HIST(minioninfo->hchip_list[chip]->tail)->when)); + } + if (minioninfo->chip_status[chip].first_nonce.tv_sec == 0L || + tdiff(&now, &minioninfo->chip_status[chip].first_nonce) < MINION_LED_TEST_TIME) { + ghs2_display[0] = '\0'; + minioninfo->setled[chip] = false; + } else { + ghs2 = 0xffffffffull * (minioninfo->chip_status[chip].from_first_good - 1); + ghs2 /= 1000000000.0; + ghs2 /= tdiff(&now, &minioninfo->chip_status[chip].first_nonce); + minioninfo->setled[chip] = (ghs2 >= opt_minion_ledlimit); + snprintf(ghs2_display, sizeof(ghs2_display), "[%.2f]", ghs2); + } + + res_err_count = minioninfo->res_err_count[chip]; + minioninfo->res_err_count[chip] = 0; + if (res_err_count > 100) + res_err_msg[0] = '!'; + else if (res_err_count > 50) + res_err_msg[0] = '*'; + else if (res_err_count > 0) + res_err_msg[0] = '\''; + else + res_err_msg[0] = '\0'; + snprintf(buf + len, sizeof(buf) - len, + " %d=%s%.2f%s", chip, res_err_msg, ghs, ghs2_display); + minioninfo->history_ghs[chip] = ghs; + } + } + K_RUNLOCK(minioninfo->hfree_list); + + // But only display it if required + if (opt_minion_chipreport > 0) { + msdiff = ms_tdiff(&now, &(minioninfo->chip_rpt)); + if (msdiff >= (opt_minion_chipreport * 1000)) { + memcpy(&(minioninfo->chip_chk), &now, sizeof(now)); + applogsiz(LOG_WARNING, 512, + "%s%d: Chip GHs%s", + minioncgpu->drv->name, minioncgpu->device_id, buf); + memcpy(&(minioninfo->chip_rpt), &now, sizeof(now)); + } + } + + msdiff = ms_tdiff(&now, &(minioninfo->chip_chk)); + if (total_secs >= MINION_RESET_s && msdiff >= (minioninfo->history_gen * 1000)) { + K_RLOCK(minioninfo->hfree_list); + for (chip = 0; chip < (int)MINION_CHIPS; chip++) { + if (minioninfo->has_chip[chip]) { + // Don't reset the chip while 'changing' + if (minioninfo->changing[chip]) + continue; + + if (!minioninfo->reset_mark[chip] || + minioninfo->reset_count[chip] < 2) { + elapsed = 0.0; + ghs = 0.0; + } else { + // 'now' includes that it may have stopped getting nonces + elapsed = tdiff(&now, &(DATA_HIST(minioninfo->reset_mark[chip])->when)); + ghs = 0xffffffffull * (minioninfo->reset_count[chip] - 1); + ghs /= 1000000000.0; + ghs /= elapsed; + } + expect = (double)(minioninfo->init_freq[chip]) * + MINION_RESET_PERCENT / 1000.0; + howlong = tdiff(&now, &(minioninfo->last_reset[chip])); + if (ghs <= expect && howlong >= minioninfo->reset_time[chip]) { + minioninfo->do_reset[chip] = expect; + + // For now - no lock required since no other code accesses it + pitem = k_unlink_head(minioninfo->pfree_list); + DATA_PERF(pitem)->elapsed = elapsed; + DATA_PERF(pitem)->nonces = minioninfo->reset_count[chip] - 1; + DATA_PERF(pitem)->freq = minioninfo->init_freq[chip]; + DATA_PERF(pitem)->ghs = ghs; + memcpy(&(DATA_PERF(pitem)->when), &now, sizeof(now)); + k_add_head(minioninfo->p_list[chip], pitem); + } else if (second_check) { + expect = (double)(minioninfo->init_freq[chip]) * + MINION_RESET2_PERCENT / 1000.0; + if (ghs < expect && howlong >= minioninfo->reset2_time[chip]) { + /* Only do a reset, don't record it, since the ghs + is still above MINION_RESET_PERCENT */ + minioninfo->do_reset[chip] = expect; + } + } + minioninfo->history_ghs[chip] = ghs; + // Expire old perf items to stop clockdown + if (minioninfo->do_reset[chip] <= 1.0 && howlong > MINION_CLR_s) { + // Always remember the last reset + while (minioninfo->p_list[chip]->count > 1) { + pitem = k_unlink_tail(minioninfo->p_list[chip]); + k_add_head(minioninfo->pfree_list, pitem); + } + } + } + } + K_RUNLOCK(minioninfo->hfree_list); + + memcpy(&(minioninfo->chip_chk), &now, sizeof(now)); + } + + for (chip = 0; chip < (int)MINION_CHIPS; chip++) { + if (minioninfo->has_chip[chip]) { + // Don't reset the chip while 'changing' + if (minioninfo->changing[chip]) + continue; + + if (minioninfo->do_reset[chip] > 1.0 || + minioninfo->flag_reset[chip]) { + bool std_reset = true; + int curr_freq = minioninfo->init_freq[chip]; + int new_freq = 0.0; + int count; + + // Adjust frequency down? + if (!opt_minion_noautofreq && + minioninfo->p_list[chip]->count >= MINION_RESET_COUNT) { + pitem = minioninfo->p_list[chip]->head; + count = 1; + while (pitem && pitem->next && count++ < MINION_RESET_COUNT) { + if (DATA_PERF(pitem)->freq != DATA_PERF(pitem->next)->freq) + break; + if (count >= MINION_RESET_COUNT) { + new_freq = minioninfo->init_freq[chip] - + MINION_FREQ_RESET_STEP; + if (new_freq < MINION_FREQ_MIN) + new_freq = MINION_FREQ_MIN; + if (minioninfo->init_freq[chip] != new_freq) { + minioninfo->init_freq[chip] = new_freq; + std_reset = false; + } + break; + } else + pitem = pitem->next; + } + } + + if (std_reset) { + if (minioninfo->do_reset[chip] > 1.0) { + applog(LOG_WARNING, "%s%d: Chip %d %dMHz threshold " + "%.2fGHs - resetting", + minioncgpu->drv->name, + minioncgpu->device_id, + chip, curr_freq, + minioninfo->do_reset[chip]); + } else { + applog(LOG_WARNING, "%s%d: Chip %d %dMhz flagged - " + "resetting", + minioncgpu->drv->name, + minioncgpu->device_id, + chip, curr_freq); + } + } else { + if (minioninfo->do_reset[chip] > 1.0) { + applog(LOG_WARNING, "%s%d: Chip %d %dMHz threshold " + "%.2fGHs - resetting to %dMhz", + minioncgpu->drv->name, + minioncgpu->device_id, + chip, curr_freq, + minioninfo->do_reset[chip], + new_freq); + } else { + applog(LOG_WARNING, "%s%d: Chip %d %dMhz flagged - " + "resetting to %dMHz", + minioncgpu->drv->name, + minioncgpu->device_id, + chip, curr_freq, new_freq); + } + } + minioninfo->do_reset[chip] = 0.0; + memcpy(&(minioninfo->last_reset[chip]), &now, sizeof(now)); + init_chip(minioncgpu, minioninfo, chip); + minioninfo->flag_reset[chip] = false; + } + } + } +} + +static int64_t minion_scanwork(__maybe_unused struct thr_info *thr) +{ + struct cgpu_info *minioncgpu = thr->cgpu; + struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data); + int64_t hashcount = 0; + + if (minioninfo->initialised == false) + return hashcount; + + minion_do_work(minioncgpu); + + mutex_lock(&(minioninfo->nonce_lock)); + if (minioninfo->new_nonces) { + hashcount += 0xffffffffull * minioninfo->new_nonces; + minioninfo->new_nonces = 0; + } + mutex_unlock(&(minioninfo->nonce_lock)); + + if (opt_minion_idlecount) + idle_report(minioncgpu); + + // Must always generate data to check/allow for chip reset + chip_report(minioncgpu); + + /* + * To avoid wasting CPU, wait until we get an interrupt + * before returning back to the main cgminer work loop + * i.e. we then know we'll need more work + */ + cgsem_mswait(&(minioninfo->scan_work), MINION_SCAN_mS); + + return hashcount; +} + +static const char *temp_str(uint16_t temp) +{ + switch (temp) { + case MINION_TEMP_40: + return min_temp_40; + case MINION_TEMP_60: + return min_temp_60; + case MINION_TEMP_80: + return min_temp_80; + case MINION_TEMP_100: + return min_temp_100; + case MINION_TEMP_OVER: + return min_temp_over; + } + return min_temp_invalid; +} + +static void minion_get_statline_before(char *buf, size_t bufsiz, struct cgpu_info *minioncgpu) +{ + struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data); + uint16_t max_temp, cores; + int chip, core; + + max_temp = 0; + cores = 0; + mutex_lock(&(minioninfo->sta_lock)); + for (chip = 0; chip < (int)MINION_CHIPS; chip++) { + if (minioninfo->has_chip[chip]) { + if (max_temp < minioninfo->chip_status[chip].temp) + max_temp = minioninfo->chip_status[chip].temp; + for (core = 0; core < MINION_CORES; core++) { + if (minioninfo->chip_core_ena[core >> 5][chip] & (0x1 << (core % 32))) + cores++; + } + } + } + mutex_unlock(&(minioninfo->sta_lock)); + + tailsprintf(buf, bufsiz, "max%sC Ch:%d Co:%d", + temp_str(max_temp), minioninfo->chips, (int)cores); +} + +#define CHIPS_PER_STAT 5 + +static struct api_data *minion_api_stats(struct cgpu_info *minioncgpu) +{ + struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data); + struct api_data *root = NULL; + char cores[MINION_CORES+1]; + char data[2048]; + char buf[32]; + int i, to, j; + size_t datalen, nlen; + int chip, max_chip, que_work, chip_work, temp; + + if (minioninfo->initialised == false) + return NULL; + + root = api_add_uint64(root, "OK Nonces", &(minioninfo->ok_nonces), true); + root = api_add_uint64(root, "New Nonces", &(minioninfo->new_nonces), true); + root = api_add_uint64(root, "Tested Nonces", &(minioninfo->tested_nonces), true); + root = api_add_uint64(root, "Untested Nonces", &(minioninfo->untested_nonces), true); + + root = api_add_int(root, "Chips", &(minioninfo->chips), true); + i = MINION_PIN_COUNT; + root = api_add_int(root, "GPIO Pins", &i, true); + + max_chip = 0; + for (chip = 0; chip < (int)MINION_CHIPS; chip++) + if (minioninfo->has_chip[chip]) { + max_chip = chip; + + snprintf(buf, sizeof(buf), "Chip %d Pin", chip); + root = api_add_int(root, buf, &(minioninfo->chip_pin[chip]), true); + snprintf(buf, sizeof(buf), "Chip %d ChipID", chip); + i = (int)(minioninfo->chipid[chip]); + root = api_add_int(root, buf, &i, true); + snprintf(buf, sizeof(buf), "Chip %d Temperature", chip); + root = api_add_const(root, buf, temp_str(minioninfo->chip_status[chip].temp), false); + snprintf(buf, sizeof(buf), "Chip %d Cores", chip); + root = api_add_uint16(root, buf, &(minioninfo->chip_status[chip].cores), true); + snprintf(buf, sizeof(buf), "Chip %d Frequency", chip); + root = api_add_uint32(root, buf, &(minioninfo->chip_status[chip].freq), true); + snprintf(buf, sizeof(buf), "Chip %d InitFreq", chip); + root = api_add_int(root, buf, &(minioninfo->init_freq[chip]), true); + snprintf(buf, sizeof(buf), "Chip %d FreqSent", chip); + root = api_add_hex32(root, buf, &(minioninfo->freqsent[chip]), true); + snprintf(buf, sizeof(buf), "Chip %d InitTemp", chip); + temp = minioninfo->init_temp[chip]; + if (temp == MINION_TEMP_CTL_DISABLE) + root = api_add_string(root, buf, MINION_TEMP_DISABLE, true); + else { + snprintf(data, sizeof(data), "%d", temp); + root = api_add_string(root, buf, data, true); + } + snprintf(buf, sizeof(buf), "Chip %d TempSent", chip); + root = api_add_hex32(root, buf, &(minioninfo->chip_status[chip].tempsent), true); + __bin2hex(data, (unsigned char *)(&(minioninfo->init_cores[chip][0])), + sizeof(minioninfo->init_cores[chip])); + snprintf(buf, sizeof(buf), "Chip %d InitCores", chip); + root = api_add_string(root, buf, data, true); + snprintf(buf, sizeof(buf), "Chip %d IdleCount", chip); + root = api_add_hex32(root, buf, &(minioninfo->chip_status[chip].idle), true); + snprintf(buf, sizeof(buf), "Chip %d QueWork", chip); + root = api_add_uint32(root, buf, &(minioninfo->chip_status[chip].quework), true); + snprintf(buf, sizeof(buf), "Chip %d ChipWork", chip); + root = api_add_uint32(root, buf, &(minioninfo->chip_status[chip].chipwork), true); + snprintf(buf, sizeof(buf), "Chip %d RealWork", chip); + root = api_add_uint32(root, buf, &(minioninfo->chip_status[chip].realwork), true); + snprintf(buf, sizeof(buf), "Chip %d QueListCount", chip); + root = api_add_int(root, buf, &(minioninfo->wque_list[chip]->count), true); + snprintf(buf, sizeof(buf), "Chip %d WorkListCount", chip); + root = api_add_int(root, buf, &(minioninfo->wchip_list[chip]->count), true); + snprintf(buf, sizeof(buf), "Chip %d Overheat", chip); + root = api_add_bool(root, buf, &(minioninfo->chip_status[chip].overheat), true); + snprintf(buf, sizeof(buf), "Chip %d Overheats", chip); + root = api_add_uint32(root, buf, &(minioninfo->chip_status[chip].overheats), true); + snprintf(buf, sizeof(buf), "Chip %d LastOverheat", chip); + root = api_add_timeval(root, buf, &(minioninfo->chip_status[chip].lastoverheat), true); + snprintf(buf, sizeof(buf), "Chip %d LastRecover", chip); + root = api_add_timeval(root, buf, &(minioninfo->chip_status[chip].lastrecover), true); + snprintf(buf, sizeof(buf), "Chip %d OverheatIdle", chip); + root = api_add_double(root, buf, &(minioninfo->chip_status[chip].overheattime), true); + for (i = 0; i < MINION_CORES; i++) { + if (minioninfo->chip_core_ena[i >> 5][chip] & (0x1 << (i % 32))) + cores[i] = 'o'; + else + cores[i] = 'x'; + } + cores[MINION_CORES] = '\0'; + snprintf(buf, sizeof(buf), "Chip %d CoresEna", chip); + root = api_add_string(root, buf, cores, true); + for (i = 0; i < MINION_CORES; i++) { + if (minioninfo->chip_core_act[i >> 5][chip] & (0x1 << (i % 32))) + cores[i] = '-'; + else + cores[i] = 'o'; + } + cores[MINION_CORES] = '\0'; + snprintf(buf, sizeof(buf), "Chip %d CoresAct", chip); + root = api_add_string(root, buf, cores, true); + + if (opt_minion_extra) { + data[0] = '\0'; + datalen = 0; + for (i = 0; i < MINION_CORES; i++) { + if (datalen < sizeof(data)) { + nlen = snprintf(data+datalen, sizeof(data)-datalen, + "%s%"PRIu64"-%s%"PRIu64, + i == 0 ? "" : "/", + minioninfo->core_good[chip][i], + minioninfo->core_bad[chip][i] ? "'" : "", + minioninfo->core_bad[chip][i]); + if (nlen < 1) + break; + datalen += nlen; + } + } + snprintf(buf, sizeof(buf), "Chip %d Cores Good-Bad", chip); + root = api_add_string(root, buf, data, true); + } + + snprintf(buf, sizeof(buf), "Chip %d History GHs", chip); + root = api_add_mhs(root, buf, &(minioninfo->history_ghs[chip]), true); + } + + double his = MINION_HISTORY_s; + root = api_add_double(root, "History length", &his, true); + his = MINION_RESET_s; + root = api_add_double(root, "Default reset length", &his, true); + his = MINION_RESET2_s; + root = api_add_double(root, "Default reset2 length", &his, true); + root = api_add_bool(root, "Reset2 enabled", &second_check, true); + + for (i = 0; i <= max_chip; i += CHIPS_PER_STAT) { + to = i + CHIPS_PER_STAT - 1; + if (to > max_chip) + to = max_chip; + + data[0] = '\0'; + for (j = i; j <= to; j++) { + snprintf(buf, sizeof(buf), + "%s%d", + j == i ? "" : " ", + minioninfo->has_chip[j] ? 1 : 0); + strcat(data, buf); + } + snprintf(buf, sizeof(buf), "Detected %02d - %02d", i, to); + root = api_add_string(root, buf, data, true); + + data[0] = '\0'; + for (j = i; j <= to; j++) { + snprintf(buf, sizeof(buf), + "%s%8"PRIu64, + j == i ? "" : " ", + minioninfo->chip_nonces[j]); + strcat(data, buf); + } + snprintf(buf, sizeof(buf), "Nonces %02d - %02d", i, to); + root = api_add_string(root, buf, data, true); + + data[0] = '\0'; + for (j = i; j <= to; j++) { + snprintf(buf, sizeof(buf), + "%s%8"PRIu64, + j == i ? "" : " ", + minioninfo->chip_nononces[j]); + strcat(data, buf); + } + snprintf(buf, sizeof(buf), "NoNonces %02d - %02d", i, to); + root = api_add_string(root, buf, data, true); + + data[0] = '\0'; + for (j = i; j <= to; j++) { + snprintf(buf, sizeof(buf), + "%s%8"PRIu64, + j == i ? "" : " ", + minioninfo->chip_good[j]); + strcat(data, buf); + } + snprintf(buf, sizeof(buf), "Good %02d - %02d", i, to); + root = api_add_string(root, buf, data, true); + + data[0] = '\0'; + for (j = i; j <= to; j++) { + snprintf(buf, sizeof(buf), + "%s%8"PRIu64, + j == i ? "" : " ", + minioninfo->chip_bad[j]); + strcat(data, buf); + } + snprintf(buf, sizeof(buf), "Bad %02d - %02d", i, to); + root = api_add_string(root, buf, data, true); + + data[0] = '\0'; + for (j = i; j <= to; j++) { + snprintf(buf, sizeof(buf), + "%s%8"PRIu64, + j == i ? "" : " ", + minioninfo->chip_err[j]); + strcat(data, buf); + } + snprintf(buf, sizeof(buf), "Err %02d - %02d", i, to); + root = api_add_string(root, buf, data, true); + + data[0] = '\0'; + for (j = i; j <= to; j++) { + snprintf(buf, sizeof(buf), + "%s%8"PRIu64, + j == i ? "" : " ", + minioninfo->fifo_spi_errors[j]); + strcat(data, buf); + } + snprintf(buf, sizeof(buf), "FifoSpiErr %02d - %02d", i, to); + root = api_add_string(root, buf, data, true); + + data[0] = '\0'; + for (j = i; j <= to; j++) { + snprintf(buf, sizeof(buf), + "%s%8"PRIu64, + j == i ? "" : " ", + minioninfo->res_spi_errors[j]); + strcat(data, buf); + } + snprintf(buf, sizeof(buf), "ResSpiErr %02d - %02d", i, to); + root = api_add_string(root, buf, data, true); + + data[0] = '\0'; + for (j = i; j <= to; j++) { + snprintf(buf, sizeof(buf), + "%s%"PRIu64"/%"PRIu64"/%"PRIu64"/%"PRIu64"/%"PRIu64, + j == i ? "" : " ", + minioninfo->use_res2[j], + minioninfo->tasks_failed[j], + minioninfo->tasks_recovered[j], + minioninfo->nonces_failed[j], + minioninfo->nonces_recovered[j]); + strcat(data, buf); + } + snprintf(buf, sizeof(buf), "Redo %02d - %02d", i, to); + root = api_add_string(root, buf, data, true); + } + + que_work = chip_work = 0; + for (chip = 0; chip <= max_chip; chip++) { + if (minioninfo->has_chip[chip]) { + que_work += minioninfo->wque_list[chip]->count; + chip_work += minioninfo->wchip_list[chip]->count; + } + } + + root = api_add_int(root, "WFree Total", &(minioninfo->wfree_list->total), true); + root = api_add_int(root, "WFree Count", &(minioninfo->wfree_list->count), true); + root = api_add_int(root, "WWork Count", &(minioninfo->wwork_list->count), true); + root = api_add_uint64(root, "WWork Flushed", &(minioninfo->wwork_flushed), true); + root = api_add_int(root, "WQue Count", &que_work, true); + root = api_add_uint64(root, "WQue Flushed", &(minioninfo->wque_flushed), true); + root = api_add_int(root, "WChip Count", &chip_work, true); + root = api_add_uint64(root, "WChip Stale", &(minioninfo->wchip_staled), true); + + root = api_add_int(root, "TFree Total", &(minioninfo->tfree_list->total), true); + root = api_add_int(root, "TFree Count", &(minioninfo->tfree_list->count), true); + root = api_add_int(root, "Task Count", &(minioninfo->task_list->count), true); + root = api_add_int(root, "Reply Count", &(minioninfo->treply_list->count), true); + + root = api_add_int(root, "RFree Total", &(minioninfo->rfree_list->total), true); + root = api_add_int(root, "RFree Count", &(minioninfo->rfree_list->count), true); + root = api_add_int(root, "RNonce Count", &(minioninfo->rnonce_list->count), true); + + root = api_add_int(root, "XFree Count", &(minioninfo->xfree_list->count), true); + root = api_add_int(root, "XFF Count", &(minioninfo->xff_list->count), true); + root = api_add_uint64(root, "XFFs", &(minioninfo->xffs), true); + root = api_add_uint64(root, "SPI Resets", &(minioninfo->spi_resets), true); + root = api_add_uint64(root, "Power Cycles", &(minioninfo->power_cycles), true); + + root = api_add_int(root, "Chip Report", &opt_minion_chipreport, true); + root = api_add_int(root, "LED Count", &opt_minion_ledcount, true); + root = api_add_int(root, "LED Limit", &opt_minion_ledlimit, true); + bool b = !opt_minion_noautofreq; + root = api_add_bool(root, "Auto Freq", &b, true); + root = api_add_int(root, "SPI Delay", &opt_minion_spidelay, true); + root = api_add_bool(root, "SPI Reset I/O", &(minioninfo->spi_reset_io), true); + root = api_add_int(root, "SPI Reset", &(minioninfo->spi_reset_count), true); + root = api_add_int(root, "SPI Reset Sleep", &opt_minion_spisleep, true); + +#if DO_IO_STATS +#define sta_api(_name, _iostat) \ + do { \ + if ((_iostat).count) { \ + float _davg = (float)((_iostat).total_delay) / (float)((_iostat).count); \ + float _dlavg = (float)((_iostat).total_dlock) / (float)((_iostat).count); \ + float _dlwavg = (float)((_iostat).total_dlwait) / (float)((_iostat).count); \ + float _bavg = (float)((_iostat).total_bytes) / (float)((_iostat).count); \ + float _tavg = (float)((_iostat).tsd) / (float)((_iostat).count); \ + snprintf(data, sizeof(data), "%s Count=%"PRIu64 \ + " Delay=%.0fus DAvg=%.3f" \ + " DMin=%.0f DMax=%.0f DZ=%"PRIu64 \ + " DLock=%.0fus DLAvg=%.3f" \ + " DLMin=%.0f DLMax=%.0f DZ=%"PRIu64 \ + " DLWait=%.0fus DLWAvg=%.3f" \ + " Bytes=%"PRIu64" BAvg=%.3f" \ + " BMin=%"PRIu64" BMax=%"PRIu64" BZ=%"PRIu64 \ + " TSD=%.0fus TAvg=%.03f", \ + _name, (_iostat).count, \ + (_iostat).total_delay, _davg, (_iostat).min_delay, \ + (_iostat).max_delay, (_iostat).zero_delay, \ + (_iostat).total_dlock, _dlavg, (_iostat).min_dlock, \ + (_iostat).max_dlock, (_iostat).zero_dlock, \ + (_iostat).total_dlwait, _dlwavg, \ + (_iostat).total_bytes, _bavg, (_iostat).min_bytes, \ + (_iostat).max_bytes, (_iostat).zero_bytes, \ + (_iostat).tsd, _tavg); \ + root = api_add_string(root, buf, data, true); \ + } \ + } while(0); + + for (i = 0; i < 0x200; i++) { + snprintf(buf, sizeof(buf), "Stat-0x%02x", i); + sta_api(addr2txt((uint8_t)(i & 0xff)), minioninfo->iostats[i]); + } + + // Test to avoid showing applog + if (minioninfo->summary.count) { + snprintf(buf, sizeof(buf), "Stat-S"); + sta_api("Summary", minioninfo->summary); + applog(LOG_WARNING, "%s %d: (%.0f) %s - %s", + minioncgpu->drv->name, minioncgpu->device_id, + total_secs, buf, data); + } +#endif + + root = api_add_uint64(root, "Total SPI Errors", &(minioninfo->spi_errors), true); + root = api_add_uint64(root, "Work Unrolled", &(minioninfo->work_unrolled), true); + root = api_add_uint64(root, "Work Rolled", &(minioninfo->work_rolled), true); + root = api_add_uint64(root, "Ints", &(minioninfo->interrupts), true); + root = api_add_uint64(root, "Res Ints", &(minioninfo->result_interrupts), true); + root = api_add_uint64(root, "Cmd Ints", &(minioninfo->command_interrupts), true); + root = api_add_string(root, "Last Int", minioninfo->last_interrupt, true); + root = api_add_hex32(root, "Next TaskID", &(minioninfo->next_task_id), true); + + double avg; + root = api_add_uint64(root, "ToQue", &(minioninfo->que_work), true); + if (minioninfo->que_work) + avg = minioninfo->que_time / (double)(minioninfo->que_work); + else + avg = 0; + root = api_add_double(root, "Que Avg", &avg, true); + root = api_add_double(root, "Que Min", &(minioninfo->que_min), true); + root = api_add_double(root, "Que Max", &(minioninfo->que_max), true); + data[0] = '\0'; + for (i = 0; i <= TIME_BANDS; i++) { + snprintf(buf, sizeof(buf), + "%s%"PRIu64, + i == 0 ? "" : "/", + minioninfo->que_bands[i]); + strcat(data, buf); + } + root = api_add_string(root, "Que Bands", data, true); + + root = api_add_uint64(root, "ToTxRx", &(minioninfo->wt_work), true); + if (minioninfo->wt_work) + avg = minioninfo->wt_time / (double)(minioninfo->wt_work); + else + avg = 0; + root = api_add_double(root, "TxRx Avg", &avg, true); + root = api_add_double(root, "TxRx Min", &(minioninfo->wt_min), true); + root = api_add_double(root, "TxRx Max", &(minioninfo->wt_max), true); + data[0] = '\0'; + for (i = 0; i <= TIME_BANDS; i++) { + snprintf(buf, sizeof(buf), + "%s%"PRIu64, + i == 0 ? "" : "/", + minioninfo->wt_bands[i]); + strcat(data, buf); + } + root = api_add_string(root, "TxRx Bands", data, true); + + uint64_t checked, dups; + dupcounters(minioncgpu, &checked, &dups); + root = api_add_uint64(root, "Dups", &dups, true); + + return root; +} +#endif + +struct device_drv minion_drv = { + .drv_id = DRIVER_minion, + .dname = "Minion BlackArrow", + .name = "MBA", + .drv_detect = minion_detect, +#ifdef LINUX + .get_api_stats = minion_api_stats, + .get_statline_before = minion_get_statline_before, + .set_device = minion_api_set, + .identify_device = minion_identify, + .thread_prepare = minion_thread_prepare, + .hash_work = hash_queued_work, + .scanwork = minion_scanwork, + .queue_full = minion_queue_full, + .flush_work = minion_flush_work, + .thread_shutdown = minion_shutdown +#endif +}; diff --git a/driver-modminer.c b/driver-modminer.c new file mode 100644 index 0000000..a7fb856 --- /dev/null +++ b/driver-modminer.c @@ -0,0 +1,1141 @@ +/* + * Copyright 2012-2013 Andrew Smith + * Copyright 2012 Luke Dashjr + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "logging.h" +#include "miner.h" +#include "usbutils.h" +#include "fpgautils.h" +#include "util.h" + +#define BITSTREAM_FILENAME "fpgaminer_top_fixed7_197MHz.ncd" +#define BISTREAM_USER_ID "\2\4$B" + +#define BITSTREAM_MAGIC_0 0 +#define BITSTREAM_MAGIC_1 9 + +#define MODMINER_CUTOFF_TEMP 60.0 +#define MODMINER_OVERHEAT_TEMP 50.0 +#define MODMINER_RECOVER_TEMP 46.5 +#define MODMINER_TEMP_UP_LIMIT 47.0 + +#define MODMINER_HW_ERROR_PERCENT 0.75 + +// How many seconds of no nonces means there's something wrong +// First time - drop the clock and see if it revives +// Second time - (and it didn't revive) disable it +#define ITS_DEAD_JIM 300 + +// N.B. in the latest firmware the limit is 250 +// however the voltage/temperature risks preclude that +#define MODMINER_MAX_CLOCK 230 +#define MODMINER_DEF_CLOCK 200 +#define MODMINER_MIN_CLOCK 160 + +#define MODMINER_CLOCK_UP 2 +#define MODMINER_CLOCK_SET 0 +#define MODMINER_CLOCK_DOWN -2 +// = 0 means OVERHEAT doesn't affect the clock +#define MODMINER_CLOCK_OVERHEAT 0 +#define MODMINER_CLOCK_DEAD -6 +#define MODMINER_CLOCK_CUTOFF -10 + +// Commands +#define MODMINER_PING "\x00" +#define MODMINER_GET_VERSION "\x01" +#define MODMINER_FPGA_COUNT "\x02" +// Commands + require FPGAid +#define MODMINER_GET_IDCODE '\x03' +#define MODMINER_GET_USERCODE '\x04' +#define MODMINER_PROGRAM '\x05' +#define MODMINER_SET_CLOCK '\x06' +#define MODMINER_READ_CLOCK '\x07' +#define MODMINER_SEND_WORK '\x08' +#define MODMINER_CHECK_WORK '\x09' +// One byte temperature reply +#define MODMINER_TEMP1 '\x0a' +// Two byte temperature reply +#define MODMINER_TEMP2 '\x0d' + +// +6 bytes +#define MODMINER_SET_REG '\x0b' +// +2 bytes +#define MODMINER_GET_REG '\x0c' + +#define FPGAID_ALL 4 + +// Maximum how many good shares in a row means clock up +// 96 is ~34m22s at 200MH/s +#define MODMINER_TRY_UP 96 +// Initially how many good shares in a row means clock up +// This is doubled each down clock until it reaches MODMINER_TRY_UP +// 6 is ~2m9s at 200MH/s +#define MODMINER_EARLY_UP 6 +// Limit when reducing shares_to_good +#define MODMINER_MIN_BACK 12 + +// 45 noops sent when detecting, in case the device was left in "start job" reading +static const char NOOP[] = MODMINER_PING "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"; + +static void do_ping(struct cgpu_info *modminer) +{ + char buf[0x100+1]; + int err, amount; + + // Don't care if it fails + err = usb_write(modminer, (char *)NOOP, sizeof(NOOP)-1, &amount, C_PING); + applog(LOG_DEBUG, "%s%u: flush noop got %d err %d", + modminer->drv->name, modminer->fpgaid, amount, err); + + // Clear any outstanding data + while ((err = usb_read_once(modminer, buf, sizeof(buf)-1, &amount, C_CLEAR)) == 0 && amount > 0) + applog(LOG_DEBUG, "%s%u: clear got %d", + modminer->drv->name, modminer->fpgaid, amount); + + applog(LOG_DEBUG, "%s%u: final clear got %d err %d", + modminer->drv->name, modminer->fpgaid, amount, err); +} + +static struct cgpu_info *modminer_detect_one(struct libusb_device *dev, struct usb_find_devices *found) +{ + char buf[0x100+1]; + char *devname = NULL; + char devpath[32]; + int err, i, amount; + bool added = false; + + struct cgpu_info *modminer = usb_alloc_cgpu(&modminer_drv, 1); + + modminer->modminer_mutex = calloc(1, sizeof(*(modminer->modminer_mutex))); + mutex_init(modminer->modminer_mutex); + modminer->fpgaid = (char)0; + + if (!usb_init(modminer, dev, found)) + goto shin; + + do_ping(modminer); + + if ((err = usb_write(modminer, MODMINER_GET_VERSION, 1, &amount, C_REQUESTVERSION)) < 0 || amount != 1) { + applog(LOG_ERR, "%s detect (%s) send version request failed (%d:%d)", + modminer->drv->dname, modminer->device_path, amount, err); + goto unshin; + } + + if ((err = usb_read_once(modminer, buf, sizeof(buf)-1, &amount, C_GETVERSION)) < 0 || amount < 1) { + if (err < 0) + applog(LOG_ERR, "%s detect (%s) no version reply (%d)", + modminer->drv->dname, modminer->device_path, err); + else + applog(LOG_ERR, "%s detect (%s) empty version reply (%d)", + modminer->drv->dname, modminer->device_path, amount); + + applog(LOG_DEBUG, "%s detect (%s) check the firmware", + modminer->drv->dname, modminer->device_path); + + goto unshin; + } + buf[amount] = '\0'; + devname = strdup(buf); + applog(LOG_DEBUG, "%s (%s) identified as: %s", modminer->drv->dname, modminer->device_path, devname); + + if ((err = usb_write(modminer, MODMINER_FPGA_COUNT, 1, &amount, C_REQUESTFPGACOUNT) < 0 || amount != 1)) { + applog(LOG_ERR, "%s detect (%s) FPGA count request failed (%d:%d)", + modminer->drv->dname, modminer->device_path, amount, err); + goto unshin; + } + + if ((err = usb_read(modminer, buf, 1, &amount, C_GETFPGACOUNT)) < 0 || amount != 1) { + applog(LOG_ERR, "%s detect (%s) no FPGA count reply (%d:%d)", + modminer->drv->dname, modminer->device_path, amount, err); + goto unshin; + } + + // TODO: flag it use 1 byte temp if it is an old firmware + // can detect with modminer->cgusb->serial ? + + if (buf[0] == 0) { + applog(LOG_ERR, "%s detect (%s) zero FPGA count from %s", + modminer->drv->dname, modminer->device_path, devname); + goto unshin; + } + + if (buf[0] < 1 || buf[0] > 4) { + applog(LOG_ERR, "%s detect (%s) invalid FPGA count (%u) from %s", + modminer->drv->dname, modminer->device_path, buf[0], devname); + goto unshin; + } + + applog(LOG_DEBUG, "%s (%s) %s has %u FPGAs", + modminer->drv->dname, modminer->device_path, devname, buf[0]); + + modminer->name = devname; + + // TODO: test with 1 board missing in the middle and each end + // to see how that affects the sequence numbers + for (i = 0; i < buf[0]; i++) { + struct cgpu_info *tmp = usb_copy_cgpu(modminer); + + sprintf(devpath, "%d:%d:%d", + (int)(modminer->usbinfo.bus_number), + (int)(modminer->usbinfo.device_address), + i); + + tmp->device_path = strdup(devpath); + + // Only the first copy gets the already used stats + if (added) + tmp->usbinfo.usbstat = USB_NOSTAT; + + tmp->fpgaid = (char)i; + tmp->modminer_mutex = modminer->modminer_mutex; + + if (!add_cgpu(tmp)) { + tmp = usb_free_cgpu(tmp); + goto unshin; + } + + update_usb_stats(tmp); + + added = true; + } + + modminer = usb_free_cgpu(modminer); + + return modminer; + +unshin: + if (!added) + usb_uninit(modminer); + +shin: + if (!added) { + free(modminer->modminer_mutex); + modminer->modminer_mutex = NULL; + } + + modminer = usb_free_cgpu(modminer); + + if (added) + return modminer; + else + return NULL; +} + +static void modminer_detect(bool __maybe_unused hotplug) +{ + usb_detect(&modminer_drv, modminer_detect_one); +} + +static bool get_expect(struct cgpu_info *modminer, FILE *f, char c) +{ + char buf; + + if (fread(&buf, 1, 1, f) != 1) { + applog(LOG_ERR, "%s%u: Error (%d) reading bitstream (%c)", + modminer->drv->name, modminer->device_id, errno, c); + return false; + } + + if (buf != c) { + applog(LOG_ERR, "%s%u: bitstream code mismatch (%c)", + modminer->drv->name, modminer->device_id, c); + return false; + } + + return true; +} + +static bool get_info(struct cgpu_info *modminer, FILE *f, char *buf, int bufsiz, const char *name) +{ + unsigned char siz[2]; + int len; + + if (fread(siz, 2, 1, f) != 1) { + applog(LOG_ERR, "%s%u: Error (%d) reading bitstream '%s' len", + modminer->drv->name, modminer->device_id, errno, name); + return false; + } + + len = siz[0] * 256 + siz[1]; + + if (len >= bufsiz) { + applog(LOG_ERR, "%s%u: Bitstream '%s' len too large (%d)", + modminer->drv->name, modminer->device_id, name, len); + return false; + } + + if (fread(buf, len, 1, f) != 1) { + applog(LOG_ERR, "%s%u: Error (%d) reading bitstream '%s'", + modminer->drv->name, modminer->device_id, errno, name); + return false; + } + + buf[len] = '\0'; + + return true; +} + +#define USE_DEFAULT_TIMEOUT 0 + +// mutex must always be locked before calling +static bool get_status_timeout(struct cgpu_info *modminer, char *msg, unsigned int timeout, enum usb_cmds cmd) +{ + int err, amount; + char buf[1]; + + if (timeout == USE_DEFAULT_TIMEOUT) + err = usb_read(modminer, buf, 1, &amount, cmd); + else + err = usb_read_timeout(modminer, buf, 1, &amount, timeout, cmd); + + if (err < 0 || amount != 1) { + mutex_unlock(modminer->modminer_mutex); + + applog(LOG_ERR, "%s%u: Error (%d:%d) getting %s reply", + modminer->drv->name, modminer->device_id, amount, err, msg); + + return false; + } + + if (buf[0] != 1) { + mutex_unlock(modminer->modminer_mutex); + + applog(LOG_ERR, "%s%u: Error, invalid %s reply (was %d should be 1)", + modminer->drv->name, modminer->device_id, msg, buf[0]); + + return false; + } + + return true; +} + +// mutex must always be locked before calling +static bool get_status(struct cgpu_info *modminer, char *msg, enum usb_cmds cmd) +{ + return get_status_timeout(modminer, msg, USE_DEFAULT_TIMEOUT, cmd); +} + +static bool modminer_fpga_upload_bitstream(struct cgpu_info *modminer) +{ + const char *bsfile = BITSTREAM_FILENAME; + char buf[0x100], *p; + char devmsg[64]; + unsigned char *ubuf = (unsigned char *)buf; + unsigned long totlen, len; + size_t buflen, remaining; + float nextmsg, upto; + char fpgaid = FPGAID_ALL; + int err, amount, tries; + char *ptr; + + FILE *f = open_bitstream("modminer", bsfile); + if (!f) { + mutex_unlock(modminer->modminer_mutex); + + applog(LOG_ERR, "%s%u: Error (%d) opening bitstream file %s", + modminer->drv->name, modminer->device_id, errno, bsfile); + + return false; + } + + if (fread(buf, 2, 1, f) != 1) { + mutex_unlock(modminer->modminer_mutex); + + applog(LOG_ERR, "%s%u: Error (%d) reading bitstream magic", + modminer->drv->name, modminer->device_id, errno); + + goto dame; + } + + if (buf[0] != BITSTREAM_MAGIC_0 || buf[1] != BITSTREAM_MAGIC_1) { + mutex_unlock(modminer->modminer_mutex); + + applog(LOG_ERR, "%s%u: bitstream has incorrect magic (%u,%u) instead of (%u,%u)", + modminer->drv->name, modminer->device_id, + buf[0], buf[1], + BITSTREAM_MAGIC_0, BITSTREAM_MAGIC_1); + + goto dame; + } + + if (fseek(f, 11L, SEEK_CUR)) { + mutex_unlock(modminer->modminer_mutex); + + applog(LOG_ERR, "%s%u: Error (%d) bitstream seek failed", + modminer->drv->name, modminer->device_id, errno); + + goto dame; + } + + if (!get_expect(modminer, f, 'a')) + goto undame; + + if (!get_info(modminer, f, buf, sizeof(buf), "Design name")) + goto undame; + + applog(LOG_DEBUG, "%s%u: bitstream file '%s' info:", + modminer->drv->name, modminer->device_id, bsfile); + + applog(LOG_DEBUG, " Design name: '%s'", buf); + + p = strrchr(buf, ';') ? : buf; + p = strrchr(buf, '=') ? : p; + if (p[0] == '=') + p++; + + unsigned long fwusercode = (unsigned long)strtoll(p, &p, 16); + + if (p[0] != '\0') { + mutex_unlock(modminer->modminer_mutex); + + applog(LOG_ERR, "%s%u: Bad usercode in bitstream file", + modminer->drv->name, modminer->device_id); + + goto dame; + } + + if (fwusercode == 0xffffffff) { + mutex_unlock(modminer->modminer_mutex); + + applog(LOG_ERR, "%s%u: bitstream doesn't support user code", + modminer->drv->name, modminer->device_id); + + goto dame; + } + + applog(LOG_DEBUG, " Version: %lu, build %lu", (fwusercode >> 8) & 0xff, fwusercode & 0xff); + + if (!get_expect(modminer, f, 'b')) + goto undame; + + if (!get_info(modminer, f, buf, sizeof(buf), "Part number")) + goto undame; + + applog(LOG_DEBUG, " Part number: '%s'", buf); + + if (!get_expect(modminer, f, 'c')) + goto undame; + + if (!get_info(modminer, f, buf, sizeof(buf), "Build date")) + goto undame; + + applog(LOG_DEBUG, " Build date: '%s'", buf); + + if (!get_expect(modminer, f, 'd')) + goto undame; + + if (!get_info(modminer, f, buf, sizeof(buf), "Build time")) + goto undame; + + applog(LOG_DEBUG, " Build time: '%s'", buf); + + if (!get_expect(modminer, f, 'e')) + goto undame; + + if (fread(buf, 4, 1, f) != 1) { + mutex_unlock(modminer->modminer_mutex); + + applog(LOG_ERR, "%s%u: Error (%d) reading bitstream data len", + modminer->drv->name, modminer->device_id, errno); + + goto dame; + } + + len = ((unsigned long)ubuf[0] << 24) | ((unsigned long)ubuf[1] << 16) | (ubuf[2] << 8) | ubuf[3]; + applog(LOG_DEBUG, " Bitstream size: %lu", len); + + strcpy(devmsg, modminer->device_path); + ptr = strrchr(devmsg, ':'); + if (ptr) + *ptr = '\0'; + + applog(LOG_WARNING, "%s%u: Programming all FPGA on %s ... Mining will not start until complete", + modminer->drv->name, modminer->device_id, devmsg); + + buf[0] = MODMINER_PROGRAM; + buf[1] = fpgaid; + buf[2] = (len >> 0) & 0xff; + buf[3] = (len >> 8) & 0xff; + buf[4] = (len >> 16) & 0xff; + buf[5] = (len >> 24) & 0xff; + + if ((err = usb_write(modminer, buf, 6, &amount, C_STARTPROGRAM)) < 0 || amount != 6) { + mutex_unlock(modminer->modminer_mutex); + + applog(LOG_ERR, "%s%u: Program init failed (%d:%d)", + modminer->drv->name, modminer->device_id, amount, err); + + goto dame; + } + + if (!get_status(modminer, "initialise", C_STARTPROGRAMSTATUS)) + goto undame; + +// It must be 32 bytes according to MCU legacy.c +#define WRITE_SIZE 32 + + totlen = len; + nextmsg = 0.1; + while (len > 0) { + buflen = len < WRITE_SIZE ? len : WRITE_SIZE; + if (fread(buf, buflen, 1, f) != 1) { + mutex_unlock(modminer->modminer_mutex); + + applog(LOG_ERR, "%s%u: bitstream file read error %d (%lu bytes left)", + modminer->drv->name, modminer->device_id, errno, len); + + goto dame; + } + + tries = 0; + ptr = buf; + remaining = buflen; + while ((err = usb_write(modminer, ptr, remaining, &amount, C_PROGRAM)) < 0 || amount != (int)remaining) { + if (err == LIBUSB_ERROR_TIMEOUT && amount > 0 && ++tries < 4) { + remaining -= amount; + ptr += amount; + + if (opt_debug) + applog(LOG_DEBUG, "%s%u: Program timeout (%d:%d) sent %d tries %d", + modminer->drv->name, modminer->device_id, + amount, err, (int)remaining, tries); + + if (!get_status(modminer, "write status", C_PROGRAMSTATUS2)) + goto dame; + + } else { + mutex_unlock(modminer->modminer_mutex); + + applog(LOG_ERR, "%s%u: Program failed (%d:%d) sent %d", + modminer->drv->name, modminer->device_id, amount, err, (int)remaining); + + goto dame; + } + } + + if (!get_status(modminer, "write status", C_PROGRAMSTATUS)) + goto dame; + + len -= buflen; + + upto = (float)(totlen - len) / (float)(totlen); + if (upto >= nextmsg) { + applog(LOG_WARNING, + "%s%u: Programming %.1f%% (%lu out of %lu)", + modminer->drv->name, modminer->device_id, upto*100, (totlen - len), totlen); + + nextmsg += 0.1; + } + } + + if (!get_status(modminer, "final status", C_FINALPROGRAMSTATUS)) + goto undame; + + applog(LOG_WARNING, "%s%u: Programming completed for all FPGA on %s", + modminer->drv->name, modminer->device_id, devmsg); + + // Give it a 2/3s delay after programming + cgsleep_ms(666); + + usb_set_dev_start(modminer); + + return true; +undame: + ; + mutex_unlock(modminer->modminer_mutex); + ; +dame: + fclose(f); + return false; +} + +static bool modminer_fpga_prepare(struct thr_info *thr) +{ +// struct cgpu_info *modminer = thr->cgpu; + struct modminer_fpga_state *state; + + state = thr->cgpu_data = calloc(1, sizeof(struct modminer_fpga_state)); + state->shares_to_good = MODMINER_EARLY_UP; + state->overheated = false; + + return true; +} + +/* + * Clocking rules: + * If device exceeds cutoff or overheat temp - stop sending work until it cools + * decrease the clock by MODMINER_CLOCK_CUTOFF/MODMINER_CLOCK_OVERHEAT + * for when it restarts + * with MODMINER_CLOCK_OVERHEAT=0 basically says that temp shouldn't + * affect the clock unless we reach CUTOFF + * + * If device overheats + * set shares_to_good back to MODMINER_MIN_BACK + * to speed up clock recovery if temp drop doesnt help + * + * When to clock down: + * If device gets MODMINER_HW_ERROR_PERCENT errors since last clock up or down + * if clock is <= default it requires 2 HW to do this test + * if clock is > default it only requires 1 HW to do this test + * also double shares_to_good + * + * When to clock up: + * If device gets shares_to_good good shares in a row + * and temp < MODMINER_TEMP_UP_LIMIT + * + * N.B. clock must always be a multiple of 2 + */ +static const char *clocknodev = "clock failed - no device"; +static const char *clockoldwork = "clock already changed for this work"; +static const char *clocktoolow = "clock too low"; +static const char *clocktoohi = "clock too high"; +static const char *clocksetfail = "clock set command failed"; +static const char *clockreplyfail = "clock reply failed"; + +static const char *modminer_delta_clock(struct thr_info *thr, int delta, bool temp, bool force) +{ + struct cgpu_info *modminer = thr->cgpu; + struct modminer_fpga_state *state = thr->cgpu_data; + unsigned char cmd[6], buf[1]; + int err, amount; + + // Device is gone + if (modminer->usbinfo.nodev) + return clocknodev; + + // Only do once if multiple shares per work or multiple reasons + if (!state->new_work && !force) + return clockoldwork; + + state->new_work = false; + + state->shares = 0; + state->shares_last_hw = 0; + state->hw_errors = 0; + + // FYI clock drop has little effect on temp + if (delta < 0 && (modminer->clock + delta) < MODMINER_MIN_CLOCK) + return clocktoolow; + + if (delta > 0 && (modminer->clock + delta) > MODMINER_MAX_CLOCK) + return clocktoohi; + + if (delta < 0) { + if (temp) + state->shares_to_good = MODMINER_MIN_BACK; + else { + if ((state->shares_to_good * 2) < MODMINER_TRY_UP) + state->shares_to_good *= 2; + else + state->shares_to_good = MODMINER_TRY_UP; + } + } + + modminer->clock += delta; + + cmd[0] = MODMINER_SET_CLOCK; + cmd[1] = modminer->fpgaid; + cmd[2] = modminer->clock; + cmd[3] = cmd[4] = cmd[5] = '\0'; + + mutex_lock(modminer->modminer_mutex); + + if ((err = usb_write(modminer, (char *)cmd, 6, &amount, C_SETCLOCK)) < 0 || amount != 6) { + mutex_unlock(modminer->modminer_mutex); + + applog(LOG_ERR, "%s%u: Error writing set clock speed (%d:%d)", + modminer->drv->name, modminer->device_id, amount, err); + + return clocksetfail; + } + + if ((err = usb_read(modminer, (char *)(&buf), 1, &amount, C_REPLYSETCLOCK)) < 0 || amount != 1) { + mutex_unlock(modminer->modminer_mutex); + + applog(LOG_ERR, "%s%u: Error reading set clock speed (%d:%d)", + modminer->drv->name, modminer->device_id, amount, err); + + return clockreplyfail; + } + + mutex_unlock(modminer->modminer_mutex); + + applog(LOG_WARNING, "%s%u: Set clock speed %sto %u", + modminer->drv->name, modminer->device_id, + (delta < 0) ? "down " : (delta > 0 ? "up " : ""), + modminer->clock); + + return NULL; +} + +static bool modminer_fpga_init(struct thr_info *thr) +{ + struct cgpu_info *modminer = thr->cgpu; + unsigned char cmd[2], buf[4]; + int err, amount; + + mutex_lock(modminer->modminer_mutex); + + cmd[0] = MODMINER_GET_USERCODE; + cmd[1] = modminer->fpgaid; + if ((err = usb_write(modminer, (char *)cmd, 2, &amount, C_REQUESTUSERCODE)) < 0 || amount != 2) { + mutex_unlock(modminer->modminer_mutex); + + applog(LOG_ERR, "%s%u: Error requesting USER code (%d:%d)", + modminer->drv->name, modminer->device_id, amount, err); + + return false; + } + + if ((err = usb_read(modminer, (char *)buf, 4, &amount, C_GETUSERCODE)) < 0 || amount != 4) { + mutex_unlock(modminer->modminer_mutex); + + applog(LOG_ERR, "%s%u: Error reading USER code (%d:%d)", + modminer->drv->name, modminer->device_id, amount, err); + + return false; + } + + if (memcmp(buf, BISTREAM_USER_ID, 4)) { + applog(LOG_ERR, "%s%u: FPGA not programmed", + modminer->drv->name, modminer->device_id); + + if (!modminer_fpga_upload_bitstream(modminer)) + return false; + + mutex_unlock(modminer->modminer_mutex); + } else { + mutex_unlock(modminer->modminer_mutex); + + applog(LOG_DEBUG, "%s%u: FPGA is already programmed :)", + modminer->drv->name, modminer->device_id); + } + + modminer->clock = MODMINER_DEF_CLOCK; + modminer_delta_clock(thr, MODMINER_CLOCK_SET, false, false); + + thr->primary_thread = true; + + return true; +} + +static void get_modminer_statline_before(char *buf, size_t bufsiz, struct cgpu_info *modminer) +{ + tailsprintf(buf, bufsiz, "%s%.1fC %3uMHz", + (modminer->temp < 10) ? " " : "", + modminer->temp, + (unsigned int)(modminer->clock)); +} + +static bool modminer_start_work(struct thr_info *thr, struct work *work) +{ + struct cgpu_info *modminer = thr->cgpu; + struct modminer_fpga_state *state = thr->cgpu_data; + int err, amount; + char cmd[48]; + bool sta; + + cmd[0] = MODMINER_SEND_WORK; + cmd[1] = modminer->fpgaid; + memcpy(&cmd[2], work->midstate, 32); + memcpy(&cmd[34], work->data + 64, 12); + + if (state->first_work.tv_sec == 0) + cgtime(&state->first_work); + + if (state->last_nonce.tv_sec == 0) + cgtime(&state->last_nonce); + + mutex_lock(modminer->modminer_mutex); + + if ((err = usb_write(modminer, cmd, 46, &amount, C_SENDWORK)) < 0 || amount != 46) { + mutex_unlock(modminer->modminer_mutex); + + applog(LOG_ERR, "%s%u: Start work failed (%d:%d)", + modminer->drv->name, modminer->device_id, amount, err); + + return false; + } + + cgtime(&state->tv_workstart); + + sta = get_status(modminer, "start work", C_SENDWORKSTATUS); + + if (sta) { + mutex_unlock(modminer->modminer_mutex); + state->new_work = true; + } + + return sta; +} + +static void check_temperature(struct thr_info *thr) +{ + struct cgpu_info *modminer = thr->cgpu; + struct modminer_fpga_state *state = thr->cgpu_data; + char cmd[2], temperature[2]; + int tbytes, tamount; + int amount; + + // Device is gone + if (modminer->usbinfo.nodev) + return; + + if (state->one_byte_temp) { + cmd[0] = MODMINER_TEMP1; + tbytes = 1; + } else { + cmd[0] = MODMINER_TEMP2; + tbytes = 2; + } + + cmd[1] = modminer->fpgaid; + + mutex_lock(modminer->modminer_mutex); + if (usb_write(modminer, (char *)cmd, 2, &amount, C_REQUESTTEMPERATURE) == 0 && amount == 2 && + usb_read(modminer, (char *)(&temperature), tbytes, &tamount, C_GETTEMPERATURE) == 0 && tamount == tbytes) { + mutex_unlock(modminer->modminer_mutex); + if (state->one_byte_temp) + modminer->temp = temperature[0]; + else { + // Only accurate to 2 and a bit places + modminer->temp = roundf((temperature[1] * 256.0 + temperature[0]) / 0.128) / 1000.0; + + state->tried_two_byte_temp = true; + } + + if (state->overheated) { + // Limit recovery to lower than OVERHEAT so it doesn't just go straight over again + if (modminer->temp < MODMINER_RECOVER_TEMP) { + state->overheated = false; + applog(LOG_WARNING, "%s%u: Recovered, temp less than (%.1f) now %.3f", + modminer->drv->name, modminer->device_id, + MODMINER_RECOVER_TEMP, modminer->temp); + } + } + else if (modminer->temp >= MODMINER_OVERHEAT_TEMP) { + if (modminer->temp >= MODMINER_CUTOFF_TEMP) { + applog(LOG_WARNING, "%s%u: Hit thermal cutoff limit! (%.1f) at %.3f", + modminer->drv->name, modminer->device_id, + MODMINER_CUTOFF_TEMP, modminer->temp); + + modminer_delta_clock(thr, MODMINER_CLOCK_CUTOFF, true, false); + state->overheated = true; + dev_error(modminer, REASON_DEV_THERMAL_CUTOFF); + } else { + applog(LOG_WARNING, "%s%u: Overheat limit (%.1f) reached %.3f", + modminer->drv->name, modminer->device_id, + MODMINER_OVERHEAT_TEMP, modminer->temp); + + // If it's defined to be 0 then don't call modminer_delta_clock() + if (MODMINER_CLOCK_OVERHEAT != 0) + modminer_delta_clock(thr, MODMINER_CLOCK_OVERHEAT, true, false); + state->overheated = true; + dev_error(modminer, REASON_DEV_OVER_HEAT); + } + } + } else { + mutex_unlock(modminer->modminer_mutex); + + if (!state->tried_two_byte_temp) { + state->tried_two_byte_temp = true; + state->one_byte_temp = true; + } + } +} + +#define work_restart(thr) thr->work_restart + +// 250Mhz is 17.17s - ensure we don't go idle +static const double processtime = 17.0; +// 160Mhz is 26.84 - when overheated ensure we don't throw away shares +static const double overheattime = 26.9; + +static uint64_t modminer_process_results(struct thr_info *thr, struct work *work) +{ + struct cgpu_info *modminer = thr->cgpu; + struct modminer_fpga_state *state = thr->cgpu_data; + struct timeval now; + char cmd[2]; + uint32_t nonce; + uint32_t curr_hw_errors; + int err, amount, amount2; + int timeoutloop; + double timeout; + int temploop; + + // Device is gone + if (modminer->usbinfo.nodev) + return -1; + + // If we are overheated it will just keep checking for results + // since we can't stop the work + // The next work will not start until the temp drops + check_temperature(thr); + + cmd[0] = MODMINER_CHECK_WORK; + cmd[1] = modminer->fpgaid; + + timeoutloop = 0; + temploop = 0; + while (0x80085) { + mutex_lock(modminer->modminer_mutex); + if ((err = usb_write(modminer, cmd, 2, &amount, C_REQUESTWORKSTATUS)) < 0 || amount != 2) { + mutex_unlock(modminer->modminer_mutex); + + // timeoutloop never resets so the timeouts can't + // accumulate much during a single item of work + if (err == LIBUSB_ERROR_TIMEOUT && ++timeoutloop < 5) { + state->timeout_fail++; + goto tryagain; + } + + applog(LOG_ERR, "%s%u: Error sending (get nonce) (%d:%d)", + modminer->drv->name, modminer->device_id, amount, err); + + return -1; + } + + err = usb_read(modminer, (char *)(&nonce), 4, &amount, C_GETWORKSTATUS); + while (err == LIBUSB_SUCCESS && amount < 4) { + size_t remain = 4 - amount; + char *pos = ((char *)(&nonce)) + amount; + + state->success_more++; + + err = usb_read(modminer, pos, remain, &amount2, C_GETWORKSTATUS); + + amount += amount2; + } + mutex_unlock(modminer->modminer_mutex); + + if (err < 0 || amount < 4) { + // timeoutloop never resets so the timeouts can't + // accumulate much during a single item of work + if (err == LIBUSB_ERROR_TIMEOUT && ++timeoutloop < 10) { + state->timeout_fail++; + goto tryagain; + } + + applog(LOG_ERR, "%s%u: Error reading (get nonce) (%d:%d)", + modminer->drv->name, modminer->device_id, amount+amount2, err); + } + + if (memcmp(&nonce, "\xff\xff\xff\xff", 4)) { + // found 'something' ... + state->shares++; + curr_hw_errors = state->hw_errors; + submit_nonce(thr, work, nonce); + if (state->hw_errors > curr_hw_errors) { + cgtime(&now); + // Ignore initial errors that often happen + if (tdiff(&now, &state->first_work) < 2.0) { + state->shares = 0; + state->shares_last_hw = 0; + state->hw_errors = 0; + } else { + state->shares_last_hw = state->shares; + if (modminer->clock > MODMINER_DEF_CLOCK || state->hw_errors > 1) { + float pct = (state->hw_errors * 100.0 / (state->shares ? : 1.0)); + if (pct >= MODMINER_HW_ERROR_PERCENT) + modminer_delta_clock(thr, MODMINER_CLOCK_DOWN, false, false); + } + } + } else { + cgtime(&state->last_nonce); + state->death_stage_one = false; + // If we've reached the required good shares in a row then clock up + if (((state->shares - state->shares_last_hw) >= state->shares_to_good) && + modminer->temp < MODMINER_TEMP_UP_LIMIT) + modminer_delta_clock(thr, MODMINER_CLOCK_UP, false, false); + } + } else { + // on rare occasions - the MMQ can just stop returning valid nonces + double death = ITS_DEAD_JIM * (state->death_stage_one ? 2.0 : 1.0); + cgtime(&now); + if (tdiff(&now, &state->last_nonce) >= death) { + if (state->death_stage_one) { + modminer_delta_clock(thr, MODMINER_CLOCK_DEAD, false, true); + applog(LOG_ERR, "%s%u: DEATH clock down", + modminer->drv->name, modminer->device_id); + + // reset the death info and DISABLE it + state->last_nonce.tv_sec = 0; + state->last_nonce.tv_usec = 0; + state->death_stage_one = false; + return -1; + } else { + modminer_delta_clock(thr, MODMINER_CLOCK_DEAD, false, true); + applog(LOG_ERR, "%s%u: death clock down", + modminer->drv->name, modminer->device_id); + + state->death_stage_one = true; + } + } + } + +tryagain: + + if (work_restart(thr)) + break; + + if (state->overheated == true) { + // don't check every time (every ~1/2 sec) + if (++temploop > 4) { + check_temperature(thr); + temploop = 0; + } + + } + + if (state->overheated == true) + timeout = overheattime; + else + timeout = processtime; + + cgtime(&now); + if (tdiff(&now, &state->tv_workstart) > timeout) + break; + + // 1/10th sec to lower CPU usage + cgsleep_ms(100); + if (work_restart(thr)) + break; + } + + struct timeval tv_workend, elapsed; + cgtime(&tv_workend); + timersub(&tv_workend, &state->tv_workstart, &elapsed); + + // Not exact since the clock may have changed ... but close enough I guess + uint64_t hashes = (uint64_t)modminer->clock * (((uint64_t)elapsed.tv_sec * 1000000) + elapsed.tv_usec); + // Overheat will complete the nonce range + if (hashes > 0xffffffff) + hashes = 0xffffffff; + + work->nonce = 0xffffffff; + + return hashes; +} + +static int64_t modminer_scanhash(struct thr_info *thr, struct work *work, int64_t __maybe_unused max_nonce) +{ + struct modminer_fpga_state *state = thr->cgpu_data; + struct timeval tv1, tv2; + int64_t hashes; + + // Device is gone + if (thr->cgpu->usbinfo.nodev) + return -1; + + // Don't start new work if overheated + if (state->overheated == true) { + cgtime(&tv1); + + while (state->overheated == true) { + check_temperature(thr); + + // Device is gone + if (thr->cgpu->usbinfo.nodev) + return -1; + + if (state->overheated == true) { + cgtime(&tv2); + + // give up on this work item after 30s + if (work_restart(thr) || tdiff(&tv2, &tv1) > 30) + return 0; + + // Give it 1s rest then check again + cgsleep_ms(1000); + } + } + } + + if (!modminer_start_work(thr, work)) + return -1; + + hashes = modminer_process_results(thr, work); + if (hashes == -1) + return hashes; + + return hashes; +} + +static void modminer_hw_error(struct thr_info *thr) +{ + struct modminer_fpga_state *state = thr->cgpu_data; + + state->hw_errors++; +} + +static void modminer_fpga_shutdown(struct thr_info *thr) +{ + free(thr->cgpu_data); + thr->cgpu_data = NULL; +} + +static char *modminer_set_device(struct cgpu_info *modminer, char *option, char *setting, char *replybuf) +{ + const char *ret; + int val; + + if (strcasecmp(option, "help") == 0) { + sprintf(replybuf, "clock: range %d-%d and a multiple of 2", + MODMINER_MIN_CLOCK, MODMINER_MAX_CLOCK); + return replybuf; + } + + if (strcasecmp(option, "clock") == 0) { + if (!setting || !*setting) { + sprintf(replybuf, "missing clock setting"); + return replybuf; + } + + val = atoi(setting); + if (val < MODMINER_MIN_CLOCK || val > MODMINER_MAX_CLOCK || (val & 1) != 0) { + sprintf(replybuf, "invalid clock: '%s' valid range %d-%d and a multiple of 2", + setting, MODMINER_MIN_CLOCK, MODMINER_MAX_CLOCK); + return replybuf; + } + + val -= (int)(modminer->clock); + + ret = modminer_delta_clock(modminer->thr[0], val, false, true); + if (ret) { + sprintf(replybuf, "Set clock failed: %s", ret); + return replybuf; + } else + return NULL; + } + + sprintf(replybuf, "Unknown option: %s", option); + return replybuf; +} + +struct device_drv modminer_drv = { + .drv_id = DRIVER_modminer, + .dname = "ModMiner", + .name = "MMQ", + .drv_detect = modminer_detect, + .get_statline_before = get_modminer_statline_before, + .set_device = modminer_set_device, + .thread_prepare = modminer_fpga_prepare, + .thread_init = modminer_fpga_init, + .scanhash = modminer_scanhash, + .hw_error = modminer_hw_error, + .thread_shutdown = modminer_fpga_shutdown, +}; diff --git a/driver-spondoolies-sp10-p.c b/driver-spondoolies-sp10-p.c new file mode 100644 index 0000000..c37e171 --- /dev/null +++ b/driver-spondoolies-sp10-p.c @@ -0,0 +1,44 @@ +/* + * Copyright 2014 Con Kolivas + * Copyright 2014 Zvi (Zvisha) Shteingart - Spondoolies-tech.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + * + * Note that changing this SW will void your miners guaranty + */ + +/* + This file holds functions needed for minergate packet parsing/creation + by Zvisha Shteingart +*/ + +#include "driver-spondoolies-sp10-p.h" +#include "assert.h" +//#include "spond_debug.h" + +minergate_req_packet *allocate_minergate_packet_req(uint8_t requester_id, uint8_t request_id) +{ + minergate_req_packet *p = (minergate_req_packet*)malloc(sizeof(minergate_req_packet)); + p->requester_id = requester_id; + p->req_count = 0; + p->protocol_version = MINERGATE_PROTOCOL_VERSION; + p->request_id = request_id; + p->magic = 0xcaf4; + p->mask |= 0x01; // first packet + return p; +} + +minergate_rsp_packet *allocate_minergate_packet_rsp(uint8_t requester_id, uint8_t request_id) +{ + minergate_rsp_packet *p = (minergate_rsp_packet*)malloc(sizeof(minergate_rsp_packet)); + p->requester_id = requester_id; + p->rsp_count = 0; + p->protocol_version = MINERGATE_PROTOCOL_VERSION; + p->request_id = request_id; + p->magic = 0xcaf4; + p->gh_div_10_rate = 0; + return p; +} diff --git a/driver-spondoolies-sp10-p.h b/driver-spondoolies-sp10-p.h new file mode 100644 index 0000000..1476bc2 --- /dev/null +++ b/driver-spondoolies-sp10-p.h @@ -0,0 +1,92 @@ +/* + * Copyright 2014 Con Kolivas + * Copyright 2014 Zvi (Zvisha) Shteingart - Spondoolies-tech.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + * + * Note that changing this SW will void your miners guaranty + */ + +#ifndef ____MINERGATE_LIB_H___ +#define ____MINERGATE_LIB_H___ + +//#include "squid.h" +#include +#include +#include +#include +#include +#include +#include +#include +//#include "queue.h" +//#include "spond_debug.h" + +#ifndef passert +#define passert assert +#endif + +#define MINERGATE_PROTOCOL_VERSION 6 +#define MINERGATE_SOCKET_FILE "/tmp/connection_pipe" + +typedef enum { + //MINERGATE_DATA_ID_CONNECT = 1, + MINERGATE_DATA_ID_DO_JOB_REQ = 2, + MINERGATE_DATA_ID_DO_JOB_RSP = 3, + +} MINERGATE_DATA_ID; + +typedef struct { + uint32_t work_id_in_sw; + uint32_t difficulty; + uint32_t timestamp; + uint32_t mrkle_root; + uint32_t midstate[8]; + uint8_t leading_zeroes; + uint8_t ntime_limit; + uint8_t ntime_offset; + uint8_t resr1; +} minergate_do_job_req; + +#define MAX_REQUESTS 100 +#define MAX_RESPONDS 300 +#define MINERGATE_TOTAL_QUEUE 300 + +typedef struct { + uint32_t work_id_in_sw; + uint32_t mrkle_root; // to validate + uint32_t winner_nonce[2]; + uint8_t ntime_offset; + uint8_t res; // 0 = done, 1 = overflow, 2 = dropped bist + uint8_t resrv1; + uint8_t resrv2; +} minergate_do_job_rsp; + + +typedef struct { + uint8_t requester_id; + uint8_t request_id; + uint8_t protocol_version; + uint8_t mask; // 0x01 = first request, 0x2 = drop old work + uint16_t magic; // 0xcafe + uint16_t req_count; + minergate_do_job_req req[MAX_REQUESTS]; // array of requests +} minergate_req_packet; + +typedef struct { + uint8_t requester_id; + uint8_t request_id; + uint8_t protocol_version; + uint8_t gh_div_10_rate; // == + uint16_t magic; // 0xcafe + uint16_t rsp_count; + minergate_do_job_rsp rsp[MAX_RESPONDS]; // array of responce +} minergate_rsp_packet; + +minergate_req_packet *allocate_minergate_packet_req(uint8_t requester_id, uint8_t request_id); +minergate_rsp_packet *allocate_minergate_packet_rsp(uint8_t requester_id, uint8_t request_id); + +#endif diff --git a/driver-spondoolies-sp10.c b/driver-spondoolies-sp10.c new file mode 100644 index 0000000..3ffab8d --- /dev/null +++ b/driver-spondoolies-sp10.c @@ -0,0 +1,446 @@ +/* + * Copyright 2014 Con Kolivas + * Copyright 2014 Zvi (Zvisha) Shteingart - Spondoolies-tech.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +/* + This driver communicates the job requests via Unix socket to the minergate + process, that is responsible for controlling the Spondoolies Dawson SP10 miner. + + The jobs sent each with unique ID and returned asynchronously in one of the next + transactions. REQUEST_PERIOD and REQUEST_SIZE define the communication rate with minergate. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" + +#ifdef WIN32 +#include +#endif + +#include "compat.h" +#include "miner.h" +#include "driver-spondoolies-sp10-p.h" +#include "driver-spondoolies-sp10.h" + +#ifdef WORDS_BIGENDIAN +# define swap32tobe(out, in, sz) ((out == in) ? (void)0 : memmove(out, in, sz)) +# define LOCAL_swap32be(type, var, sz) ; +# define swap32tole(out, in, sz) swap32yes(out, in, sz) +# define LOCAL_swap32le(type, var, sz) LOCAL_swap32(type, var, sz) +#else +# define swap32tobe(out, in, sz) swap32yes(out, in, sz) +# define LOCAL_swap32be(type, var, sz) LOCAL_swap32(type, var, sz) +# define swap32tole(out, in, sz) ((out == in) ? (void)0 : memmove(out, in, sz)) +# define LOCAL_swap32le(type, var, sz) ; +#endif + +static inline void swap32yes(void *out, const void *in, size_t sz) +{ + size_t swapcounter; + + for (swapcounter = 0; swapcounter < sz; ++swapcounter) + (((uint32_t*)out)[swapcounter]) = swab32(((uint32_t*)in)[swapcounter]); +} + +static void send_minergate_pkt(const minergate_req_packet* mp_req, minergate_rsp_packet* mp_rsp, + int socket_fd) +{ + int nbytes, nwrote, nread; + + nbytes = sizeof(minergate_req_packet); + nwrote = write(socket_fd, (const void *)mp_req, nbytes); + if (unlikely(nwrote != nbytes)) + _quit(-1); + nbytes = sizeof(minergate_rsp_packet); + nread = read(socket_fd, (void *)mp_rsp, nbytes); + if (unlikely(nread != nbytes)) + _quit(-1); + passert(mp_rsp->magic == 0xcaf4); +} + +static bool spondoolies_prepare(struct thr_info *thr) +{ + struct cgpu_info *spondoolies = thr->cgpu; + struct timeval now; + + assert(spondoolies); + cgtime(&now); + /* FIXME: Vladik */ +#if NEED_FIX + get_datestamp(spondoolies->init, &now); +#endif + return true; +} + +static int init_socket(void) +{ + int socket_fd = socket(PF_UNIX, SOCK_STREAM, 0); + struct sockaddr_un address; + + if (socket_fd < 0) { + printf("socket() failed\n"); + perror("Err:"); + return 0; + } + + /* start with a clean address structure */ + memset(&address, 0, sizeof(struct sockaddr_un)); + + address.sun_family = AF_UNIX; + sprintf(address.sun_path, MINERGATE_SOCKET_FILE); + + if(connect(socket_fd, (struct sockaddr *) &address, sizeof(struct sockaddr_un))) { + printf("connect() failed\n"); + perror("Err:"); + return 0; + } + + return socket_fd; +} + +static bool spondoolies_flush_queue(struct spond_adapter* a, bool flush_queue) +{ + if (!a->parse_resp) { + static int i = 0; + + if (i++ % 10 == 0 && a->works_in_minergate_and_pending_tx + a->works_pending_tx != a->works_in_driver) + printf("%d + %d != %d\n", a->works_in_minergate_and_pending_tx, a->works_pending_tx,a->works_in_driver); + assert(a->works_in_minergate_and_pending_tx + a->works_pending_tx == a->works_in_driver); + send_minergate_pkt(a->mp_next_req, a->mp_last_rsp, a->socket_fd); + if (flush_queue) + a->mp_next_req->mask |= 0x02; + else + a->mp_next_req->mask &= ~0x02; + + a->mp_next_req->req_count = 0; + a->parse_resp = 1; + a->works_in_minergate_and_pending_tx += a->works_pending_tx; + a->works_pending_tx = 0; + } + return true; +} + +static void spondoolies_detect(__maybe_unused bool hotplug) +{ + struct cgpu_info *cgpu = calloc(1, sizeof(*cgpu)); + struct device_drv *drv = &sp10_drv; + struct spond_adapter *a; + +#if NEED_FIX + nDevs = 1; +#endif + + assert(cgpu); + cgpu->drv = drv; + cgpu->deven = DEV_ENABLED; + cgpu->threads = 1; + cgpu->device_data = calloc(sizeof(struct spond_adapter), 1); + if (unlikely(!(cgpu->device_data))) + quit(1, "Failed to calloc cgpu_info data"); + a = cgpu->device_data; + a->cgpu = (void *)cgpu; + a->adapter_state = ADAPTER_STATE_OPERATIONAL; + a->mp_next_req = allocate_minergate_packet_req(0xca, 0xfe); + a->mp_last_rsp = allocate_minergate_packet_rsp(0xca, 0xfe); + + pthread_mutex_init(&a->lock, NULL); + a->socket_fd = init_socket(); + if (a->socket_fd < 1) { + printf("Error connecting to minergate server!"); + _quit(-1); + } + + assert(add_cgpu(cgpu)); + // Clean MG socket + spondoolies_flush_queue(a, true); + spondoolies_flush_queue(a, true); + spondoolies_flush_queue(a, true); + applog(LOG_DEBUG, "SPOND spondoolies_detect done"); +} + +static struct api_data *spondoolies_api_stats(struct cgpu_info *cgpu) +{ + struct spond_adapter *a = cgpu->device_data; + struct api_data *root = NULL; + + root = api_add_int(root, "ASICs total rate", &a->temp_rate, false); + root = api_add_int(root, "Temperature front", &a->front_temp, false); + root = api_add_int(root, "Temperature rear top", &a->rear_temp_top, false); + root = api_add_int(root, "Temperature rear bot", &a->rear_temp_bot, false); + + return root; +} + +#if 0 +static unsigned char get_leading_zeroes(const unsigned char *target) +{ + unsigned char leading = 0; + int first_non_zero_chr; + uint8_t m; + + for (first_non_zero_chr = 31; first_non_zero_chr >= 0; first_non_zero_chr--) { + if (target[first_non_zero_chr] == 0) + leading += 8; + else + break; + } + + // j = first non-zero + m = target[first_non_zero_chr]; + while ((m & 0x80) == 0) { + leading++; + m = m << 1; + } + return leading; +} +#endif + +static void spondoolies_shutdown(__maybe_unused struct thr_info *thr) +{ +} + +static void fill_minergate_request(minergate_do_job_req* work, struct work *cg_work, + int ntime_offset) +{ + uint32_t x[64/4]; + uint64_t wd; + + memset(work, 0, sizeof(minergate_do_job_req)); + //work-> + LOCAL_swap32le(unsigned char, cg_work->midstate, 32/4) + LOCAL_swap32le(unsigned char, cg_work->data+64, 64/4) + swap32yes(x, cg_work->data + 64, 64/4); + memcpy(work->midstate, cg_work->midstate, 32); + work->mrkle_root = ntohl(x[0]); + work->timestamp = ntohl(x[1]); + work->difficulty = ntohl(x[2]); + //work->leading_zeroes = get_leading_zeroes(cg_work->target); + // Is there no better way to get leading zeroes? + work->leading_zeroes = 30; + wd = round(cg_work->work_difficulty); + while (wd) { + work->leading_zeroes++; + wd = wd >> 1; + } + //printf("%d %d\n",work->leading_zeroes, (int)round(cg_work->work_difficulty)); + work->work_id_in_sw = cg_work->subid; + work->ntime_limit = 0; + work->ntime_offset = ntime_offset; +} + +// returns true if queue full. +static struct timeval last_force_queue = {0}; + +static bool spondoolies_queue_full(struct cgpu_info *cgpu) +{ + // Only once every 1/10 second do work. + struct spond_adapter* a = cgpu->device_data; + int next_job_id, ntime_clones, i; + struct timeval tv; + struct work *work; + unsigned int usec; + bool ret = false; + + mutex_lock(&a->lock); + passert(a->works_pending_tx <= REQUEST_SIZE); + + gettimeofday(&tv, NULL); + + usec = (tv.tv_sec-last_force_queue.tv_sec) * 1000000; + usec += (tv.tv_usec-last_force_queue.tv_usec); + + if ((usec >= REQUEST_PERIOD) || (a->reset_mg_queue == 2) || + ((a->reset_mg_queue == 1) && (a->works_pending_tx == REQUEST_SIZE))) { + spondoolies_flush_queue(a, (a->reset_mg_queue == 2)); + if (a->reset_mg_queue) + a->reset_mg_queue--; + last_force_queue = tv; + } + + // see if we have enough jobs + if (a->works_pending_tx == REQUEST_SIZE) { + ret = true; + goto return_unlock; + } + + // see if can take 1 more job. + next_job_id = (a->current_job_id + 1) % MAX_JOBS_IN_MINERGATE; + if (a->my_jobs[next_job_id].cgminer_work) { + ret = true; + goto return_unlock; + } + work = get_queued(cgpu); + if (!work) { + cgsleep_ms(10); + goto return_unlock; + } + + work->thr = cgpu->thr[0]; + work->thr_id = cgpu->thr[0]->id; + assert(work->thr); + + // Create 5 works using ntime increment + a->current_job_id = next_job_id; + work->subid = a->current_job_id; + // Get pointer for the request + a->my_jobs[a->current_job_id].cgminer_work = work; + a->my_jobs[a->current_job_id].state = SPONDWORK_STATE_IN_BUSY; + a->my_jobs[a->current_job_id].ntime_clones = 0; + + ntime_clones = (work->drv_rolllimit < MAX_NROLES) ? work->drv_rolllimit : MAX_NROLES; + for (i = 0 ; (i < ntime_clones) && (a->works_pending_tx < REQUEST_SIZE) ; i++) { + minergate_do_job_req* pkt_job = &a->mp_next_req->req[a->works_pending_tx]; + fill_minergate_request(pkt_job, work, i); + a->works_in_driver++; + a->works_pending_tx++; + a->mp_next_req->req_count++; + a->my_jobs[a->current_job_id].merkle_root = pkt_job->mrkle_root; + a->my_jobs[a->current_job_id].ntime_clones++; + } + +return_unlock: + mutex_unlock(&a->lock); + + return ret; +} + +static void spond_poll_stats(struct cgpu_info *spond, struct spond_adapter *a) +{ + FILE *fp = fopen("/var/run/mg_rate_temp", "r"); + + if (!fp) { + applog(LOG_DEBUG, "SPOND unable to open mg_rate_temp"); + a->temp_rate = a->front_temp = a->rear_temp_top = a->rear_temp_bot = 0; + } else { + int ret = fscanf(fp, "%d %d %d %d", &a->temp_rate, &a->front_temp , &a->rear_temp_top , &a->rear_temp_bot); + + if (ret != 4) + a->temp_rate = a->front_temp = a->rear_temp_top = a->rear_temp_bot = 0; + fclose(fp); + } + applog(LOG_DEBUG, "SPOND poll_stats rate: %d front: %d rear(T/B): %d/%d", + a->temp_rate, a->front_temp , a->rear_temp_top, a->rear_temp_bot); + /* Use the rear temperature as the dev temperature for now */ + spond->temp = (a->rear_temp_top + a->rear_temp_bot)/2; +} + +// Return completed work to submit_nonce() and work_completed() +// struct timeval last_force_queue = {0}; +static int64_t spond_scanhash(struct thr_info *thr) +{ + struct cgpu_info *cgpu = thr->cgpu; + struct spond_adapter *a = cgpu->device_data; + int64_t ghashes = 0; + cgtimer_t cgt; + time_t now_t; + + cgsleep_prepare_r(&cgt); + now_t = time(NULL); + /* Poll stats only once per second */ + if (now_t != a->last_stats) { + a->last_stats = now_t; + spond_poll_stats(cgpu, a); + } + + if (a->parse_resp) { + int array_size, i, j; + + mutex_lock(&a->lock); + ghashes = (a->mp_last_rsp->gh_div_10_rate); + ghashes = ghashes * 10000 * REQUEST_PERIOD; + array_size = a->mp_last_rsp->rsp_count; + for (i = 0; i < array_size; i++) { // walk the jobs + int job_id; + + minergate_do_job_rsp* work = a->mp_last_rsp->rsp + i; + job_id = work->work_id_in_sw; + if ((a->my_jobs[job_id].cgminer_work)) { + if (a->my_jobs[job_id].merkle_root == work->mrkle_root) { + assert(a->my_jobs[job_id].state == SPONDWORK_STATE_IN_BUSY); + a->works_in_minergate_and_pending_tx--; + a->works_in_driver--; + for (j = 0; j < 2; j++) { + if (work->winner_nonce[j]) { + bool __maybe_unused ok; + struct work *cg_work = a->my_jobs[job_id].cgminer_work; +#ifndef SP_NTIME + ok = submit_nonce(cg_work->thr, cg_work, work->winner_nonce[j]); +#else + ok = submit_noffset_nonce(cg_work->thr, cg_work, work->winner_nonce[j], work->ntime_offset); +#endif + //printf("OK on %d:%d = %d\n",work->work_id_in_sw,j, ok); + a->wins++; + } + } + //printf("%d ntime_clones = %d\n",job_id,a->my_jobs[job_id].ntime_clones); + if ((--a->my_jobs[job_id].ntime_clones) == 0) { + //printf("Done with %d\n", job_id); + work_completed(a->cgpu, a->my_jobs[job_id].cgminer_work); + a->good++; + a->my_jobs[job_id].cgminer_work = NULL; + a->my_jobs[job_id].state = SPONDWORK_STATE_EMPTY; + } + } else { + a->bad++; + printf("Dropping minergate old job id=%d mrkl=%x my-mrkl=%x\n", + job_id, a->my_jobs[job_id].merkle_root, work->mrkle_root); + } + } else { + a->empty++; + printf("No cgminer job (id:%d res:%d)!\n",job_id, work->res); + } + } + mutex_unlock(&a->lock); + + a->parse_resp = 0; + } + cgsleep_ms_r(&cgt, 40); + + return ghashes; +} + +// Remove all work from queue +static void spond_flush_work(struct cgpu_info *cgpu) +{ + struct spond_adapter *a = cgpu->device_data; + + mutex_lock(&a->lock); + a->reset_mg_queue = 2; + mutex_unlock(&a->lock); +} + +struct device_drv sp10_drv = { + .drv_id = DRIVER_sp10, + .dname = "Spondoolies", + .name = "SPN", + .max_diff = 64.0, // Limit max diff to get some nonces back regardless + .drv_detect = spondoolies_detect, + .get_api_stats = spondoolies_api_stats, + .thread_prepare = spondoolies_prepare, + .thread_shutdown = spondoolies_shutdown, + .hash_work = hash_queued_work, + .queue_full = spondoolies_queue_full, + .scanwork = spond_scanhash, + .flush_work = spond_flush_work, +}; diff --git a/driver-spondoolies-sp10.h b/driver-spondoolies-sp10.h new file mode 100644 index 0000000..b99fe21 --- /dev/null +++ b/driver-spondoolies-sp10.h @@ -0,0 +1,84 @@ +/* + * Copyright 2014 Con Kolivas + * Copyright 2014 Zvi Shteingart - Spondoolies-tech.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#ifndef SPONDA_HFILE +#define SPONDA_HFILE + +#include "miner.h" +#include "driver-spondoolies-sp10-p.h" + + +#define SP_NTIME + +typedef enum adapter_state { + ADAPTER_STATE_INIT, + ADAPTER_STATE_OPERATIONAL, +} ADAPTER_STATE; + +typedef enum spond_work_state { + SPONDWORK_STATE_EMPTY, + SPONDWORK_STATE_IN_BUSY, +} SPONDWORK_STATE; + +#define MAX_JOBS_IN_MINERGATE MINERGATE_TOTAL_QUEUE // 1.5 sec worth of jobs +#define MAX_NROLES 50 + +typedef struct { + struct work *cgminer_work; + SPONDWORK_STATE state; + uint32_t merkle_root; + time_t start_time; + int job_id[MAX_NROLES]; + int ntime_clones; +} spond_driver_work; + +struct spond_adapter { + pthread_mutex_t lock; + ADAPTER_STATE adapter_state; + void *cgpu; + + // Statistics + int wins; + int good; + int empty; + int bad; + int overflow; + // state + int works_in_driver; + int works_in_minergate_and_pending_tx; + int works_pending_tx; + int socket_fd; + int reset_mg_queue; // 2=reset, 1=fast send, 0=nada + int current_job_id; + int parse_resp; + minergate_req_packet* mp_next_req; + minergate_rsp_packet* mp_last_rsp; + spond_driver_work my_jobs[MAX_JOBS_IN_MINERGATE]; + + // Temperature statistics + int temp_rate; + int front_temp; + int rear_temp_top; + int rear_temp_bot; + + // Last second we polled stats + time_t last_stats; +}; + +// returns non-zero if needs to change ASICs. +int spond_one_sec_timer_scaling(struct spond_adapter *a, int t); +int spond_do_scaling(struct spond_adapter *a); + +extern void one_sec_spondoolies_watchdog(int uptime); + +#define REQUEST_PERIOD (100000) // times per second - in usec +#define REQUEST_SIZE 100 // jobs per request + +#endif diff --git a/driver-spondoolies-sp30-p.c b/driver-spondoolies-sp30-p.c new file mode 100644 index 0000000..2d6b604 --- /dev/null +++ b/driver-spondoolies-sp30-p.c @@ -0,0 +1,47 @@ +/* + * Copyright 2014 Zvi (Zvisha) Shteingart - Spondoolies-tech.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +/* + This file holds functions needed for minergate packet parsing/creation +*/ + +#include "driver-spondoolies-sp30-p.h" +#include "assert.h" +//#include "spond_debug.h" + +#ifndef passert +#define passert assert +#endif + +minergate_req_packet_sp30 *allocate_minergate_packet_req_sp30(uint8_t requester_id, + uint8_t request_id) { + minergate_req_packet_sp30 *p = + (minergate_req_packet_sp30 *)malloc(sizeof(minergate_req_packet_sp30)); + p->requester_id = requester_id; + p->req_count = 0; + p->protocol_version = MINERGATE_PROTOCOL_VERSION_SP30; + p->request_id = request_id; + p->magic = 0xcaf4; + p->mask = 0; + return p; +} + +minergate_rsp_packet_sp30 *allocate_minergate_packet_rsp_sp30(uint8_t requester_id, + uint8_t request_id) { + + minergate_rsp_packet_sp30 *p = + (minergate_rsp_packet_sp30 *)malloc(sizeof(minergate_rsp_packet_sp30)); + p->requester_id = requester_id; + p->rsp_count = 0; + p->protocol_version = MINERGATE_PROTOCOL_VERSION_SP30; + p->request_id = request_id; + p->magic = 0xcaf4; + p->gh_div_50_rate= 0; + return p; +} diff --git a/driver-spondoolies-sp30-p.h b/driver-spondoolies-sp30-p.h new file mode 100644 index 0000000..27f755b --- /dev/null +++ b/driver-spondoolies-sp30-p.h @@ -0,0 +1,83 @@ +/* + * Copyright 2014 Zvi (Zvisha) Shteingart - Spondoolies-tech.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + * + * Note that changing this SW will void your miners guaranty + */ + + +#ifndef ____MINERGATE_LIB30_H___ +#define ____MINERGATE_LIB30_H___ + +#include +#include +#include +#include +#include +#include +#include +#include + + +#define MINERGATE_PROTOCOL_VERSION_SP30 30 +#define MINERGATE_SOCKET_FILE_SP30 "/tmp/connection_pipe_sp30" + +typedef enum { + MINERGATE_DATA_ID_DO_JOB_REQ_SP30 = 5, + MINERGATE_DATA_ID_DO_JOB_RSP_SP30 = 6, +} MINERGATE_DATA_ID_SP30; + +typedef struct { + uint32_t work_id_in_sw; + uint32_t difficulty; + uint32_t timestamp; + uint32_t mrkle_root; + uint32_t midstate[8]; + uint8_t leading_zeroes; + uint8_t ntime_limit; // max ntime - should be 60 + uint8_t resr2; + uint8_t resr1; +} minergate_do_job_req_sp30; + +#define MAX_REQUESTS_SP30 30 +#define MAX_RESPONDS_SP30 60 +#define MINERGATE_ADAPTER_QUEUE_SP30 40 + +typedef struct { + uint32_t work_id_in_sw; + uint32_t mrkle_root; // to validate + uint32_t winner_nonce; + uint8_t ntime_offset; + uint8_t res; // 0 = done, 1 = overflow, 2 = dropped bist + uint8_t job_complete; + uint8_t resrv2; +} minergate_do_job_rsp_sp30; + +typedef struct { + uint8_t requester_id; + uint8_t request_id; + uint8_t protocol_version; + uint8_t mask; // 0x01 = first request, 0x2 = drop old work + uint16_t magic; // 0xcaf4 + uint16_t req_count; + minergate_do_job_req_sp30 req[MAX_REQUESTS_SP30]; // array of requests +} minergate_req_packet_sp30; + +typedef struct { + uint8_t requester_id; + uint8_t request_id; + uint8_t protocol_version; + uint8_t gh_div_50_rate; + uint16_t magic; // 0xcaf4 + uint16_t rsp_count; + minergate_do_job_rsp_sp30 rsp[MAX_RESPONDS_SP30]; // array of responces +} minergate_rsp_packet_sp30; + +minergate_req_packet_sp30* allocate_minergate_packet_req_sp30(uint8_t requester_id,uint8_t request_id); +minergate_rsp_packet_sp30* allocate_minergate_packet_rsp_sp30(uint8_t requester_id,uint8_t request_id); + +#endif diff --git a/driver-spondoolies-sp30.c b/driver-spondoolies-sp30.c new file mode 100644 index 0000000..f7664d3 --- /dev/null +++ b/driver-spondoolies-sp30.c @@ -0,0 +1,489 @@ +/* + * Copyright 2014 Con Kolivas + * Copyright 2014 Zvi (Zvisha) Shteingart - Spondoolies-tech.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +/* + This driver communicates the job requests via Unix socket to the minergate + process, that is responsible for controlling the Spondoolies Dawson SP10 miner. + + The jobs sent each with unique ID and returned asynchronously in one of the next + transactions. REQUEST_PERIOD and REQUEST_SIZE define the communication rate with minergate. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" + +#ifdef WIN32 +#include +#endif + +#include "compat.h" +#include "miner.h" +#include "driver-spondoolies-sp30-p.h" +#include "driver-spondoolies-sp30.h" + +#ifdef WORDS_BIGENDIAN +# define swap32tobe(out, in, sz) ((out == in) ? (void)0 : memmove(out, in, sz)) +# define LOCAL_swap32be(type, var, sz) ; +# define swap32tole(out, in, sz) swap32yes(out, in, sz) +# define LOCAL_swap32le(type, var, sz) LOCAL_swap32(type, var, sz) +#else +# define swap32tobe(out, in, sz) swap32yes(out, in, sz) +# define LOCAL_swap32be(type, var, sz) LOCAL_swap32(type, var, sz) +# define swap32tole(out, in, sz) ((out == in) ? (void)0 : memmove(out, in, sz)) +# define LOCAL_swap32le(type, var, sz) ; +#endif + +static inline void swap32yes(void *out, const void *in, size_t sz) +{ + size_t swapcounter; + + for (swapcounter = 0; swapcounter < sz; ++swapcounter) + (((uint32_t*)out)[swapcounter]) = swab32(((uint32_t*)in)[swapcounter]); +} + +static void send_minergate_pkt(const minergate_req_packet_sp30* mp_req, minergate_rsp_packet_sp30* mp_rsp, + int socket_fd) +{ + int nbytes, nwrote, nread; + + nbytes = sizeof(minergate_req_packet_sp30); + nwrote = write(socket_fd, (const void *)mp_req, nbytes); + if (unlikely(nwrote != nbytes)) + _quit(-1); + nbytes = sizeof(minergate_rsp_packet_sp30); + nread = read(socket_fd, (void *)mp_rsp, nbytes); + if (unlikely(nread != nbytes)) + _quit(-1); + assert(mp_rsp->magic == 0xcaf4); +} + +static bool spondoolies_prepare_sp30(struct thr_info *thr) +{ + struct cgpu_info *spondoolies_sp30 = thr->cgpu; + struct timeval now; + + assert(spondoolies_sp30); + cgtime(&now); + /* FIXME: Vladik */ +#if NEED_FIX + get_datestamp(spondoolies_sp30->init, &now); +#endif + return true; +} + +static int init_socket(void) +{ + int socket_fd; + struct sockaddr_un address; + + printf("Init\n"); + socket_fd = socket(PF_UNIX, SOCK_STREAM, 0); + if (socket_fd < 0) { + printf("socket() failed\n"); + perror("Err:"); + return 0; + } + + /* start with a clean address structure */ + memset(&address, 0, sizeof(struct sockaddr_un)); + + address.sun_family = AF_UNIX; + sprintf(address.sun_path, MINERGATE_SOCKET_FILE_SP30); + + if (connect(socket_fd, (struct sockaddr *) &address, sizeof(struct sockaddr_un))) { + printf("connect() failed\n"); + perror("Err:"); + return 0; + } + + return socket_fd; +} + +static bool spondoolies_flush_queue(struct spond_adapter* a, bool flush_queue) +{ + if (!a->parse_resp) { + static int i = 0; + + if (i++ % 10 == 0 && a->works_in_minergate_and_pending_tx + a->works_pending_tx != a->works_in_driver) + printf("%d + %d != %d\n", a->works_in_minergate_and_pending_tx, a->works_pending_tx,a->works_in_driver); + assert(a->works_in_minergate_and_pending_tx + a->works_pending_tx == a->works_in_driver); + send_minergate_pkt(a->mp_next_req, a->mp_last_rsp, a->socket_fd); + if (flush_queue) { + printf("FLUSH!\n"); + a->mp_next_req->mask |= 0x02; + } else { + a->mp_next_req->mask &= ~0x02; + } + + a->mp_next_req->req_count = 0; + a->parse_resp = 1; + a->works_in_minergate_and_pending_tx += a->works_pending_tx; + a->works_pending_tx = 0; + } + return true; +} + +static void spondoolies_detect_sp30(__maybe_unused bool hotplug) +{ + struct cgpu_info *cgpu = calloc(1, sizeof(*cgpu)); + struct device_drv *drv = &sp30_drv; + struct spond_adapter *a; + +#if NEED_FIX + nDevs = 1; +#endif + + assert(cgpu); + cgpu->drv = drv; + cgpu->deven = DEV_ENABLED; + cgpu->threads = 1; + cgpu->device_data = calloc(sizeof(struct spond_adapter), 1); + if (unlikely(!(cgpu->device_data))) + quit(1, "Failed to calloc cgpu_info data"); + a = cgpu->device_data; + a->cgpu = (void *)cgpu; + a->adapter_state = ADAPTER_STATE_OPERATIONAL; + a->mp_next_req = allocate_minergate_packet_req_sp30(0xca, 0xfe); + a->mp_last_rsp = allocate_minergate_packet_rsp_sp30(0xca, 0xfe); + + pthread_mutex_init(&a->lock, NULL); + a->socket_fd = init_socket(); + if (a->socket_fd < 1) { + printf("Error connecting to minergate server!"); + _quit(-1); + } + + assert(add_cgpu(cgpu)); + // Clean MG socket + spondoolies_flush_queue(a, true); + spondoolies_flush_queue(a, true); + spondoolies_flush_queue(a, true); + applog(LOG_DEBUG, "SPOND spondoolies_detect_sp30 done"); +} + +static struct api_data *spondoolies_api_stats_sp30(struct cgpu_info *cgpu) +{ + struct spond_adapter *a = cgpu->device_data; + struct api_data *root = NULL; + + root = api_add_int(root, "ASICs total rate", &a->temp_rate, false); + root = api_add_int(root, "Temperature front", &a->front_temp, false); + root = api_add_int(root, "Temperature rear top", &a->rear_temp_top, false); + root = api_add_int(root, "Temperature rear bot", &a->rear_temp_bot, false); + + + + return root; +} + +#if 0 +static unsigned char get_leading_zeroes(const unsigned char *target) +{ + unsigned char leading = 0; + int first_non_zero_chr; + uint8_t m; + + for (first_non_zero_chr = 31; first_non_zero_chr >= 0; first_non_zero_chr--) { + if (target[first_non_zero_chr] == 0) + leading += 8; + else + break; + } + + // j = first non-zero + m = target[first_non_zero_chr]; + while ((m & 0x80) == 0) { + leading++; + m = m << 1; + } + return leading; +} +#endif + +static void spondoolies_shutdown_sp30(__maybe_unused struct thr_info *thr) +{ +} + +static void fill_minergate_request(minergate_do_job_req_sp30* work, struct work *cg_work, int max_offset) +{ + uint32_t x[64 / 4]; + uint64_t wd; + + memset(work, 0, sizeof(minergate_do_job_req_sp30)); + //work-> + LOCAL_swap32le(unsigned char, cg_work->midstate, 32 / 4) + LOCAL_swap32le(unsigned char, cg_work->data + 64, 64 / 4) + swap32yes(x, cg_work->data + 64, 64 / 4); + memcpy(work->midstate, cg_work->midstate, 32); + work->mrkle_root = ntohl(x[0]); + work->timestamp = ntohl(x[1]); + work->difficulty = ntohl(x[2]); + //work->leading_zeroes = get_leading_zeroes(cg_work->target); + // Is there no better way to get leading zeroes? + work->leading_zeroes = 31; + wd = round(cg_work->device_diff); + while (wd) { + work->leading_zeroes++; + wd = wd >> 1; + } + //printf("%d %d\n",work->leading_zeroes, (int)round(cg_work->work_difficulty)); + work->work_id_in_sw = cg_work->subid; + work->ntime_limit = max_offset; + //printf("ID:%d, TS:%x\n",work->work_id_in_sw,work->timestamp); + //work->ntime_offset = ntime_offset; +} + +// returns true if queue full. +static struct timeval last_force_queue; + +unsigned long usec_stamp(void) +{ + static unsigned long long int first_usec = 0; + struct timeval tv; + unsigned long long int curr_usec; + + cgtime(&tv); + curr_usec = tv.tv_sec * 1000000 + tv.tv_usec; + if (first_usec == 0) { + first_usec = curr_usec; + curr_usec = 0; + } else + curr_usec -= first_usec; + return curr_usec; +} + +static bool spondoolies_queue_full_sp30(struct cgpu_info *cgpu) +{ + struct spond_adapter* a = cgpu->device_data; +#if 0 + static int bla = 0; + + if (!((bla++)%500)) { + printf("FAKE TEST FLUSH T:%d!\n",usec_stamp()); + a->reset_mg_queue = 3; + } +#endif + // Only once every 1/10 second do work. + bool ret = false, do_sleep = false; + int next_job_id; + struct timeval tv; + struct work *work; + unsigned int usec; + + mutex_lock(&a->lock); + assert(a->works_pending_tx <= REQUEST_SIZE); + + gettimeofday(&tv, NULL); + + usec = (tv.tv_sec-last_force_queue.tv_sec) * 1000000; + usec += (tv.tv_usec-last_force_queue.tv_usec); + + if ((usec >= REQUEST_PERIOD) || + (a->reset_mg_queue == 3) || // push flush + ((a->reset_mg_queue == 2)) || // Fast pull + ((a->reset_mg_queue == 1) && (a->works_pending_tx == REQUEST_SIZE))) { // Fast push after flush + spondoolies_flush_queue(a, (a->reset_mg_queue == 3)); + if (a->reset_mg_queue) { + //printf("FLUSH(%d) %d T:%d\n",a->reset_mg_queue , a->works_pending_tx, usec_stamp()); + if (a->works_pending_tx || (a->reset_mg_queue == 3)) { + a->reset_mg_queue--; + } + } + last_force_queue = tv; + } + + // see if we have enough jobs + if (a->works_pending_tx == REQUEST_SIZE) { + ret = true; + goto return_unlock; + } + + // see if can take 1 more job. + // Must be smaller to prevent overflow. + assert(MAX_JOBS_PENDING_IN_MINERGATE_SP30 < MINERGATE_ADAPTER_QUEUE_SP30); + next_job_id = (a->current_job_id + 1) % MAX_JOBS_PENDING_IN_MINERGATE_SP30; + if (a->my_jobs[next_job_id].cgminer_work) { + ret = true; + goto return_unlock; + } + work = get_queued(cgpu); + if (unlikely(!work)) { + do_sleep = true; + goto return_unlock; + } + + work->thr = cgpu->thr[0]; + work->thr_id = cgpu->thr[0]->id; + assert(work->thr); + + a->current_job_id = next_job_id; + work->subid = a->current_job_id; + // Get pointer for the request + a->my_jobs[a->current_job_id].cgminer_work = work; + a->my_jobs[a->current_job_id].state = SPONDWORK_STATE_IN_BUSY; + //printf("Push: %d\n", a->current_job_id); + + int max_ntime_roll = (work->drv_rolllimit < MAX_NROLES) ? work->drv_rolllimit : MAX_NROLES; + minergate_do_job_req_sp30* pkt_job = &a->mp_next_req->req[a->works_pending_tx]; + fill_minergate_request(pkt_job, work, max_ntime_roll); + a->works_in_driver++; + a->works_pending_tx++; + a->mp_next_req->req_count++; + a->my_jobs[a->current_job_id].merkle_root = pkt_job->mrkle_root; + +return_unlock: + //printf("D:P.TX:%d inD:%d\n", a->works_pending_tx, a->works_in_driver); + mutex_unlock(&a->lock); + + if (do_sleep) + cgsleep_ms(10); + + return ret; +} + +static void spond_poll_stats(struct cgpu_info *spond, struct spond_adapter *a) +{ + FILE *fp = fopen("/var/run/mg_rate_temp", "r"); + + if (!fp) { + applog(LOG_DEBUG, "SPOND unable to open mg_rate_temp"); + a->temp_rate = a->front_temp = a->rear_temp_top = a->rear_temp_bot = 0; + } else { + int ret = fscanf(fp, "%d %d %d %d", &a->temp_rate, &a->front_temp , &a->rear_temp_top , &a->rear_temp_bot); + + + if (ret != 4) + a->temp_rate = a->front_temp = a->rear_temp_top = a->rear_temp_bot = 0; + fclose(fp); + } + applog(LOG_DEBUG, "SPOND poll_stats rate: %d front: %d rear(T/B): %d/%d", + a->temp_rate, a->front_temp , a->rear_temp_top, a->rear_temp_bot); + /* Use the rear temperature as the dev temperature for now */ + spond->temp = (a->rear_temp_top + a->rear_temp_bot)/2; +} + +// Return completed work to submit_nonce() and work_completed() +// struct timeval last_force_queue = {0}; +static int64_t spond_scanhash_sp30(struct thr_info *thr) +{ + struct cgpu_info *cgpu = thr->cgpu; + struct spond_adapter *a = cgpu->device_data; + int64_t ghashes = 0; + cgtimer_t cgt; + time_t now_t; + + cgsleep_prepare_r(&cgt); + now_t = time(NULL); + /* Poll stats only once per second */ + if (now_t != a->last_stats) { + a->last_stats = now_t; + spond_poll_stats(cgpu, a); + } + + if (a->parse_resp) { + int array_size, i; + + mutex_lock(&a->lock); + //ghashes = (a->mp_last_rsp->gh_div_50_rate); + //ghashes = ghashes * 50000 * REQUEST_PERIOD; + array_size = a->mp_last_rsp->rsp_count; + for (i = 0; i < array_size; i++) { // walk the jobs + int job_id; + + minergate_do_job_rsp_sp30* work = a->mp_last_rsp->rsp + i; + job_id = work->work_id_in_sw; + if ((a->my_jobs[job_id].cgminer_work)) { + if (a->my_jobs[job_id].merkle_root == work->mrkle_root) { + assert(a->my_jobs[job_id].state == SPONDWORK_STATE_IN_BUSY); + + if (work->winner_nonce) { + struct work *cg_work = a->my_jobs[job_id].cgminer_work; + bool ok; + + ok = submit_noffset_nonce(cg_work->thr, cg_work, work->winner_nonce, work->ntime_offset); + if (ok) + ghashes += 0xffffffffull * cg_work->device_diff; + /*printf("WIn on %d (+%d), none=%x = %d\n", + * work->work_id_in_sw, work->ntime_offset, htole32(work->winner_nonce), ok);*/ + a->wins++; + } + + //printf("%d ntime_clones = %d\n",job_id,a->my_jobs[job_id].ntime_clones); + + //printf("Done with %d\n", job_id); + if (work->job_complete) { + //printf("Complete %d\n", job_id); + work_completed(a->cgpu, a->my_jobs[job_id].cgminer_work); + a->good++; + a->my_jobs[job_id].cgminer_work = NULL; + a->my_jobs[job_id].state = SPONDWORK_STATE_EMPTY; + a->works_in_minergate_and_pending_tx--; + a->works_in_driver--; + } + } else { + a->bad++; + printf("Dropping minergate old job id=%d mrkl=%x my-mrkl=%x\n", + job_id, a->my_jobs[job_id].merkle_root, work->mrkle_root); + } + } else { + a->empty++; + printf("No cgminer job (id:%d res:%d)!\n",job_id, work->res); + } + } + mutex_unlock(&a->lock); + + a->parse_resp = 0; + } + cgsleep_ms_r(&cgt, 40); + + return ghashes; +} + +// Remove all work from queue +static void spond_flush_work_sp30(struct cgpu_info *cgpu) +{ + struct spond_adapter *a = cgpu->device_data; + + //printf("GOT FLUSH!%d\n"); + mutex_lock(&a->lock); + a->reset_mg_queue = 3; + mutex_unlock(&a->lock); +} + +struct device_drv sp30_drv = { + .drv_id = DRIVER_sp30, + .dname = "Sp30", + .name = "S30", + .min_diff = 16, + .max_diff = 1024.0, // Limit max diff to get some nonces back regardless + .drv_detect = spondoolies_detect_sp30, + .get_api_stats = spondoolies_api_stats_sp30, + .thread_prepare = spondoolies_prepare_sp30, + .thread_shutdown = spondoolies_shutdown_sp30, + .hash_work = hash_queued_work, + .queue_full = spondoolies_queue_full_sp30, + .scanwork = spond_scanhash_sp30, + .flush_work = spond_flush_work_sp30, +}; diff --git a/driver-spondoolies-sp30.h b/driver-spondoolies-sp30.h new file mode 100644 index 0000000..ced4eb4 --- /dev/null +++ b/driver-spondoolies-sp30.h @@ -0,0 +1,85 @@ +/* + * Copyright 2014 Con Kolivas + * Copyright 2014 Zvi Shteingart - Spondoolies-tech.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#ifndef SPONDA_HFILE +#define SPONDA_HFILE + +#include "miner.h" +#include "driver-spondoolies-sp30-p.h" + + + +typedef enum adapter_state { + ADAPTER_STATE_INIT, + ADAPTER_STATE_OPERATIONAL, +} ADAPTER_STATE; + +typedef enum spond_work_state { + SPONDWORK_STATE_EMPTY, + SPONDWORK_STATE_IN_BUSY, +} SPONDWORK_STATE; + +#define MAX_JOBS_PENDING_IN_MINERGATE_SP30 30 +#define MAX_NROLES 60 + + +typedef struct { + struct work *cgminer_work; + SPONDWORK_STATE state; + uint32_t merkle_root; + time_t start_time; + int job_id; +} spond_driver_work_sp30; + + + +struct spond_adapter { + pthread_mutex_t lock; + ADAPTER_STATE adapter_state; + void *cgpu; + + // Statistics + int wins; + int good; + int empty; + int bad; + int overflow; + // state + int works_in_driver; + int works_in_minergate_and_pending_tx; + int works_pending_tx; + int socket_fd; + int reset_mg_queue; // 3=reset, 2=fast send 1 job, 1=fast send 10 jobs, 0=nada + int current_job_id; + int parse_resp; + minergate_req_packet_sp30* mp_next_req; + minergate_rsp_packet_sp30* mp_last_rsp; + spond_driver_work_sp30 my_jobs[MAX_JOBS_PENDING_IN_MINERGATE_SP30]; + + // Temperature statistics + int temp_rate; + int front_temp; + int rear_temp_top; + int rear_temp_bot; + + // Last second we polled stats + time_t last_stats; +}; + +// returns non-zero if needs to change ASICs. +int spond_one_sec_timer_scaling(struct spond_adapter *a, int t); +int spond_do_scaling(struct spond_adapter *a); + +extern void one_sec_spondoolies_watchdog(int uptime); + +#define REQUEST_PERIOD (100000) // times per second - in usec +#define REQUEST_SIZE 10 // jobs per request + +#endif diff --git a/elist.h b/elist.h new file mode 100644 index 0000000..afd5937 --- /dev/null +++ b/elist.h @@ -0,0 +1,256 @@ +#ifndef _LINUX_LIST_H +#define _LINUX_LIST_H + +/* + * Simple doubly linked list implementation. + * + * Some of the internal functions ("__xxx") are useful when + * manipulating whole lists rather than single entries, as + * sometimes we already know the next/prev entries and we can + * generate better code by using them directly rather than + * using the generic single-entry routines. + */ + +struct list_head { + struct list_head *next, *prev; +}; + +#define LIST_HEAD_INIT(name) { &(name), &(name) } + +#define LIST_HEAD(name) \ + struct list_head name = LIST_HEAD_INIT(name) + +#define INIT_LIST_HEAD(ptr) do { \ + (ptr)->next = (ptr); (ptr)->prev = (ptr); \ +} while (0) + +/* + * Insert a new entry between two known consecutive entries. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static inline void __list_add(struct list_head *new, + struct list_head *prev, + struct list_head *next) +{ + next->prev = new; + new->next = next; + new->prev = prev; + prev->next = new; +} + +/** + * list_add - add a new entry + * @new: new entry to be added + * @head: list head to add it after + * + * Insert a new entry after the specified head. + * This is good for implementing stacks. + */ +static inline void list_add(struct list_head *new, struct list_head *head) +{ + __list_add(new, head, head->next); +} + +/** + * list_add_tail - add a new entry + * @new: new entry to be added + * @head: list head to add it before + * + * Insert a new entry before the specified head. + * This is useful for implementing queues. + */ +static inline void list_add_tail(struct list_head *new, struct list_head *head) +{ + __list_add(new, head->prev, head); +} + +/* + * Delete a list entry by making the prev/next entries + * point to each other. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static inline void __list_del(struct list_head *prev, struct list_head *next) +{ + next->prev = prev; + prev->next = next; +} + +/** + * list_del - deletes entry from list. + * @entry: the element to delete from the list. + * Note: list_empty on entry does not return true after this, the entry is in an undefined state. + */ +static inline void list_del(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + entry->next = (void *) 0; + entry->prev = (void *) 0; +} + +/** + * list_del_init - deletes entry from list and reinitialize it. + * @entry: the element to delete from the list. + */ +static inline void list_del_init(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + INIT_LIST_HEAD(entry); +} + +/** + * list_move - delete from one list and add as another's head + * @list: the entry to move + * @head: the head that will precede our entry + */ +static inline void list_move(struct list_head *list, struct list_head *head) +{ + __list_del(list->prev, list->next); + list_add(list, head); +} + +/** + * list_move_tail - delete from one list and add as another's tail + * @list: the entry to move + * @head: the head that will follow our entry + */ +static inline void list_move_tail(struct list_head *list, + struct list_head *head) +{ + __list_del(list->prev, list->next); + list_add_tail(list, head); +} + +/** + * list_empty - tests whether a list is empty + * @head: the list to test. + */ +static inline int list_empty(struct list_head *head) +{ + return head->next == head; +} + +static inline void __list_splice(struct list_head *list, + struct list_head *head) +{ + struct list_head *first = list->next; + struct list_head *last = list->prev; + struct list_head *at = head->next; + + first->prev = head; + head->next = first; + + last->next = at; + at->prev = last; +} + +/** + * list_splice - join two lists + * @list: the new list to add. + * @head: the place to add it in the first list. + */ +static inline void list_splice(struct list_head *list, struct list_head *head) +{ + if (!list_empty(list)) + __list_splice(list, head); +} + +/** + * list_splice_init - join two lists and reinitialise the emptied list. + * @list: the new list to add. + * @head: the place to add it in the first list. + * + * The list at @list is reinitialised + */ +static inline void list_splice_init(struct list_head *list, + struct list_head *head) +{ + if (!list_empty(list)) { + __list_splice(list, head); + INIT_LIST_HEAD(list); + } +} + +/** + * list_entry - get the struct for this entry + * @ptr: the &struct list_head pointer. + * @type: the type of the struct this is embedded in. + * @member: the name of the list_struct within the struct. + */ +#ifndef _WIN64 +#define list_entry(ptr, type, member) \ + ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))) +#else +#define list_entry(ptr, type, member) \ + ((type *)((char *)(ptr)-(unsigned long long)(&((type *)0)->member))) +#endif + +/** + * list_for_each - iterate over a list + * @pos: the &struct list_head to use as a loop counter. + * @head: the head for your list. + */ +#define list_for_each(pos, head) \ + for (pos = (head)->next; pos != (head); \ + pos = pos->next) +/** + * list_for_each_prev - iterate over a list backwards + * @pos: the &struct list_head to use as a loop counter. + * @head: the head for your list. + */ +#define list_for_each_prev(pos, head) \ + for (pos = (head)->prev; pos != (head); \ + pos = pos->prev) + +/** + * list_for_each_safe - iterate over a list safe against removal of list entry + * @pos: the &struct list_head to use as a loop counter. + * @n: another &struct list_head to use as temporary storage + * @head: the head for your list. + */ +#define list_for_each_safe(pos, n, head) \ + for (pos = (head)->next, n = pos->next; pos != (head); \ + pos = n, n = pos->next) + +/** + * list_for_each_entry - iterate over list of given type + * @pos: the type * to use as a loop counter. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry(pos, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = list_entry(pos->member.next, typeof(*pos), member)) + +/** + * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry + * @pos: the type * to use as a loop counter. + * @n: another type * to use as temporary storage + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry_safe(pos, n, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member), \ + n = list_entry(pos->member.next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.next, typeof(*n), member)) + +/** + * list_for_each_entry_continue - iterate over list of given type + * continuing after existing point + * @pos: the type * to use as a loop counter. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry_continue(pos, head, member) \ + for (pos = list_entry(pos->member.next, typeof(*pos), member), \ + prefetch(pos->member.next); \ + &pos->member != (head); \ + pos = list_entry(pos->member.next, typeof(*pos), member), \ + prefetch(pos->member.next)) + +#endif diff --git a/example.conf b/example.conf new file mode 100644 index 0000000..2448ed5 --- /dev/null +++ b/example.conf @@ -0,0 +1,25 @@ +{ +"pools" : [ + { + "url" : "http://url1:8332", + "user" : "user1", + "pass" : "pass1" + }, + { + "url" : "http://url2:8344", + "user" : "user2", + "pass" : "pass2" + }, + { + "url" : "http://url3:8332", + "user" : "user3", + "pass" : "pass3" + } +], + +"failover-only" : true, +"no-submit-stale" : true, +"api-listen" : true, +"api-port" : "4028", +"api-allow" : "W:192.168.1.0/24,W:127.0.0.1" +} diff --git a/fpgautils.c b/fpgautils.c new file mode 100644 index 0000000..8c262e7 --- /dev/null +++ b/fpgautils.c @@ -0,0 +1,610 @@ +/* + * Copyright 2013 Con Kolivas + * Copyright 2012 Luke Dashjr + * Copyright 2012 Andrew Smith + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#include "config.h" + +#include +#include +#include + +#include "miner.h" + +#ifndef WIN32 +#include +#include +#include +#include +#include +#include +#ifndef O_CLOEXEC +#define O_CLOEXEC 0 +#endif +#else +#include +#include +#endif + +#ifdef HAVE_LIBUDEV +#include +#include +#endif + +#include "elist.h" +#include "logging.h" +#include "miner.h" +#include "fpgautils.h" + +#ifdef HAVE_LIBUDEV +int serial_autodetect_udev(detectone_func_t detectone, const char*prodname) +{ + struct udev *udev = udev_new(); + struct udev_enumerate *enumerate = udev_enumerate_new(udev); + struct udev_list_entry *list_entry; + char found = 0; + + udev_enumerate_add_match_subsystem(enumerate, "tty"); + udev_enumerate_add_match_property(enumerate, "ID_MODEL", prodname); + udev_enumerate_scan_devices(enumerate); + udev_list_entry_foreach(list_entry, udev_enumerate_get_list_entry(enumerate)) { + struct udev_device *device = udev_device_new_from_syspath( + udev_enumerate_get_udev(enumerate), + udev_list_entry_get_name(list_entry) + ); + if (!device) + continue; + + const char *devpath = udev_device_get_devnode(device); + if (devpath && detectone(devpath)) + ++found; + + udev_device_unref(device); + } + udev_enumerate_unref(enumerate); + udev_unref(udev); + + return found; +} +#else +int serial_autodetect_udev(__maybe_unused detectone_func_t detectone, __maybe_unused const char*prodname) +{ + return 0; +} +#endif + +int serial_autodetect_devserial(__maybe_unused detectone_func_t detectone, __maybe_unused const char*prodname) +{ +#ifndef WIN32 + DIR *D; + struct dirent *de; + const char udevdir[] = "/dev/serial/by-id"; + char devpath[sizeof(udevdir) + 1 + NAME_MAX]; + char *devfile = devpath + sizeof(udevdir); + char found = 0; + + D = opendir(udevdir); + if (!D) + return 0; + memcpy(devpath, udevdir, sizeof(udevdir) - 1); + devpath[sizeof(udevdir) - 1] = '/'; + while ( (de = readdir(D)) ) { + if (!strstr(de->d_name, prodname)) + continue; + strcpy(devfile, de->d_name); + if (detectone(devpath)) + ++found; + } + closedir(D); + + return found; +#else + return 0; +#endif +} + +int _serial_detect(struct device_drv *drv, detectone_func_t detectone, autoscan_func_t autoscan, bool forceauto) +{ + struct string_elist *iter, *tmp; + const char *dev, *colon; + bool inhibitauto = false; + char found = 0; + size_t namel = strlen(drv->name); + size_t dnamel = strlen(drv->dname); + + list_for_each_entry_safe(iter, tmp, &scan_devices, list) { + dev = iter->string; + if ((colon = strchr(dev, ':')) && colon[1] != '\0') { + size_t idlen = colon - dev; + + // allow either name:device or dname:device + if ((idlen != namel || strncasecmp(dev, drv->name, idlen)) + && (idlen != dnamel || strncasecmp(dev, drv->dname, idlen))) + continue; + + dev = colon + 1; + } + if (!strcmp(dev, "auto")) + forceauto = true; + else if (!strcmp(dev, "noauto")) + inhibitauto = true; + else if (detectone(dev)) { + string_elist_del(iter); + inhibitauto = true; + ++found; + } + } + + if ((forceauto || !inhibitauto) && autoscan) + found += autoscan(); + + return found; +} + +// This code is purely for debugging but is very useful for that +// It also took quite a bit of effort so I left it in +// #define TERMIOS_DEBUG 1 +// Here to include it at compile time +// It's off by default +#ifndef WIN32 +#ifdef TERMIOS_DEBUG + +#define BITSSET "Y" +#define BITSNOTSET "N" + +int tiospeed(speed_t speed) +{ + switch (speed) { + case B0: + return 0; + case B50: + return 50; + case B75: + return 75; + case B110: + return 110; + case B134: + return 134; + case B150: + return 150; + case B200: + return 200; + case B300: + return 300; + case B600: + return 600; + case B1200: + return 1200; + case B1800: + return 1800; + case B2400: + return 2400; + case B4800: + return 4800; + case B9600: + return 9600; + case B19200: + return 19200; + case B38400: + return 38400; + case B57600: + return 57600; + case B115200: + return 115200; + case B230400: + return 230400; + case B460800: + return 460800; + case B500000: + return 500000; + case B576000: + return 576000; + case B921600: + return 921600; + case B1000000: + return 1000000; + case B1152000: + return 1152000; + case B1500000: + return 1500000; + case B2000000: + return 2000000; + case B2500000: + return 2500000; + case B3000000: + return 3000000; + case B3500000: + return 3500000; + case B4000000: + return 4000000; + default: + return -1; + } +} + +void termios_debug(const char *devpath, struct termios *my_termios, const char *msg) +{ + applog(LOG_DEBUG, "TIOS: Open %s attributes %s: ispeed=%d ospeed=%d", + devpath, msg, tiospeed(cfgetispeed(my_termios)), tiospeed(cfgetispeed(my_termios))); + +#define ISSETI(b) ((my_termios->c_iflag | (b)) ? BITSSET : BITSNOTSET) + + applog(LOG_DEBUG, "TIOS: c_iflag: IGNBRK=%s BRKINT=%s IGNPAR=%s PARMRK=%s INPCK=%s ISTRIP=%s INLCR=%s IGNCR=%s ICRNL=%s IUCLC=%s IXON=%s IXANY=%s IOFF=%s IMAXBEL=%s IUTF8=%s", + ISSETI(IGNBRK), ISSETI(BRKINT), ISSETI(IGNPAR), ISSETI(PARMRK), + ISSETI(INPCK), ISSETI(ISTRIP), ISSETI(INLCR), ISSETI(IGNCR), + ISSETI(ICRNL), ISSETI(IUCLC), ISSETI(IXON), ISSETI(IXANY), + ISSETI(IXOFF), ISSETI(IMAXBEL), ISSETI(IUTF8)); + +#define ISSETO(b) ((my_termios->c_oflag | (b)) ? BITSSET : BITSNOTSET) +#define VALO(b) (my_termios->c_oflag | (b)) + + applog(LOG_DEBUG, "TIOS: c_oflag: OPOST=%s OLCUC=%s ONLCR=%s OCRNL=%s ONOCR=%s ONLRET=%s OFILL=%s OFDEL=%s NLDLY=%d CRDLY=%d TABDLY=%d BSDLY=%d VTDLY=%d FFDLY=%d", + ISSETO(OPOST), ISSETO(OLCUC), ISSETO(ONLCR), ISSETO(OCRNL), + ISSETO(ONOCR), ISSETO(ONLRET), ISSETO(OFILL), ISSETO(OFDEL), + VALO(NLDLY), VALO(CRDLY), VALO(TABDLY), VALO(BSDLY), + VALO(VTDLY), VALO(FFDLY)); + +#define ISSETC(b) ((my_termios->c_cflag | (b)) ? BITSSET : BITSNOTSET) +#define VALC(b) (my_termios->c_cflag | (b)) + + applog(LOG_DEBUG, "TIOS: c_cflag: CBAUDEX=%s CSIZE=%d CSTOPB=%s CREAD=%s PARENB=%s PARODD=%s HUPCL=%s CLOCAL=%s" +#ifdef LOBLK + " LOBLK=%s" +#endif + " CMSPAR=%s CRTSCTS=%s", + ISSETC(CBAUDEX), VALC(CSIZE), ISSETC(CSTOPB), ISSETC(CREAD), + ISSETC(PARENB), ISSETC(PARODD), ISSETC(HUPCL), ISSETC(CLOCAL), +#ifdef LOBLK + ISSETC(LOBLK), +#endif + ISSETC(CMSPAR), ISSETC(CRTSCTS)); + +#define ISSETL(b) ((my_termios->c_lflag | (b)) ? BITSSET : BITSNOTSET) + + applog(LOG_DEBUG, "TIOS: c_lflag: ISIG=%s ICANON=%s XCASE=%s ECHO=%s ECHOE=%s ECHOK=%s ECHONL=%s ECHOCTL=%s ECHOPRT=%s ECHOKE=%s" +#ifdef DEFECHO + " DEFECHO=%s" +#endif + " FLUSHO=%s NOFLSH=%s TOSTOP=%s PENDIN=%s IEXTEN=%s", + ISSETL(ISIG), ISSETL(ICANON), ISSETL(XCASE), ISSETL(ECHO), + ISSETL(ECHOE), ISSETL(ECHOK), ISSETL(ECHONL), ISSETL(ECHOCTL), + ISSETL(ECHOPRT), ISSETL(ECHOKE), +#ifdef DEFECHO + ISSETL(DEFECHO), +#endif + ISSETL(FLUSHO), ISSETL(NOFLSH), ISSETL(TOSTOP), ISSETL(PENDIN), + ISSETL(IEXTEN)); + +#define VALCC(b) (my_termios->c_cc[b]) + applog(LOG_DEBUG, "TIOS: c_cc: VINTR=0x%02x VQUIT=0x%02x VERASE=0x%02x VKILL=0x%02x VEOF=0x%02x VMIN=%u VEOL=0x%02x VTIME=%u VEOL2=0x%02x" +#ifdef VSWTCH + " VSWTCH=0x%02x" +#endif + " VSTART=0x%02x VSTOP=0x%02x VSUSP=0x%02x" +#ifdef VDSUSP + " VDSUSP=0x%02x" +#endif + " VLNEXT=0x%02x VWERASE=0x%02x VREPRINT=0x%02x VDISCARD=0x%02x" +#ifdef VSTATUS + " VSTATUS=0x%02x" +#endif + , + VALCC(VINTR), VALCC(VQUIT), VALCC(VERASE), VALCC(VKILL), + VALCC(VEOF), VALCC(VMIN), VALCC(VEOL), VALCC(VTIME), + VALCC(VEOL2), +#ifdef VSWTCH + VALCC(VSWTCH), +#endif + VALCC(VSTART), VALCC(VSTOP), VALCC(VSUSP), +#ifdef VDSUSP + VALCC(VDSUSP), +#endif + VALCC(VLNEXT), VALCC(VWERASE), + VALCC(VREPRINT), VALCC(VDISCARD) +#ifdef VSTATUS + ,VALCC(VSTATUS) +#endif + ); +} +#endif +#endif + +int serial_open(const char *devpath, unsigned long baud, signed short timeout, bool purge) +{ +#ifdef WIN32 + HANDLE hSerial = CreateFile(devpath, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); + if (unlikely(hSerial == INVALID_HANDLE_VALUE)) + { + DWORD e = GetLastError(); + switch (e) { + case ERROR_ACCESS_DENIED: + applog(LOG_ERR, "Do not have user privileges required to open %s", devpath); + break; + case ERROR_SHARING_VIOLATION: + applog(LOG_ERR, "%s is already in use by another process", devpath); + break; + default: + applog(LOG_DEBUG, "Open %s failed, GetLastError:%d", devpath, (int)e); + break; + } + return -1; + } + + // thanks to af_newbie for pointers about this + COMMCONFIG comCfg = {0}; + comCfg.dwSize = sizeof(COMMCONFIG); + comCfg.wVersion = 1; + comCfg.dcb.DCBlength = sizeof(DCB); + comCfg.dcb.BaudRate = baud; + comCfg.dcb.fBinary = 1; + comCfg.dcb.fDtrControl = DTR_CONTROL_ENABLE; + comCfg.dcb.fRtsControl = RTS_CONTROL_ENABLE; + comCfg.dcb.ByteSize = 8; + + SetCommConfig(hSerial, &comCfg, sizeof(comCfg)); + + // Code must specify a valid timeout value (0 means don't timeout) + const DWORD ctoms = (timeout * 100); + COMMTIMEOUTS cto = {ctoms, 0, ctoms, 0, ctoms}; + SetCommTimeouts(hSerial, &cto); + + if (purge) { + PurgeComm(hSerial, PURGE_RXABORT); + PurgeComm(hSerial, PURGE_TXABORT); + PurgeComm(hSerial, PURGE_RXCLEAR); + PurgeComm(hSerial, PURGE_TXCLEAR); + } + + return _open_osfhandle((intptr_t)hSerial, 0); +#else + int fdDev = open(devpath, O_RDWR | O_CLOEXEC | O_NOCTTY); + + if (unlikely(fdDev == -1)) + { + if (errno == EACCES) + applog(LOG_ERR, "Do not have user privileges required to open %s", devpath); + else + applog(LOG_DEBUG, "Open %s failed, errno:%d", devpath, errno); + + return -1; + } + + struct termios my_termios; + + tcgetattr(fdDev, &my_termios); + +#ifdef TERMIOS_DEBUG + termios_debug(devpath, &my_termios, "before"); +#endif + + switch (baud) { + case 0: + break; + case 19200: + cfsetispeed(&my_termios, B19200); + cfsetospeed(&my_termios, B19200); + break; + case 38400: + cfsetispeed(&my_termios, B38400); + cfsetospeed(&my_termios, B38400); + break; + case 57600: + cfsetispeed(&my_termios, B57600); + cfsetospeed(&my_termios, B57600); + break; + case 115200: + cfsetispeed(&my_termios, B115200); + cfsetospeed(&my_termios, B115200); + break; + // TODO: try some higher speeds with the Icarus and BFL to see + // if they support them and if setting them makes any difference + // N.B. B3000000 doesn't work on Icarus + default: + applog(LOG_WARNING, "Unrecognized baud rate: %lu", baud); + } + + my_termios.c_cflag &= ~(CSIZE | PARENB); + my_termios.c_cflag |= CS8; + my_termios.c_cflag |= CREAD; + my_termios.c_cflag |= CLOCAL; + + my_termios.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | + ISTRIP | INLCR | IGNCR | ICRNL | IXON); + my_termios.c_oflag &= ~OPOST; + my_termios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); + + // Code must specify a valid timeout value (0 means don't timeout) + my_termios.c_cc[VTIME] = (cc_t)timeout; + my_termios.c_cc[VMIN] = 0; + +#ifdef TERMIOS_DEBUG + termios_debug(devpath, &my_termios, "settings"); +#endif + + tcsetattr(fdDev, TCSANOW, &my_termios); + +#ifdef TERMIOS_DEBUG + tcgetattr(fdDev, &my_termios); + termios_debug(devpath, &my_termios, "after"); +#endif + + if (purge) + tcflush(fdDev, TCIOFLUSH); + return fdDev; +#endif +} + +ssize_t _serial_read(int fd, char *buf, size_t bufsiz, char *eol) +{ + ssize_t len, tlen = 0; + while (bufsiz) { + len = read(fd, buf, eol ? 1 : bufsiz); + if (unlikely(len == -1)) + break; + tlen += len; + if (eol && *eol == buf[0]) + break; + buf += len; + bufsiz -= len; + } + return tlen; +} + +static FILE *_open_bitstream(const char *path, const char *subdir, const char *filename) +{ + char fullpath[PATH_MAX]; + strcpy(fullpath, path); + strcat(fullpath, "/"); + if (subdir) { + strcat(fullpath, subdir); + strcat(fullpath, "/"); + } + strcat(fullpath, filename); + return fopen(fullpath, "rb"); +} +#define _open_bitstream(path, subdir) do { \ + f = _open_bitstream(path, subdir, filename); \ + if (f) \ + return f; \ +} while(0) + +#define _open_bitstream3(path) do { \ + _open_bitstream(path, dname); \ + _open_bitstream(path, "bitstreams"); \ + _open_bitstream(path, NULL); \ +} while(0) + +FILE *open_bitstream(const char *dname, const char *filename) +{ + FILE *f; + + _open_bitstream3(opt_kernel_path); + _open_bitstream3(cgminer_path); + _open_bitstream3("."); + + return NULL; +} + +#ifndef WIN32 + +static bool _select_wait_read(int fd, struct timeval *timeout) +{ + fd_set rfds; + + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + + if (select(fd+1, &rfds, NULL, NULL, timeout) > 0) + return true; + else + return false; +} + +// Default timeout 100ms - only for device initialisation +const struct timeval tv_timeout_default = { 0, 100000 }; +// Default inter character timeout = 1ms - only for device initialisation +const struct timeval tv_inter_char_default = { 0, 1000 }; + +// Device initialisation function - NOT for work processing +size_t _select_read(int fd, char *buf, size_t bufsiz, struct timeval *timeout, struct timeval *char_timeout, int finished) +{ + struct timeval tv_time, tv_char; + ssize_t siz, red = 0; + char got; + + // timeout is the maximum time to wait for the first character + tv_time.tv_sec = timeout->tv_sec; + tv_time.tv_usec = timeout->tv_usec; + + if (!_select_wait_read(fd, &tv_time)) + return 0; + + while (4242) { + if ((siz = read(fd, buf, 1)) < 0) + return red; + + got = *buf; + buf += siz; + red += siz; + bufsiz -= siz; + + if (bufsiz < 1 || (finished >= 0 && got == finished)) + return red; + + // char_timeout is the maximum time to wait for each subsequent character + // this is OK for initialisation, but bad for work processing + // work processing MUST have a fixed size so this doesn't come into play + tv_char.tv_sec = char_timeout->tv_sec; + tv_char.tv_usec = char_timeout->tv_usec; + + if (!_select_wait_read(fd, &tv_char)) + return red; + } + + return red; +} + +// Device initialisation function - NOT for work processing +size_t _select_write(int fd, char *buf, size_t siz, struct timeval *timeout) +{ + struct timeval tv_time, tv_now, tv_finish; + fd_set rfds; + ssize_t wrote = 0, ret; + + cgtime(&tv_now); + timeradd(&tv_now, timeout, &tv_finish); + + // timeout is the maximum time to spend trying to write + tv_time.tv_sec = timeout->tv_sec; + tv_time.tv_usec = timeout->tv_usec; + + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + + while (siz > 0 && (tv_now.tv_sec < tv_finish.tv_sec || (tv_now.tv_sec == tv_finish.tv_sec && tv_now.tv_usec < tv_finish.tv_usec)) && select(fd+1, NULL, &rfds, NULL, &tv_time) > 0) { + if ((ret = write(fd, buf, 1)) > 0) { + buf++; + wrote++; + siz--; + } + else if (ret < 0) + return wrote; + + cgtime(&tv_now); + } + + return wrote; +} + +int get_serial_cts(int fd) +{ + int flags; + + if (!fd) + return -1; + + ioctl(fd, TIOCMGET, &flags); + return (flags & TIOCM_CTS) ? 1 : 0; +} +#else +int get_serial_cts(const int fd) +{ + if (!fd) + return -1; + const HANDLE fh = (HANDLE)_get_osfhandle(fd); + if (!fh) + return -1; + + DWORD flags; + if (!GetCommModemStatus(fh, &flags)) + return -1; + + return (flags & MS_CTS_ON) ? 1 : 0; +} +#endif // ! WIN32 diff --git a/fpgautils.h b/fpgautils.h new file mode 100644 index 0000000..8a7dd83 --- /dev/null +++ b/fpgautils.h @@ -0,0 +1,84 @@ +/* + * Copyright 2012 Luke Dashjr + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#ifndef FPGAUTILS_H +#define FPGAUTILS_H + +#include +#include + +typedef bool(*detectone_func_t)(const char*); +typedef int(*autoscan_func_t)(); + +extern int _serial_detect(struct device_drv *drv, detectone_func_t, autoscan_func_t, bool force_autoscan); +#define serial_detect_fauto(drv, detectone, autoscan) \ + _serial_detect(drv, detectone, autoscan, true) +#define serial_detect_auto(drv, detectone, autoscan) \ + _serial_detect(drv, detectone, autoscan, false) +#define serial_detect(drv, detectone) \ + _serial_detect(drv, detectone, NULL, false) +extern int serial_autodetect_devserial(detectone_func_t, const char *prodname); +extern int serial_autodetect_udev(detectone_func_t, const char *prodname); + +extern int serial_open(const char *devpath, unsigned long baud, signed short timeout, bool purge); +extern ssize_t _serial_read(int fd, char *buf, size_t buflen, char *eol); +#define serial_read(fd, buf, count) \ + _serial_read(fd, (char*)(buf), count, NULL) +#define serial_read_line(fd, buf, bufsiz, eol) \ + _serial_read(fd, buf, bufsiz, &eol) +#define serial_close(fd) close(fd) + +extern FILE *open_bitstream(const char *dname, const char *filename); + +extern int get_serial_cts(int fd); + +#ifndef WIN32 +extern const struct timeval tv_timeout_default; +extern const struct timeval tv_inter_char_default; + +extern size_t _select_read(int fd, char *buf, size_t bufsiz, struct timeval *timeout, struct timeval *char_timeout, int finished); +extern size_t _select_write(int fd, char *buf, size_t siz, struct timeval *timeout); + +#define select_open(devpath) \ + serial_open(devpath, 0, 0, false) + +#define select_open_purge(devpath, purge)\ + serial_open(devpath, 0, 0, purge) + +#define select_write(fd, buf, siz) \ + _select_write(fd, buf, siz, (struct timeval *)(&tv_timeout_default)) + +#define select_write_full _select_write + +#define select_read(fd, buf, bufsiz) \ + _select_read(fd, buf, bufsiz, (struct timeval *)(&tv_timeout_default), \ + (struct timeval *)(&tv_inter_char_default), -1) + +#define select_read_til(fd, buf, bufsiz, eol) \ + _select_read(fd, buf, bufsiz, (struct timeval *)(&tv_timeout_default), \ + (struct timeval *)(&tv_inter_char_default), eol) + +#define select_read_wait(fd, buf, bufsiz, timeout) \ + _select_read(fd, buf, bufsiz, timeout, \ + (struct timeval *)(&tv_inter_char_default), -1) + +#define select_read_wait_til(fd, buf, bufsiz, timeout, eol) \ + _select_read(fd, buf, bufsiz, timeout, \ + (struct timeval *)(&tv_inter_char_default), eol) + +#define select_read_wait_both(fd, buf, bufsiz, timeout, char_timeout) \ + _select_read(fd, buf, bufsiz, timeout, char_timeout, -1) + +#define select_read_full _select_read + +#define select_close(fd) close(fd) + +#endif // ! WIN32 + +#endif diff --git a/hexdump.c b/hexdump.c new file mode 100644 index 0000000..80b8c2d --- /dev/null +++ b/hexdump.c @@ -0,0 +1,77 @@ +/* + * hexdump implementation without depenecies to *printf() + * output is equal to 'hexdump -C' + * should be compatible to 64bit architectures + * + * Copyright (c) 2009 Daniel Mack + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#define hex_print(p) applog(LOG_DEBUG, "%s", p) + +static char nibble[] = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + +#define BYTES_PER_LINE 0x10 + +static void hexdump(const uint8_t *p, unsigned int len) +{ + unsigned int i, addr; + unsigned int wordlen = sizeof(unsigned int); + unsigned char v, line[BYTES_PER_LINE * 5]; + + for (addr = 0; addr < len; addr += BYTES_PER_LINE) { + /* clear line */ + for (i = 0; i < sizeof(line); i++) { + if (i == wordlen * 2 + 52 || + i == wordlen * 2 + 69) { + line[i] = '|'; + continue; + } + + if (i == wordlen * 2 + 70) { + line[i] = '\0'; + continue; + } + + line[i] = ' '; + } + + /* print address */ + for (i = 0; i < wordlen * 2; i++) { + v = addr >> ((wordlen * 2 - i - 1) * 4); + line[i] = nibble[v & 0xf]; + } + + /* dump content */ + for (i = 0; i < BYTES_PER_LINE; i++) { + int pos = (wordlen * 2) + 3 + (i / 8); + + if (addr + i >= len) + break; + + v = p[addr + i]; + line[pos + (i * 3) + 0] = nibble[v >> 4]; + line[pos + (i * 3) + 1] = nibble[v & 0xf]; + + /* character printable? */ + line[(wordlen * 2) + 53 + i] = + (v >= ' ' && v <= '~') ? v : '.'; + } + + hex_print(line); + } +} diff --git a/hf_protocol.h b/hf_protocol.h new file mode 100644 index 0000000..7a1a0c2 --- /dev/null +++ b/hf_protocol.h @@ -0,0 +1,407 @@ +// +// Copyright 2013, 2014 HashFast Technologies LLC +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. See COPYING for more details. +// +// Useful data structures and values for interfacing with HashFast products +// +// Version 1.1 +// + +#ifndef _HF_PROTOCOL_H_ +#define _HF_PROTOCOL_H_ + +#define HF_PROTOCOL_VERSION ((0<<8)|1) + +#define HF_PREAMBLE (uint8_t) 0xaa +#define HF_BROADCAST_ADDRESS (uint8_t) 0xff +#define HF_GWQ_ADDRESS (uint8_t) 254 + +// Serial protocol operation codes (Second header byte) +#define OP_NULL 0 +#define OP_ROOT 1 +#define OP_RESET 2 +#define OP_PLL_CONFIG 3 +#define OP_ADDRESS 4 +#define OP_READDRESS 5 +#define OP_HIGHEST 6 +#define OP_BAUD 7 +#define OP_UNROOT 8 + +#define OP_HASH 9 +#define OP_NONCE 10 +#define OP_ABORT 11 +#define OP_STATUS 12 +#define OP_GPIO 13 +#define OP_CONFIG 14 +#define OP_STATISTICS 15 +#define OP_GROUP 16 +#define OP_CLOCKGATE 17 + +// Conversions for the ADC readings from GN on-chip sensors +#define GN_CORE_VOLTAGE(a) ((float)(a)/256*1.2) +#define GN_DIE_TEMPERATURE(a) ((((float)(a)*240)/4096.0)-61.5) + +// What to use in an OP_CONFIG hdata field to set thermal overload point to a given temp in degrees C +#define GN_THERMAL_CUTOFF(temp) ((uint16_t)(((temp)+61.5)*4096/240)) + +// The sequence distance between a sent and received sequence number. +#define HF_SEQUENCE_DISTANCE(tx,rx) ((tx)>=(rx)?((tx)-(rx)):(info->num_sequence+(tx)-(rx))) + +// Values the protocol field in the above structure may take +#define PROTOCOL_USB_MAPPED_SERIAL 0 +#define PROTOCOL_GLOBAL_WORK_QUEUE 1 + +// Conversions for the board/module level sensors +#define M_VOLTAGE(a) ((float)(a)*19.0734e-6) +#define M_PHASE_CURRENT(a) ((float)(a)*0.794728597e-3) + +// Values info->device_type can take +#define HFD_G1 1 // HashFast G-1 GN ASIC +#define HFD_VC709 128 +#define HFD_ExpressAGX 129 + +// USB interface specific operation codes +#define OP_USB_INIT 128 // Initialize USB interface details +#define OP_GET_TRACE 129 // Send back the trace buffer if present +#define OP_LOOPBACK_USB 130 +#define OP_LOOPBACK_UART 131 +#define OP_DFU 132 // Jump into the boot loader +#define OP_USB_SHUTDOWN 133 // Initialize USB interface details +#define OP_DIE_STATUS 134 // Die status. There are 4 die per ASIC +#define OP_GWQ_STATUS 135 // Global Work Queue protocol status +#define OP_WORK_RESTART 136 // Stratum work restart regime +#define OP_USB_STATS1 137 // Statistics class 1 +#define OP_USB_GWQSTATS 138 // GWQ protocol statistics +#define OP_USB_NOTICE 139 // Asynchronous notification event +#define OP_PING 140 // Echo +#define OP_CORE_MAP 141 // Return core map +#define OP_VERSION 142 // Version information +#define OP_FAN 143 // Set Fan Speed +#define OP_NAME 144 // System name write/read +#define OP_USB_DEBUG 255 + +// HashFast vendor and product ID's +#define HF_USB_VENDOR_ID 0x297c +#define HF_USB_PRODUCT_ID_G1 0x0001 + +// If this bit is set, search forward for other nonce(s) +#define HF_NTIME_MASK 0xfff // Mask for for ntime +#define HF_NONCE_SEARCH 0x1000 // Search bit in candidate_nonce -> ntime + +// +// Fault codes that can be returned in struct hf_usb_init_base.operation_status +// +#define E_RESET_TIMEOUT 1 +#define E_ADDRESS_TIMEOUT 2 +#define E_CLOCKGATE_TIMEOUT 3 +#define E_CONFIG_TIMEOUT 4 +#define E_EXCESS_CORE_FAILURES 5 +#define E_TOTAL_CORE_FAILURES 6 +#define E_TOO_MANY_GROUPS 7 +#define E_NO_SLAVES 8 +#define E_SLAVE_COMM 9 +#define E_MAIN_POWER_BAD 10 +#define E_SECONDARY_POWER_BAD 11 +#define E_BOARD_1 12 +#define E_BOARD_2 13 +#define E_BOARD_3 14 +#define E_BOARD_4 15 +#define E_BOARD_5 16 +#define E_CORE_POWER_FAULT 17 +#define E_BAUD_TIMEOUT 18 +#define E_ADDRESS_FAILURE 19 +#define E_IR_PROG_FAILURE 20 +#define E_MIXED_MISMATCH 21 +#define E_MIXED_TIMEOUT 22 + +#define U32SIZE(x) (sizeof(x)/sizeof(uint32_t)) + +// Baud rate vs. code for gpi[7:5] coming out of reset +#define BAUD_RATE_PWRUP_0 115200 +#define BAUD_RATE_PWRUP_1 9600 +#define BAUD_RATE_PWRUP_2 38400 +#define BAUD_RATE_PWRUP_3 57600 +#define BAUD_RATE_PWRUP_4 230400 +#define BAUD_RATE_PWRUP_5 576000 +#define BAUD_RATE_PWRUP_6 921600 +#define BAUD_RATE_PWRUP_7 1152000 + +// OP_WORK_RESTART hash clock change methods. +// +// May be issued *infrequently* by the host to adjust hash clock rate for thermal control +// The "hdata" field, if non zero, contains adjustment instructions. Bits 15:12 of "hdata" +// contain the adjustment type according to the following code, and bits 11:0 contain the +// associated value. Examples: +// hdata = (1<<12)|550 = Set hash clock rate to 550 Mhz +// hdata = (4<<12)|1 = Increase hash clock rate by 1% +// hdata = (6<<12) = Go back to whatever the "original" OP_USB_INIT settings were +// +// Finally, if 4 bytes of "data" follows the OP_WORK_RESTART header, then that data is taken +// as a little endian bitmap, bit set = enable clock change to that die, bit clear = don't +// change clock on that die, i.e. considered as a uint32_t, then 0x1 = die 0, 0x2 = die 1 etc. + +#define WR_NO_CHANGE 0 +#define WR_CLOCK_VALUE 1 +#define WR_MHZ_INCREASE 2 +#define WR_MHZ_DECREASE 3 +#define WR_PERCENT_INCREASE 4 +#define WR_PERCENT_DECREASE 5 +#define WR_REVERT 6 + +#define WR_COMMAND_SHIFT 12 + +// Structure definitions, LE platforms + +#if __BYTE_ORDER == __BIG_ENDIAN && !defined(WIN32) +#include "hf_protocol_be.h" +#else +// Generic header +struct hf_header { + uint8_t preamble; // Always 0xaa + uint8_t operation_code; + uint8_t chip_address; + uint8_t core_address; + uint16_t hdata; // Header specific data + uint8_t data_length; // .. of data frame to follow, in 4 byte blocks, 0=no data + uint8_t crc8; // Computed across bytes 1-6 inclusive +} __attribute__((packed,aligned(4))); // 8 bytes total + +// Header specific to OP_PLL_CONFIG +struct hf_pll_config { + uint8_t preamble; + uint8_t operation_code; + uint8_t chip_address; + + uint8_t pll_divr:6; + uint8_t pll_bypass:1; + uint8_t pll_reset:1; + + uint8_t pll_divf; + + uint8_t spare1:1; // Must always be 0 + uint8_t pll_divq:3; + uint8_t pll_range:3; + uint8_t pll_fse:1; // Must always be 1 + + uint8_t data_length; // Always 0 + uint8_t crc8; // Computed across bytes 1-6 inclusive +} __attribute__((packed,aligned(4))); // 8 bytes total + +// OP_HASH serial data +struct hf_hash_serial { + uint8_t midstate[32]; // Computed from first half of block header + uint8_t merkle_residual[4]; // From block header + uint32_t timestamp; // From block header + uint32_t bits; // Actual difficulty target for block header + uint32_t starting_nonce; // Usually set to 0 + uint32_t nonce_loops; // How many nonces to search, or 0 for 2^32 + uint16_t ntime_loops; // How many times to roll timestamp, or 0 + uint8_t search_difficulty; // Search difficulty to use, # of '0' digits required + uint8_t option; + uint8_t group; + uint8_t spare3[3]; +} __attribute__((packed,aligned(4))); + +// OP_HASH usb data - header+data = 64 bytes +struct hf_hash_usb { + uint8_t midstate[32]; // Computed from first half of block header + uint8_t merkle_residual[4]; // From block header + uint32_t timestamp; // From block header + uint32_t bits; // Actual difficulty target for block header + uint32_t starting_nonce; // Usually set to 0 + uint32_t nonce_loops; // How many nonces to search, or 0 for 2^32 + uint16_t ntime_loops; // How many times to roll timestamp, or 0 + uint8_t search_difficulty; // Search difficulty to use, # of '0' digits required + uint8_t group; // Non-zero for valid group +} __attribute__((packed,aligned(4))); + +// OP_NONCE data +struct hf_candidate_nonce { + uint32_t nonce; // Candidate nonce + uint16_t sequence; // Sequence number from corresponding OP_HASH + uint16_t ntime; // ntime offset, if ntime roll occurred, in LS 12 bits + // If b12 set, search forward next 128 nonces to find solution(s) +} __attribute__((packed,aligned(4))); + +// OP_CONFIG data +struct hf_config_data { + uint16_t status_period:11; // Periodic status time, msec + uint16_t enable_periodic_status:1; // Send periodic status + uint16_t send_status_on_core_idle:1; // Schedule status whenever core goes idle + uint16_t send_status_on_pending_empty:1; // Schedule status whenever core pending goes idle + uint16_t pwm_active_level:1; // Active level of PWM outputs, if used + uint16_t forward_all_privileged_packets:1; // Forward priv pkts -- diagnostic + uint8_t status_batch_delay; // Batching delay, time to wait before sending status + uint8_t watchdog:7; // Watchdog timeout, seconds + uint8_t disable_sensors:1; // Diagnostic + + uint8_t rx_header_timeout:7; // Header timeout in char times + uint8_t rx_ignore_header_crc:1; // Ignore rx header crc's (diagnostic) + uint8_t rx_data_timeout:7; // Data timeout in char times / 16 + uint8_t rx_ignore_data_crc:1; // Ignore rx data crc's (diagnostic) + uint8_t stats_interval:7; // Minimum interval to report statistics (seconds) + uint8_t stat_diagnostic:1; // Never set this + uint8_t measure_interval; // Die temperature measurement interval (msec) + + uint32_t one_usec:12; // How many LF clocks per usec. + uint32_t max_nonces_per_frame:4; // Maximum # of nonces to combine in a single frame + uint32_t voltage_sample_points:8; // Bit mask for sample points (up to 5 bits set) + uint32_t pwm_phases:2; // phases - 1 + uint32_t trim:4; // Trim value for temperature measurements + uint32_t clock_diagnostic:1; // Never set this + uint32_t forward_all_packets:1; // Forward everything - diagnostic. + + uint16_t pwm_period; // Period of PWM outputs, in reference clock cycles + uint16_t pwm_pulse_period; // Initial count, phase 0 +} __attribute__((packed,aligned(4))); + +// OP_GROUP data +struct hf_group_data { + uint16_t nonce_msoffset; // This value << 16 added to starting nonce + uint16_t ntime_offset; // This value added to timestamp +} __attribute__((packed,aligned(4))); + +// Structure of the monitor fields for G-1, returned in OP_STATUS, core bitmap follows this +struct hf_g1_monitor { + uint16_t die_temperature; // Die temperature ADC count + uint8_t core_voltage[6]; // Core voltage + // [0] = main sensor + // [1]-[5] = other positions +} __attribute__((packed,aligned(4))); + +// What comes back in the body of an OP_STATISTICS frame (On die statistics) +struct hf_statistics { + uint8_t rx_header_crc; // Header CRC error's + uint8_t rx_body_crc; // Data CRC error's + uint8_t rx_header_timeouts; // Header timeouts + uint8_t rx_body_timeouts; // Data timeouts + uint8_t core_nonce_fifo_full; // Core nonce Q overrun events + uint8_t array_nonce_fifo_full; // System nonce Q overrun events + uint8_t stats_overrun; // Overrun in statistics reporting + uint8_t spare; +} __attribute__((packed,aligned(4))); + + +//////////////////////////////////////////////////////////////////////////////// +// USB protocol data structures +//////////////////////////////////////////////////////////////////////////////// + +// Convenience header specific to OP_USB_INIT +struct hf_usb_init_header { + uint8_t preamble; // Always 0xaa + uint8_t operation_code; + uint8_t spare1; + + uint8_t protocol:3; // Which protocol to use + uint8_t user_configuration:1; // Use the following configuration data + uint8_t pll_bypass:1; // Force PLL bypass, hash clock = ref clock + uint8_t no_asic_initialization:1; // Do not perform automatic ASIC initialization + uint8_t do_atspeed_core_tests:1; // Do core tests at speed, return second bitmap + uint8_t shed_supported:1; // Host supports gwq status shed_count + + uint16_t hash_clock; // Requested hash clock frequency + + uint8_t data_length; // .. of data frame to follow, in 4 byte blocks + uint8_t crc8; // Computed across bytes 1-6 inclusive +} __attribute__((packed,aligned(4))); // 8 bytes total + +// Options (only if present) that may be appended to the above header +// Each option involving a numerical value will only be in effect if the value is non-zero +// This allows the user to select only those options desired for modification. Do not +// use this facility unless you are an expert - loading inconsistent settings will not work. +struct hf_usb_init_options { + uint16_t group_ntime_roll; // Total ntime roll amount per group + uint16_t core_ntime_roll; // Total core ntime roll amount + uint8_t low_operating_temp_limit; // Lowest normal operating limit + uint8_t high_operating_temp_limit; // Highest normal operating limit + uint16_t spare; +} __attribute__((packed,aligned(4))); + +// Base item returned from device for OP_USB_INIT +struct hf_usb_init_base { + uint16_t firmware_rev; // Firmware revision # + uint16_t hardware_rev; // Hardware revision # + uint32_t serial_number; // Board serial number + uint8_t operation_status; // Reply status for OP_USB_INIT (0 = success) + uint8_t extra_status_1; // Extra reply status information, code specific + uint16_t sequence_modulus; // Sequence numbers are to be modulo this + uint16_t hash_clockrate; // Actual hash clock rate used (nearest Mhz) + uint16_t inflight_target; // Target inflight amount for GWQ protocol +} __attribute__((packed,aligned(4))); + +// The above base item (16 bytes) is followed by the struct hf_config_data (16 bytes) actually +// used internally (so users may modify non-critical fields by doing subsequent +// OP_CONFIG operations). This is followed by a device specific "core good" bitmap (unless the +// user disabled initialization), and optionally by an at-speed "core good" bitmap. + + +// Information in an OP_DIE_STATUS frame. This is for one die - there are four per ASIC. +// Board level phase current and voltage sensors are likely to disappear in later production models. +struct hf_g1_die_data { + struct hf_g1_monitor die; // Die sensors - 8 bytes + uint16_t phase_currents[4]; // Phase currents (0 if unavailable) + uint16_t voltage; // Voltage at device boundary (0 if unavailable) + uint16_t temperature; // Regulator temp sensor + uint16_t tacho; // See documentation + uint16_t spare; +} __attribute__((packed,aligned(4))); // 24 bytes total + + +// Information for an OP_GWQ_STATUS frame +// If sequence_head == sequence_tail, then there is no active work and sequence_head is invalid +struct hf_gwq_data { + uint64_t hash_count; // Add this to host's cumulative hash count + uint16_t sequence_head; // The latest, internal, active sequence # + uint16_t sequence_tail; // The latest, internal, inactive sequence # + uint16_t shed_count; // # of cores have been shedded for thermal control + uint16_t spare; +} __attribute__((packed,aligned(4))); + + +// Information for an OP_USB_STATS1 frame - Communication statistics +struct hf_usb_stats1 { + // USB incoming + uint16_t usb_rx_preambles; + uint16_t usb_rx_receive_byte_errors; + uint16_t usb_rx_bad_hcrc; + + // USB outgoing + uint16_t usb_tx_attempts; + uint16_t usb_tx_packets; + uint16_t usb_tx_timeouts; + uint16_t usb_tx_incompletes; + uint16_t usb_tx_endpointstalled; + uint16_t usb_tx_disconnected; + uint16_t usb_tx_suspended; + + // Internal UART transmit + uint16_t uart_tx_queue_dma; + uint16_t uart_tx_interrupts; + + // Internal UART receive + uint16_t uart_rx_preamble_ints; + uint16_t uart_rx_missed_preamble_ints; + uint16_t uart_rx_header_done; + uint16_t uart_rx_data_done; + uint16_t uart_rx_bad_hcrc; + //uint16_t uart_rx_bad_crc32; + uint16_t uart_rx_bad_dma; + uint16_t uart_rx_short_dma; + uint16_t uart_rx_buffers_full; + + uint8_t max_tx_buffers; // Maximum # of send buffers ever used + uint8_t max_rx_buffers; // Maximum # of receive buffers ever used +} __attribute__((packed,aligned(4))); + +// Information for an OP_USB_NOTICE frame +struct hf_usb_notice_data { + uint32_t extra_data; // Depends on notification code + char message[]; // NULL terminated, little endian byte order +}; +#endif + +#endif diff --git a/hf_protocol_be.h b/hf_protocol_be.h new file mode 100644 index 0000000..5920d50 --- /dev/null +++ b/hf_protocol_be.h @@ -0,0 +1,267 @@ +// +// Copyright 2013, 2014 HashFast Technologies LLC +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. See COPYING for more details. +// +// Big endian versions of packed structures +// +// Version 1.0 +// + +#ifndef _HF_PROTOCOL_BE_H_ +#define _HF_PROTOCOL_BE_H_ + +// Generic header +struct hf_header { + uint8_t preamble; // Always 0xaa + uint8_t operation_code; + uint8_t chip_address; + uint8_t core_address; + uint16_t hdata; // Header specific data + uint8_t data_length; // .. of data frame to follow, in 4 byte blocks, 0=no data + uint8_t crc8; // Computed across bytes 1-6 inclusive +} __attribute__((packed,aligned(4))); // 8 bytes total + +// Header specific to OP_PLL_CONFIG +struct hf_pll_config { + uint8_t preamble; + uint8_t operation_code; + uint8_t chip_address; + + uint8_t pll_reset:1; + uint8_t pll_bypass:1; + uint8_t pll_divr:6; + + uint8_t pll_divf; + + uint8_t pll_fse:1; // Must always be 1 + uint8_t pll_range:3; + uint8_t pll_divq:3; + uint8_t spare1:1; // Must always be 0 + + uint8_t data_length; // Always 0 + uint8_t crc8; // Computed across bytes 1-6 inclusive +} __attribute__((packed,aligned(4))); // 8 bytes total + +// OP_HASH serial data +struct hf_hash_serial { + uint8_t midstate[32]; // Computed from first half of block header + uint8_t merkle_residual[4]; // From block header + uint32_t timestamp; // From block header + uint32_t bits; // Actual difficulty target for block header + uint32_t starting_nonce; // Usually set to 0 + uint32_t nonce_loops; // How many nonces to search, or 0 for 2^32 + uint16_t ntime_loops; // How many times to roll timestamp, or 0 + uint8_t search_difficulty; // Search difficulty to use, # of '0' digits required + uint8_t option; + uint8_t group; + uint8_t spare3[3]; +} __attribute__((packed,aligned(4))); + +// OP_HASH usb data - header+data = 64 bytes +struct hf_hash_usb { + uint8_t midstate[32]; // Computed from first half of block header + uint8_t merkle_residual[4]; // From block header + uint32_t timestamp; // From block header + uint32_t bits; // Actual difficulty target for block header + uint32_t starting_nonce; // Usually set to 0 + uint32_t nonce_loops; // How many nonces to search, or 0 for 2^32 + uint16_t ntime_loops; // How many times to roll timestamp, or 0 + uint8_t search_difficulty; // Search difficulty to use, # of '0' digits required + uint8_t group; // Non-zero for valid group +} __attribute__((packed,aligned(4))); + +// OP_NONCE data +struct hf_candidate_nonce { + uint32_t nonce; // Candidate nonce + uint16_t sequence; // Sequence number from corresponding OP_HASH + uint16_t ntime; // ntime offset, if ntime roll occurred, in LS 12 bits + // If b12 set, search forward next 128 nonces to find solution(s) +} __attribute__((packed,aligned(4))); + +// OP_CONFIG data +// This is usually internal data only, for serial drivers only +// Users shouldn't normally need to interpret this, but in the event a Big Endian +// user requires access to this data, the following structure will get all +// the fields in the right place, but byte swaps will be required for the +// uint16_t's and the uint32_t. +struct hf_config_data { + uint16_t forward_all_privileged_packets:1; // Forward priv pkts -- diagnostic + uint16_t pwm_active_level:1; // Active level of PWM outputs, if used + uint16_t send_status_on_pending_empty:1; // Schedule status whenever core pending goes idle + uint16_t send_status_on_core_idle:1; // Schedule status whenever core goes idle + uint16_t enable_periodic_status:1; // Send periodic status + uint16_t status_period:11; // Periodic status time, msec + + uint8_t status_batch_delay; // Batching delay, time to wait before sending status + uint8_t disable_sensors:1; // Diagnostic + uint8_t watchdog:7; // Watchdog timeout, seconds + + uint8_t rx_ignore_header_crc:1; // Ignore rx header crc's (diagnostic) + uint8_t rx_header_timeout:7; // Header timeout in char times + uint8_t rx_ignore_data_crc:1; // Ignore rx data crc's (diagnostic) + uint8_t rx_data_timeout:7; // Data timeout in char times / 16 + uint8_t stat_diagnostic:1; // Never set this + uint8_t stats_interval:7; // Minimum interval to report statistics (seconds) + uint8_t measure_interval; // Die temperature measurement interval (msec) + + uint32_t forward_all_packets:1; // Forward everything - diagnostic. + uint32_t clock_diagnostic:1; // Never set this + uint32_t trim:4; // Trim value for temperature measurements + uint32_t pwm_phases:2; // phases - 1 + uint32_t voltage_sample_points:8; // Bit mask for sample points (up to 5 bits set) + uint32_t max_nonces_per_frame:4; // Maximum # of nonces to combine in a single frame + uint32_t one_usec:12; // How many LF clocks per usec. + + uint16_t pwm_period; // Period of PWM outputs, in reference clock cycles + uint16_t pwm_pulse_period; // Initial count, phase 0 +} __attribute__((packed,aligned(4))); + +// OP_GROUP data +struct hf_group_data { + uint16_t nonce_msoffset; // This value << 16 added to starting nonce + uint16_t ntime_offset; // This value added to timestamp +} __attribute__((packed,aligned(4))); + +// Structure of the monitor fields for G-1, returned in OP_STATUS, core bitmap follows this +struct hf_g1_monitor { + uint16_t die_temperature; // Die temperature ADC count + uint8_t core_voltage[6]; // Core voltage + // [0] = main sensor + // [1]-[5] = other positions +} __attribute__((packed,aligned(4))); + +// What comes back in the body of an OP_STATISTICS frame (On die statistics) +struct hf_statistics { + uint8_t rx_header_crc; // Header CRC error's + uint8_t rx_body_crc; // Data CRC error's + uint8_t rx_header_timeouts; // Header timeouts + uint8_t rx_body_timeouts; // Data timeouts + uint8_t core_nonce_fifo_full; // Core nonce Q overrun events + uint8_t array_nonce_fifo_full; // System nonce Q overrun events + uint8_t stats_overrun; // Overrun in statistics reporting + uint8_t spare; +} __attribute__((packed,aligned(4))); + + +//////////////////////////////////////////////////////////////////////////////// +// USB protocol data structures +//////////////////////////////////////////////////////////////////////////////// + +// Convenience header specific to OP_USB_INIT +struct hf_usb_init_header { + uint8_t preamble; // Always 0xaa + uint8_t operation_code; + uint8_t spare1; + + uint8_t shed_supported:1; // Host supports gwq status shed_count + uint8_t do_atspeed_core_tests:1; // Do core tests at speed, return second bitmap + uint8_t no_asic_initialization:1; // Do not perform automatic ASIC initialization + uint8_t pll_bypass:1; // Force PLL bypass, hash clock = ref clock + uint8_t user_configuration:1; // Use the following configuration data + uint8_t protocol:3; // Which protocol to use + + uint16_t hash_clock; // Requested hash clock frequency + + uint8_t data_length; // .. of data frame to follow, in 4 byte blocks + uint8_t crc8; // Computed across bytes 1-6 inclusive +} __attribute__((packed,aligned(4))); // 8 bytes total + +// Options (only if present) that may be appended to the above header +// Each option involving a numerical value will only be in effect if the value is non-zero +// This allows the user to select only those options desired for modification. Do not +// use this facility unless you are an expert - loading inconsistent settings will not work. +struct hf_usb_init_options { + uint16_t group_ntime_roll; // Total ntime roll amount per group + uint16_t core_ntime_roll; // Total core ntime roll amount + uint8_t low_operating_temp_limit; // Lowest normal operating limit + uint8_t high_operating_temp_limit; // Highest normal operating limit + uint16_t spare; +} __attribute__((packed,aligned(4))); + +// Base item returned from device for OP_USB_INIT +struct hf_usb_init_base { + uint16_t firmware_rev; // Firmware revision # + uint16_t hardware_rev; // Hardware revision # + uint32_t serial_number; // Board serial number + uint8_t operation_status; // Reply status for OP_USB_INIT (0 = success) + uint8_t extra_status_1; // Extra reply status information, code specific + uint16_t sequence_modulus; // Sequence numbers are to be modulo this + uint16_t hash_clockrate; // Actual hash clock rate used (nearest Mhz) + uint16_t inflight_target; // Target inflight amount for GWQ protocol +} __attribute__((packed,aligned(4))); + +// The above base item (16 bytes) is followed by the struct hf_config_data (16 bytes) actually +// used internally (so users may modify non-critical fields by doing subsequent +// OP_CONFIG operations). This is followed by a device specific "core good" bitmap (unless the +// user disabled initialization), and optionally by an at-speed "core good" bitmap. + + +// Information in an OP_DIE_STATUS frame. This is for one die - there are four per ASIC. +// Board level phase current and voltage sensors are likely to disappear in later production models. +struct hf_g1_die_data { + struct hf_g1_monitor die; // Die sensors - 8 bytes + uint16_t phase_currents[4]; // Phase currents (0 if unavailable) + uint16_t voltage; // Voltage at device boundary (0 if unavailable) + uint16_t temperature; // Regulator temp sensor + uint16_t tacho; // See documentation + uint16_t spare; +} __attribute__((packed,aligned(4))); // 24 bytes total + +// Information for an OP_GWQ_STATUS frame +// If sequence_head == sequence_tail, then there is no active work and sequence_head is invalid +struct hf_gwq_data { + uint64_t hash_count; // Add this to host's cumulative hash count + uint16_t sequence_head; // The latest, internal, active sequence # + uint16_t sequence_tail; // The latest, internal, inactive sequence # + uint16_t shed_count; // # of cores have been shedded for thermal control + uint16_t spare; +} __attribute__((packed,aligned(4))); + + +// Information for an OP_USB_STATS1 frame - Communication statistics +struct hf_usb_stats1 { + // USB incoming + uint16_t usb_rx_preambles; + uint16_t usb_rx_receive_byte_errors; + uint16_t usb_rx_bad_hcrc; + + // USB outgoing + uint16_t usb_tx_attempts; + uint16_t usb_tx_packets; + uint16_t usb_tx_timeouts; + uint16_t usb_tx_incompletes; + uint16_t usb_tx_endpointstalled; + uint16_t usb_tx_disconnected; + uint16_t usb_tx_suspended; + + // Internal UART transmit + uint16_t uart_tx_queue_dma; + uint16_t uart_tx_interrupts; + + // Internal UART receive + uint16_t uart_rx_preamble_ints; + uint16_t uart_rx_missed_preamble_ints; + uint16_t uart_rx_header_done; + uint16_t uart_rx_data_done; + uint16_t uart_rx_bad_hcrc; + //uint16_t uart_rx_bad_crc32; + uint16_t uart_rx_bad_dma; + uint16_t uart_rx_short_dma; + uint16_t uart_rx_buffers_full; + + uint8_t max_tx_buffers; // Maximum # of send buffers ever used + uint8_t max_rx_buffers; // Maximum # of receive buffers ever used +} __attribute__((packed,aligned(4))); + +// Information for an OP_USB_NOTICE frame +struct hf_usb_notice_data { + uint32_t extra_data; // Depends on notification code + char message[]; // NULL terminated, little endian byte order +}; + + +#endif diff --git a/i2c-context.c b/i2c-context.c new file mode 100644 index 0000000..b3fbe18 --- /dev/null +++ b/i2c-context.c @@ -0,0 +1,102 @@ +/* + * generic I2C slave access interface + * + * Copyright 2014 Zefir Kurtisi + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "miner.h" +#include "i2c-context.h" + + +static bool i2c_slave_write(struct i2c_ctx *ctx, uint8_t reg, uint8_t val) +{ + union i2c_smbus_data data; + data.byte = val; + + struct i2c_smbus_ioctl_data args; + + args.read_write = I2C_SMBUS_WRITE; + args.command = reg; + args.size = I2C_SMBUS_BYTE_DATA; + args.data = &data; + + if (ioctl(ctx->file, I2C_SMBUS, &args) == -1) { + applog(LOG_INFO, "i2c 0x%02x: failed to write to fdesc %d: %s", + ctx->addr, ctx->file, strerror(errno)); + return false; + } + applog(LOG_DEBUG, "I2C-W(0x%02x/0x%02x)=0x%02x", ctx->addr, reg, val); + return true; +} + +static bool i2c_slave_read(struct i2c_ctx *ctx, uint8_t reg, uint8_t *val) +{ + union i2c_smbus_data data; + struct i2c_smbus_ioctl_data args; + + args.read_write = I2C_SMBUS_READ; + args.command = reg; + args.size = I2C_SMBUS_BYTE_DATA; + args.data = &data; + + if (ioctl(ctx->file, I2C_SMBUS, &args) == -1) { + applog(LOG_INFO, "i2c 0x%02x: failed to read from fdesc %d: %s", + ctx->addr, ctx->file, strerror(errno)); + return false; + } + *val = data.byte; + applog(LOG_DEBUG, "I2C-R(0x%02x/0x%02x)=0x%02x", ctx->addr, reg, *val); + return true; +} + +static void i2c_slave_exit(struct i2c_ctx *ctx) +{ + if (ctx->file == -1) + return; + close(ctx->file); + free(ctx); +} + +extern struct i2c_ctx *i2c_slave_open(char *i2c_bus, uint8_t slave_addr) +{ + int file = open(i2c_bus, O_RDWR); + if (file < 0) { + applog(LOG_INFO, "Failed to open i2c-1: %s", strerror(errno)); + return NULL; + } + + if (ioctl(file, I2C_SLAVE, slave_addr) < 0) { + close(file); + return NULL; + } + struct i2c_ctx *ctx = malloc(sizeof(*ctx)); + assert(ctx != NULL); + + ctx->addr = slave_addr; + ctx->file = file; + ctx->exit = i2c_slave_exit; + ctx->read = i2c_slave_read; + ctx->write = i2c_slave_write; + return ctx; +} + diff --git a/i2c-context.h b/i2c-context.h new file mode 100644 index 0000000..e39d5da --- /dev/null +++ b/i2c-context.h @@ -0,0 +1,26 @@ +#ifndef I2C_CONTEXT_H +#define I2C_CONTEXT_H + +#include +#include + +/* common i2c context */ +struct i2c_ctx { + /* destructor */ + void (*exit)(struct i2c_ctx *ctx); + /* write one byte to given register */ + bool (*write)(struct i2c_ctx *ctx, uint8_t reg, uint8_t val); + /* read one byte from given register */ + bool (*read)(struct i2c_ctx *ctx, uint8_t reg, uint8_t *val); + + /* common data */ + uint8_t addr; + int file; +}; + +/* the default I2C bus on RPi */ +#define I2C_BUS "/dev/i2c-1" + +extern struct i2c_ctx *i2c_slave_open(char *i2c_bus, uint8_t slave_addr); + +#endif /* I2C_CONTEXT_H */ diff --git a/klist.c b/klist.c new file mode 100644 index 0000000..a552017 --- /dev/null +++ b/klist.c @@ -0,0 +1,430 @@ +/* + * Copyright 2013-2014 Andrew Smith - BlackArrow Ltd + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#include + +static void k_alloc_items(K_LIST *list, KLIST_FFL_ARGS) +{ + K_ITEM *item; + int allocate, i; + + if (list->is_store) { + quithere(1, "List %s store can't %s()" KLIST_FFL, + list->name, __func__, KLIST_FFL_PASS); + } + + if (list->limit > 0 && list->total >= list->limit) + return; + + allocate = list->allocate; + if (list->limit > 0 && (list->total + allocate) > list->limit) + allocate = list->limit - list->total; + + list->item_mem_count++; + if (!(list->item_memory = realloc(list->item_memory, + list->item_mem_count * sizeof(*(list->item_memory))))) { + quithere(1, "List %s item_memory failed to realloc count=%d", + list->name, list->item_mem_count); + } + item = calloc(allocate, sizeof(*item)); + if (!item) { + quithere(1, "List %s failed to calloc %d new items - total was %d, limit was %d", + list->name, allocate, list->total, list->limit); + } + list->item_memory[list->item_mem_count - 1] = (void *)item; + + list->total += allocate; + list->count = allocate; + list->count_up = allocate; + + item[0].name = list->name; + item[0].prev = NULL; + item[0].next = &(item[1]); + for (i = 1; i < allocate-1; i++) { + item[i].name = list->name; + item[i].prev = &item[i-1]; + item[i].next = &item[i+1]; + } + item[allocate-1].name = list->name; + item[allocate-1].prev = &(item[allocate-2]); + item[allocate-1].next = NULL; + + list->head = item; + if (list->do_tail) + list->tail = &(item[allocate-1]); + + item = list->head; + while (item) { + list->data_mem_count++; + if (!(list->data_memory = realloc(list->data_memory, + list->data_mem_count * + sizeof(*(list->data_memory))))) { + quithere(1, "List %s data_memory failed to realloc count=%d", + list->name, list->data_mem_count); + } + item->data = calloc(1, list->siz); + if (!(item->data)) + quithere(1, "List %s failed to calloc item data", list->name); + list->data_memory[list->data_mem_count - 1] = (void *)(item->data); + item = item->next; + } +} + +K_STORE *k_new_store(K_LIST *list) +{ + K_STORE *store; + + store = calloc(1, sizeof(*store)); + if (!store) + quithere(1, "Failed to calloc store for %s", list->name); + + store->is_store = true; + store->lock = list->lock; + store->name = list->name; + store->do_tail = list->do_tail; + + return store; +} + +K_LIST *_k_new_list(const char *name, size_t siz, int allocate, int limit, bool do_tail, KLIST_FFL_ARGS) +{ + K_LIST *list; + + if (allocate < 1) + quithere(1, "Invalid new list %s with allocate %d must be > 0", name, allocate); + + if (limit < 0) + quithere(1, "Invalid new list %s with limit %d must be >= 0", name, limit); + + list = calloc(1, sizeof(*list)); + if (!list) + quithere(1, "Failed to calloc list %s", name); + + list->is_store = false; + + list->lock = calloc(1, sizeof(*(list->lock))); + if (!(list->lock)) + quithere(1, "Failed to calloc lock for list %s", name); + + cglock_init(list->lock); + + list->name = name; + list->siz = siz; + list->allocate = allocate; + list->limit = limit; + list->do_tail = do_tail; + + k_alloc_items(list, KLIST_FFL_PASS); + + return list; +} + +/* + * Unlink and return the head of the list + * If the list is empty: + * 1) If it's a store - return NULL + * 2) alloc a new list and return the head - + * which is NULL if the list limit has been reached + */ +K_ITEM *_k_unlink_head(K_LIST *list, KLIST_FFL_ARGS) +{ + K_ITEM *item; + + if (!(list->head) && !(list->is_store)) + k_alloc_items(list, KLIST_FFL_PASS); + + if (!(list->head)) + return NULL; + + item = list->head; + list->head = item->next; + if (list->head) + list->head->prev = NULL; + else { + if (list->do_tail) + list->tail = NULL; + } + + item->prev = item->next = NULL; + + list->count--; + + return item; +} + +// Zeros the head returned +K_ITEM *_k_unlink_head_zero(K_LIST *list, KLIST_FFL_ARGS) +{ + K_ITEM *item; + + item = _k_unlink_head(list, KLIST_FFL_PASS); + + if (item) + memset(item->data, 0, list->siz); + + return item; +} + +// Returns NULL if empty +K_ITEM *_k_unlink_tail(K_LIST *list, KLIST_FFL_ARGS) +{ + K_ITEM *item; + + if (!(list->do_tail)) { + quithere(1, "List %s can't %s() - do_tail is false" KLIST_FFL, + list->name, __func__, KLIST_FFL_PASS); + } + + if (!(list->tail)) + return NULL; + + item = list->tail; + list->tail = item->prev; + if (list->tail) + list->tail->next = NULL; + else + list->head = NULL; + + item->prev = item->next = NULL; + + list->count--; + + return item; +} + +void _k_add_head(K_LIST *list, K_ITEM *item, KLIST_FFL_ARGS) +{ + if (item->name != list->name) { + quithere(1, "List %s can't %s() a %s item" KLIST_FFL, + list->name, __func__, item->name, KLIST_FFL_PASS); + } + + item->prev = NULL; + item->next = list->head; + if (list->head) + list->head->prev = item; + + list->head = item; + + if (list->do_tail) { + if (!(list->tail)) + list->tail = item; + } + + list->count++; + list->count_up++; +} + +/* slows it down (of course) - only for debugging +void _k_free_head(K_LIST *list, K_ITEM *item, KLIST_FFL_ARGS) +{ + memset(item->data, 0xff, list->siz); + _k_add_head(list, item, KLIST_FFL_PASS); +} +*/ + +void _k_add_tail(K_LIST *list, K_ITEM *item, KLIST_FFL_ARGS) +{ + if (item->name != list->name) { + quithere(1, "List %s can't %s() a %s item" KLIST_FFL, + list->name, __func__, item->name, KLIST_FFL_PASS); + } + + if (!(list->do_tail)) { + quithere(1, "List %s can't %s() - do_tail is false" KLIST_FFL, + list->name, __func__, KLIST_FFL_PASS); + } + + item->prev = list->tail; + item->next = NULL; + if (list->tail) + list->tail->next = item; + + list->tail = item; + + if (!(list->head)) + list->head = item; + + list->count++; + list->count_up++; +} + +void _k_insert_before(K_LIST *list, K_ITEM *item, K_ITEM *before, KLIST_FFL_ARGS) +{ + if (item->name != list->name) { + quithere(1, "List %s can't %s() a %s item" KLIST_FFL, + list->name, __func__, item->name, KLIST_FFL_PASS); + } + + if (!before) { + quithere(1, "%s() (%s) can't before a null item" KLIST_FFL, + __func__, list->name, KLIST_FFL_PASS); + } + + item->next = before; + item->prev = before->prev; + if (before->prev) + before->prev->next = item; + else + list->head = item; + before->prev = item; + + list->count++; + list->count_up++; +} + +void _k_insert_after(K_LIST *list, K_ITEM *item, K_ITEM *after, KLIST_FFL_ARGS) +{ + if (item->name != list->name) { + quithere(1, "List %s can't %s() a %s item" KLIST_FFL, + list->name, __func__, item->name, KLIST_FFL_PASS); + } + + if (!after) { + quithere(1, "%s() (%s) can't after a null item" KLIST_FFL, + __func__, list->name, KLIST_FFL_PASS); + } + + item->prev = after; + item->next = after->next; + if (after->next) + after->next->prev = item; + else { + if (list->do_tail) + list->tail = item; + } + after->next = item; + + list->count++; + list->count_up++; +} + +void _k_unlink_item(K_LIST *list, K_ITEM *item, KLIST_FFL_ARGS) +{ + if (item->name != list->name) { + quithere(1, "List %s can't %s() a %s item" KLIST_FFL, + list->name, __func__, item->name, KLIST_FFL_PASS); + } + + if (item->prev) + item->prev->next = item->next; + + if (item->next) + item->next->prev = item->prev; + + if (list->head == item) + list->head = item->next; + + if (list->do_tail) { + if (list->tail == item) + list->tail = item->prev; + } + + item->prev = item->next = NULL; + + list->count--; +} + +void _k_list_transfer_to_head(K_LIST *from, K_LIST *to, KLIST_FFL_ARGS) +{ + if (from->name != to->name) { + quithere(1, "List %s can't %s() to a %s list" KLIST_FFL, + from->name, __func__, to->name, KLIST_FFL_PASS); + } + + if (!(from->do_tail)) { + quithere(1, "List %s can't %s() - do_tail is false" KLIST_FFL, + from->name, __func__, KLIST_FFL_PASS); + } + + if (!(from->head)) + return; + + if (to->head) + to->head->prev = from->tail; + else + to->tail = from->tail; + + from->tail->next = to->head; + to->head = from->head; + + from->head = from->tail = NULL; + to->count += from->count; + from->count = 0; + to->count_up += from->count_up; + from->count_up = 0; +} + +void _k_list_transfer_to_tail(K_LIST *from, K_LIST *to, KLIST_FFL_ARGS) +{ + if (from->name != to->name) { + quithere(1, "List %s can't %s() to a %s list" KLIST_FFL, + from->name, __func__, to->name, KLIST_FFL_PASS); + } + + if (!(from->do_tail)) { + quithere(1, "List %s can't %s() - do_tail is false" KLIST_FFL, + from->name, __func__, KLIST_FFL_PASS); + } + + if (!(from->head)) + return; + + if (to->tail) + to->tail->next = from->head; + else + to->head = from->head; + + from->head->prev = to->tail; + to->tail = from->tail; + + from->head = from->tail = NULL; + to->count += from->count; + from->count = 0; + to->count_up += from->count_up; + from->count_up = 0; +} + +K_LIST *_k_free_list(K_LIST *list, KLIST_FFL_ARGS) +{ + int i; + + if (list->is_store) { + quithere(1, "List %s can't %s() a store" KLIST_FFL, + list->name, __func__, KLIST_FFL_PASS); + } + + for (i = 0; i < list->item_mem_count; i++) + free(list->item_memory[i]); + free(list->item_memory); + + for (i = 0; i < list->data_mem_count; i++) + free(list->data_memory[i]); + free(list->data_memory); + + cglock_destroy(list->lock); + + free(list->lock); + + free(list); + + return NULL; +} + +K_STORE *_k_free_store(K_STORE *store, KLIST_FFL_ARGS) +{ + if (!(store->is_store)) { + quithere(1, "Store %s can't %s() the list" KLIST_FFL, + store->name, __func__, KLIST_FFL_PASS); + } + + free(store); + + return NULL; +} diff --git a/klist.h b/klist.h new file mode 100644 index 0000000..a79a4ed --- /dev/null +++ b/klist.h @@ -0,0 +1,94 @@ +/* + * Copyright 2013-2014 Andrew Smith - BlackArrow Ltd + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#ifndef KLIST_H +#define KLIST_H + +#include + +#define KLIST_FFL " - from %s %s() line %d" +#define KLIST_FFL_HERE __FILE__, __func__, __LINE__ +#define KLIST_FFL_PASS file, func, line +#define KLIST_FFL_ARGS __maybe_unused const char *file, \ + __maybe_unused const char *func, \ + __maybe_unused const int line + +typedef struct k_item { + const char *name; + struct k_item *prev; + struct k_item *next; + void *data; +} K_ITEM; + +typedef struct k_list { + const char *name; + bool is_store; + cglock_t *lock; + struct k_item *head; + struct k_item *tail; + size_t siz; // item data size + int total; // total allocated + int count; // in this list + int count_up; // incremented every time one is added + int allocate; // number to intially allocate and each time we run out + int limit; // total limit - 0 means unlimited + bool do_tail; // track the tail? + int item_mem_count; // how many item memory buffers have been allocated + void **item_memory; // allocated item memory buffers + int data_mem_count; // how many item data memory buffers have been allocated + void **data_memory; // allocated item data memory buffers +} K_LIST; + +/* + * K_STORE is for a list of items taken from a K_LIST + * The restriction is, a K_STORE must not allocate new items, + * only the K_LIST should do that + * i.e. all K_STORE items came from a K_LIST + */ +#define K_STORE K_LIST + +/* + * N.B. all locking is done in the code using the K_*LOCK macros + */ +#define K_WLOCK(_list) cg_wlock(_list->lock) +#define K_WUNLOCK(_list) cg_wunlock(_list->lock) +#define K_RLOCK(_list) cg_rlock(_list->lock) +#define K_RUNLOCK(_list) cg_runlock(_list->lock) + +extern K_STORE *k_new_store(K_LIST *list); +extern K_LIST *_k_new_list(const char *name, size_t siz, int allocate, int limit, bool do_tail, KLIST_FFL_ARGS); +#define k_new_list(_name, _siz, _allocate, _limit, _do_tail) _k_new_list(_name, _siz, _allocate, _limit, _do_tail, KLIST_FFL_HERE) +extern K_ITEM *_k_unlink_head(K_LIST *list, KLIST_FFL_ARGS); +#define k_unlink_head(_list) _k_unlink_head(_list, KLIST_FFL_HERE) +extern K_ITEM *_k_unlink_head_zero(K_LIST *list, KLIST_FFL_ARGS); +#define k_unlink_head_zero(_list) _k_unlink_head_zero(_list, KLIST_FFL_HERE) +extern K_ITEM *_k_unlink_tail(K_LIST *list, KLIST_FFL_ARGS); +#define k_unlink_tail(_list) _k_unlink_tail(_list, KLIST_FFL_HERE) +extern void _k_add_head(K_LIST *list, K_ITEM *item, KLIST_FFL_ARGS); +#define k_add_head(_list, _item) _k_add_head(_list, _item, KLIST_FFL_HERE) +// extern void k_free_head(K_LIST *list, K_ITEM *item, KLIST_FFL_ARGS); +#define k_free_head(__list, __item) _k_add_head(__list, __item, KLIST_FFL_HERE) +extern void _k_add_tail(K_LIST *list, K_ITEM *item, KLIST_FFL_ARGS); +#define k_add_tail(_list, _item) _k_add_tail(_list, _item, KLIST_FFL_HERE) +extern void _k_insert_before(K_LIST *list, K_ITEM *item, K_ITEM *before, KLIST_FFL_ARGS); +#define k_insert_before(_list, _item, _before) _k_insert_before(_list, _item, _before, KLIST_FFL_HERE) +extern void _k_insert_after(K_LIST *list, K_ITEM *item, K_ITEM *after, KLIST_FFL_ARGS); +#define k_insert_after(_list, _item, _after) _k_insert_after(_list, _item, _after, KLIST_FFL_HERE) +extern void _k_unlink_item(K_LIST *list, K_ITEM *item, KLIST_FFL_ARGS); +#define k_unlink_item(_list, _item) _k_unlink_item(_list, _item, KLIST_FFL_HERE) +void _k_list_transfer_to_head(K_LIST *from, K_LIST *to, KLIST_FFL_ARGS); +#define k_list_transfer_to_head(_from, _to) _k_list_transfer_to_head(_from, _to, KLIST_FFL_HERE) +void _k_list_transfer_to_tail(K_LIST *from, K_LIST *to, KLIST_FFL_ARGS); +#define k_list_transfer_to_tail(_from, _to) _k_list_transfer_to_tail(_from, _to, KLIST_FFL_HERE) +extern K_LIST *_k_free_list(K_LIST *list, KLIST_FFL_ARGS); +#define k_free_list(_list) _k_free_list(_list, KLIST_FFL_HERE) +extern K_STORE *_k_free_store(K_STORE *store, KLIST_FFL_ARGS); +#define k_free_store(_store) _k_free_store(_store, KLIST_FFL_HERE) + +#endif diff --git a/knc-asic.c b/knc-asic.c new file mode 100644 index 0000000..7c164f9 --- /dev/null +++ b/knc-asic.c @@ -0,0 +1,551 @@ +/* + * library for KnCminer devices + * + * Copyright 2014 KnCminer + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "miner.h" +#include "logging.h" + +#include "knc-transport.h" + +#include "knc-asic.h" + +/* Control Commands + * + * SPI command on channel. 1- + * 1'b1 3'channel 12'msglen_in_bits SPI message data + * Sends the supplied message on selected SPI bus + * + * Communication test + * 16'h1 16'x + * Simple test of SPI communication + * + * LED control + * 4'h1 4'red 4'green 4'blue + * Sets led colour + * + * Clock frequency + * 4'h2 12'msglen_in_bits 4'channel 4'die 16'MHz 512'x + * Configures the hashing clock rate + */ + +/* ASIC Command structure + * command 8 bits + * chip 8 bits + * core 16 bits + * data [command dependent] + * CRC32 32 bits (Neptune) + * + * ASIC response starts immediately after core address bits. + * + * response data + * CRC32 32 bits (Neptune) + * STATUS 8 bits 1 0 ~CRC_OK 0 0 ACCEPTED_WORK 0 1 (Neptune) + * + * Requests + * + * SETWORK (Jupiter) + * midstate 256 bits + * data 96 bits + * + * SETWORK/SETWORK_CLEAN (Neptune) + * slot | 0xf0 8 bits + * precalc_midstate 192 bits + * precalc_data 96 bits + * midstate 256 bits + * + * Returns REPORT response on Neptune + * + * Responses + * + * GETINFO + * + * (core field unused) + * + * cores 16 bits + * version 16 bits + * reserved 60 bits (Neptune) + * die_status 4 bits (Neptune) + * 1' pll_locked + * 1' hash_reset_n 1 if cores have been reset since last report + * 1' pll_reset_n 1 if PLL have been reset since last report + * 1' pll_power_down + * core_status cores * 2 bits (Neptune) rounded up to bytes + * 1' want_work + * 1' has_report (unreliable) + * + * REPORT + * + * reserved 2 bits + * next_state 1 bit next work state loaded + * state 1 bit hashing (0 on Jupiter) + * next_slot 4 bit slot id of next work state (0 on Jupiter) + * progress 8 bits upper 8 bits of nonce counter + * active_slot 4 bits slot id of current work state + * nonce_slot 4 bits slot id of found nonce + * nonce 32 bits + * + * reserved 4 bits + * nonce_slot 4 bits + * nonce 32 bits + * + * repeat for 5 nonce entries in total on Neptune + * Jupiter only has first nonce entry + */ + +// Precalculate first 3 rounds of SHA256 - as much as possible +// Macro routines copied from sha2.c +static void knc_prepare_neptune_work(unsigned char *out, struct work *work) { + const uint8_t *midstate = work->midstate; + const uint8_t *data = work->data + 16*4; + +#ifndef GET_ULONG_BE +#define GET_ULONG_BE(b,i) \ + (( (uint32_t) (b)[(i) ] << 24 ) \ + | ( (uint32_t) (b)[(i) + 1] << 16 ) \ + | ( (uint32_t) (b)[(i) + 2] << 8 ) \ + | ( (uint32_t) (b)[(i) + 3] )) +#endif + +#ifndef GET_ULONG_LE +#define GET_ULONG_LE(b,i) \ + (( (uint32_t) (b)[(i) + 3] << 24 ) \ + | ( (uint32_t) (b)[(i) + 2] << 16 ) \ + | ( (uint32_t) (b)[(i) + 1] << 8 ) \ + | ( (uint32_t) (b)[(i) + 0] )) +#endif + +#ifndef PUT_ULONG_BE +#define PUT_ULONG_BE(n,b,i) \ + { \ + (b)[(i) ] = (unsigned char) ( (n) >> 24 ); \ + (b)[(i) + 1] = (unsigned char) ( (n) >> 16 ); \ + (b)[(i) + 2] = (unsigned char) ( (n) >> 8 ); \ + (b)[(i) + 3] = (unsigned char) ( (n) ); \ + } +#endif + +#ifndef PUT_ULONG_LE +#define PUT_ULONG_LE(n,b,i) \ + { \ + (b)[(i) + 3] = (unsigned char) ( (n) >> 24 ); \ + (b)[(i) + 2] = (unsigned char) ( (n) >> 16 ); \ + (b)[(i) + 1] = (unsigned char) ( (n) >> 8 ); \ + (b)[(i) + 0] = (unsigned char) ( (n) ); \ + } +#endif + +#define SHR(x,n) ((x & 0xFFFFFFFF) >> n) +#define ROTR(x,n) (SHR(x,n) | (x << (32 - n))) + +#define S0(x) (ROTR(x, 7) ^ ROTR(x,18) ^ SHR(x, 3)) +#define S1(x) (ROTR(x,17) ^ ROTR(x,19) ^ SHR(x,10)) + +#define S2(x) (ROTR(x, 2) ^ ROTR(x,13) ^ ROTR(x,22)) +#define S3(x) (ROTR(x, 6) ^ ROTR(x,11) ^ ROTR(x,25)) + +#define F0(x,y,z) ((x & y) | (z & (x | y))) +#define F1(x,y,z) (z ^ (x & (y ^ z))) + +#define R(t) \ +( \ + W[t] = S1(W[t - 2]) + W[t - 7] + \ + S0(W[t - 15]) + W[t - 16] \ +) + +#define P(a,b,c,d,e,f,g,h,x,K) \ + { \ + temp1 = h + S3(e) + F1(e,f,g) + K + x; \ + temp2 = S2(a) + F0(a,b,c); \ + d += temp1; h = temp1 + temp2; \ + } + + uint32_t temp1, temp2, W[16+3]; + uint32_t A, B, C, D, E, F, G, H; + + W[0] = GET_ULONG_LE(data, 0*4 ); + W[1] = GET_ULONG_LE(data, 1*4 ); + W[2] = GET_ULONG_LE(data, 2*4 ); + W[3] = 0; // since S0(0)==0, this must be 0. S0(nonce) is added in hardware. + W[4] = 0x80000000; + W[5] = 0; + W[6] = 0; + W[7] = 0; + W[8] = 0; + W[9] = 0; + W[10] = 0; + W[11] = 0; + W[12] = 0; + W[13] = 0; + W[14] = 0; + W[15] = 0x00000280; + R(16); // Expand W 14, 9, 1, 0 + R(17); // 15, 10, 2, 1 + R(18); // 16, 11, 3, 2 + + A = GET_ULONG_LE(midstate, 0*4 ); + B = GET_ULONG_LE(midstate, 1*4 ); + C = GET_ULONG_LE(midstate, 2*4 ); + D = GET_ULONG_LE(midstate, 3*4 ); + E = GET_ULONG_LE(midstate, 4*4 ); + F = GET_ULONG_LE(midstate, 5*4 ); + G = GET_ULONG_LE(midstate, 6*4 ); + H = GET_ULONG_LE(midstate, 7*4 ); + + uint32_t D_ = D, H_ = H; + P( A, B, C, D_, E, F, G, H_, W[ 0], 0x428A2F98 ); + uint32_t C_ = C, G_ = G; + P( H_, A, B, C_, D_, E, F, G_, W[ 1], 0x71374491 ); + uint32_t B_ = B, F_ = F; + P( G_, H_, A, B_, C_, D_, E, F_, W[ 2], 0xB5C0FBCF ); + + PUT_ULONG_BE( D_, out, 0*4 ); + PUT_ULONG_BE( C_, out, 1*4 ); + PUT_ULONG_BE( B_, out, 2*4 ); + PUT_ULONG_BE( H_, out, 3*4 ); + PUT_ULONG_BE( G_, out, 4*4 ); + PUT_ULONG_BE( F_, out, 5*4 ); + PUT_ULONG_BE( W[18], out, 6*4 ); // This is partial S0(nonce) added by hardware + PUT_ULONG_BE( W[17], out, 7*4 ); + PUT_ULONG_BE( W[16], out, 8*4 ); + PUT_ULONG_BE( H, out, 9*4 ); + PUT_ULONG_BE( G, out, 10*4 ); + PUT_ULONG_BE( F, out, 11*4 ); + PUT_ULONG_BE( E, out, 12*4 ); + PUT_ULONG_BE( D, out, 13*4 ); + PUT_ULONG_BE( C, out, 14*4 ); + PUT_ULONG_BE( B, out, 15*4 ); + PUT_ULONG_BE( A, out, 16*4 ); +} + +static void knc_prepare_jupiter_work(unsigned char *out, struct work *work) { + int i; + for (i = 0; i < 8 * 4; i++) + out[i] = work->midstate[8 * 4 - i - 1]; + for (i = 0; i < 3 * 4; i++) + out[8 * 4 + i] = work->data[16 * 4 + 3 * 4 - i - 1]; +} + +static void knc_prepare_core_command(uint8_t *request, int command, int die, int core) +{ + request[0] = command; + request[1] = die; + request[2] = core >> 8; + request[3] = core & 0xff; +} + +int knc_prepare_report(uint8_t *request, int die, int core) +{ + knc_prepare_core_command(request, KNC_ASIC_CMD_REPORT, die, core); + return 4; +} + +int knc_prepare_info(uint8_t *request, int die, struct knc_die_info *die_info, int *response_size) +{ + request[0] = KNC_ASIC_CMD_GETINFO; + request[1] = die; + request[2] = 0; + request[3] = 0; + switch (die_info->version) { + case KNC_VERSION_JUPITER: + *response_size = 4; + break; + default: + *response_size = 12 + (KNC_MAX_CORES_PER_DIE*2 + 7) / 8; + break; + case KNC_VERSION_NEPTUNE: + *response_size = 12 + (die_info->cores*2 + 7) / 8; + break; + } + return 4; +} + +int knc_prepare_neptune_setwork(uint8_t *request, int die, int core, int slot, struct work *work, int clean) +{ + if (!clean) + knc_prepare_core_command(request, KNC_ASIC_CMD_SETWORK, die, core); + else + knc_prepare_core_command(request, KNC_ASIC_CMD_SETWORK_CLEAN, die, core); + request[4] = slot | 0xf0; + if (work) + knc_prepare_neptune_work(request + 4 + 1, work); + else + memset(request + 4 + 1, 0, 6*4 + 3*4 + 8*4); + return 4 + 1 + 6*4 + 3*4 + 8*4; +} + +int knc_prepare_jupiter_setwork(uint8_t *request, int die, int core, int slot, struct work *work) +{ + knc_prepare_core_command(request, KNC_ASIC_CMD_SETWORK, die, core); + request[4] = slot | 0xf0; + if (work) + knc_prepare_jupiter_work(request + 4 + 1, work); + else + memset(request + 4 + 1, 0, 8*4 + 3*4); + return 4 + 1 + 8*4 + 3*4; +} + +int knc_prepare_jupiter_halt(uint8_t *request, int die, int core) +{ + knc_prepare_core_command(request, KNC_ASIC_CMD_HALT, die, core); + return 4; +} + +int knc_prepare_neptune_halt(uint8_t *request, int die, int core) +{ + knc_prepare_core_command(request, KNC_ASIC_CMD_HALT, die, core); + request[4] = 0 | 0xf0; + memset(request + 4 + 1, 0, 6*4 + 3*4 + 8*4); + return 4 + 1 + 6*4 + 3*4 + 8*4; +} + +void knc_prepare_neptune_message(int request_length, const uint8_t *request, uint8_t *buffer) +{ + uint32_t crc; + memcpy(buffer, request, request_length); + buffer += request_length; + crc = crc32(0, Z_NULL, 0); + crc = crc32(crc, request, request_length); + PUT_ULONG_BE(crc, buffer, 0); +} + +int knc_transfer_length(int request_length, int response_length) +{ + /* FPGA control, request header, request body/response, CRC(4), ACK(1), EXTRA(3) */ + return 2 + MAX(request_length, 4 + response_length ) + 4 + 1 + 3; +} + +int knc_prepare_transfer(uint8_t *txbuf, int offset, int size, int channel, int request_length, const uint8_t *request, int response_length) +{ + /* FPGA control, request header, request body/response, CRC(4), ACK(1), EXTRA(3) */ + int msglen = MAX(request_length, 4 + response_length ) + 4 + 1 + 3; + int len = 2 + msglen; + txbuf += offset; + + if (len + offset > size) { + applog(LOG_DEBUG, "KnC SPI buffer full"); + return -1; + } + txbuf[0] = 1 << 7 | (channel+1) << 4 | (msglen * 8) >> 8; + txbuf[1] = (msglen * 8); + knc_prepare_neptune_message(request_length, request, txbuf+2); + + return offset + len; +} + +/* red, green, blue valid range 0 - 15 */ +int knc_prepare_led(uint8_t *txbuf, int offset, int size, int red, int green, int blue) +{ + /* 4'h1 4'red 4'green 4'blue */ + int len = 2; + txbuf += offset; + + if (len + offset > size) { + applog(LOG_DEBUG, "KnC SPI buffer full"); + return -1; + } + txbuf[0] = 1 << 4 | red; + txbuf[1] = green << 4 | blue; + + return offset + len; + +} + +/* reset controller */ +int knc_prepare_reset(uint8_t *txbuf, int offset, int size) +{ + /* 16'h0002 16'unused */ + int len = 4; + txbuf += offset; + + if (len + offset > size) { + applog(LOG_DEBUG, "KnC SPI buffer full"); + return -1; + } + txbuf[0] = (0x0002) >> 8; + txbuf[1] = (0x0002) & 0xff; + txbuf[2] = 0; + txbuf[3] = 0; + + return offset + len; +} + +/* request_length = 0 disables communication checks, i.e. Jupiter protocol */ +int knc_decode_response(uint8_t *rxbuf, int request_length, uint8_t **response, int response_length) +{ + int ret = 0; + int len = knc_transfer_length(request_length, response_length); + if (request_length > 0 && response_length > 0) { + uint32_t crc, recv_crc; + crc = crc32(0, Z_NULL, 0); + crc = crc32(crc, rxbuf + 2 + 4, response_length); + recv_crc = GET_ULONG_BE(rxbuf + 2 + 4, response_length); + if (crc != recv_crc) + ret |= KNC_ERR_CRC; + } + + if (response) { + if (response_length > 0) { + *response = rxbuf + 2 + 4; + } else { + *response = NULL; + } + } + + if (response_length == 0) + return 0; + + uint8_t ack = rxbuf[len - 4]; + + if ((ack & KNC_ASIC_ACK_MASK) != KNC_ASIC_ACK_MATCH) + ret |= KNC_ERR_ACK; + if ((ack & KNC_ASIC_ACK_CRC)) + ret |= KNC_ERR_CRCACK; + if ((ack & KNC_ASIC_ACK_ACCEPT)) + ret |= KNC_ACCEPTED; + if (ret && memcmp(&rxbuf[len-4], "\377\377\377\377", 4) == 0) + ret = KNC_ERR_UNAVAIL; + return ret; +} + +int knc_syncronous_transfer(void *ctx, int channel, int request_length, const uint8_t *request, int response_length, uint8_t *response) +{ + int len = knc_transfer_length(request_length, response_length); + uint8_t txbuf[len]; + uint8_t rxbuf[len]; + memset(txbuf, 0, len); + knc_prepare_transfer(txbuf, 0, len, channel, request_length, request, response_length); + knc_trnsp_transfer(ctx, txbuf, rxbuf, len); + + uint8_t *response_buf; + int rc = knc_decode_response(rxbuf, request_length, &response_buf, response_length); + if (response) + memcpy(response, response_buf, response_length); + return rc; +} + +int knc_decode_info(uint8_t *response, struct knc_die_info *die_info) +{ + int cores_in_die = response[0]<<8 | response[1]; + int version = response[2]<<8 | response[3]; + if (version == KNC_ASIC_VERSION_JUPITER && cores_in_die <= 48) { + die_info->version = KNC_VERSION_JUPITER; + die_info->cores = cores_in_die; + memset(die_info->want_work, -1, cores_in_die); + die_info->pll_power_down = -1; + die_info->pll_reset_n = -1; + die_info->hash_reset_n = -1; + die_info->pll_locked = -1; + return 0; + } else if (version == KNC_ASIC_VERSION_NEPTUNE && cores_in_die <= KNC_MAX_CORES_PER_DIE) { + die_info->version = KNC_VERSION_NEPTUNE; + die_info->cores = cores_in_die; + int core; + for (core = 0; core < cores_in_die; core++) + die_info->want_work[core] = ((response[12 + core/4] >> ((3-(core % 4)) * 2)) >> 1) & 1; + int die_status = response[11] & 0xf; + die_info->pll_power_down = (die_status >> 0) & 1; + die_info->pll_reset_n = (die_status >> 1) & 1; + die_info->hash_reset_n = (die_status >> 2) & 1; + die_info->pll_locked = (die_status >> 3) & 1; + return 0; + } else { + return -1; + } +} + +int knc_decode_report(uint8_t *response, struct knc_report *report, int version) +{ +/* + * reserved 2 bits + * next_state 1 bit next work state loaded + * state 1 bit hashing (0 on Jupiter) + * next_slot 4 bit slot id of next work state (0 on Jupiter) + * progress 8 bits upper 8 bits of nonce counter + * active_slot 4 bits slot id of current work state + * nonce_slot 4 bits slot id of found nonce + * nonce 32 bits + * + * reserved 4 bits + * nonce_slot 4 bits + * nonce 32 bits + */ + report->next_state = (response[0] >> 5) & 1; + if (version != KNC_VERSION_JUPITER) { + report->state = (response[0] >> 4) & 1; + report->next_slot = response[0] & ((1<<4)-1); + } else { + report->state = -1; + report->next_slot = -1; + } + report->progress = (uint32_t)response[1] << 24; + report->active_slot = (response[2] >> 4) & ((1<<4)-1); + int n; + int n_nonces = version == KNC_VERSION_JUPITER ? 1 : 5; + for (n = 0; n < n_nonces; n++) { + report->nonce[n].slot = response[2+n*5] & ((1<<4)-1); + report->nonce[n].nonce = + (uint32_t)response[3+n*5] << 24 | + (uint32_t)response[4+n*5] << 16 | + (uint32_t)response[5+n*5] << 8 | + (uint32_t)response[6+n*5] << 0 | + 0; + } + for (; n < KNC_NONCES_PER_REPORT; n++) { + report->nonce[n].slot = -1; + report->nonce[n].nonce = 0; + } + return 0; +} + +int knc_detect_die(void *ctx, int channel, int die, struct knc_die_info *die_info) +{ + uint8_t request[4]; + int response_len = 2 + 2 + 4 + 4 + (KNC_MAX_CORES_PER_DIE*2 + 7) / 8; + uint8_t response[response_len]; + + int request_len = knc_prepare_info(request, die, die_info, &response_len); + int status = knc_syncronous_transfer(ctx, channel, request_len, request, response_len, response); + + /* Workaround for pre-ASIC version */ + int cores_in_die = response[0]<<8 | response[1]; + int version = response[2]<<8 | response[3]; + if (version == KNC_ASIC_VERSION_NEPTUNE && cores_in_die < KNC_MAX_CORES_PER_DIE) { + applog(LOG_DEBUG, "KnC %d-%d: Looks like a NEPTUNE die with %d cores", channel, die, cores_in_die); + /* Try again with right response size */ + response_len = 2 + 2 + 4 + 4 + (cores_in_die*2 + 7) / 8; + status = knc_syncronous_transfer(ctx, channel, request_len, request, response_len, response); + } + int rc = -1; + if (version == KNC_ASIC_VERSION_JUPITER || status == 0) + rc = knc_decode_info(response, die_info); + if (rc == 0) + applog(LOG_INFO, "KnC %d-%d: Found %s die with %d cores", channel, die, + die_info->version == KNC_VERSION_NEPTUNE ? "NEPTUNE" : + die_info->version == KNC_VERSION_JUPITER ? "JUPITER" : + "UNKNOWN", + cores_in_die); + else + applog(LOG_DEBUG, "KnC %d-%d: No KnC chip found", channel, die); + return rc; +} + diff --git a/knc-asic.h b/knc-asic.h new file mode 100644 index 0000000..7c48317 --- /dev/null +++ b/knc-asic.h @@ -0,0 +1,88 @@ +#ifndef _CGMINER_NEPTUNE_H +#define _CGMINER_NEPTUNE_H +#include +#include "miner.h" + +/* ASIC Command codes */ +#define KNC_ASIC_CMD_GETINFO 0x80 +#define KNC_ASIC_CMD_SETWORK 0x81 +#define KNC_ASIC_CMD_SETWORK_CLEAN 0x83 /* Neptune */ +#define KNC_ASIC_CMD_HALT 0x83 /* Jupiter */ +#define KNC_ASIC_CMD_REPORT 0x82 + +/* Status byte */ +#define KNC_ASIC_ACK_CRC (1<<5) +#define KNC_ASIC_ACK_ACCEPT (1<<2) +#define KNC_ASIC_ACK_MASK (~(KNC_ASIC_ACK_CRC|KNC_ASIC_ACK_ACCEPT)) +#define KNC_ASIC_ACK_MATCH ((1<<7)|(1<<0)) + +/* Version word */ +#define KNC_ASIC_VERSION_JUPITER 0xa001 +#define KNC_ASIC_VERSION_NEPTUNE 0xa002 + +/* Limits of current chips & I/O board */ +#define KNC_MAX_CORES_PER_DIE 360 +#define KNC_MAX_ASICS 6 + +struct knc_die_info { + enum { + KNC_VERSION_UNKNOWN = 0, + KNC_VERSION_JUPITER, + KNC_VERSION_NEPTUNE + } version; + char want_work[KNC_MAX_CORES_PER_DIE]; + int cores; + int pll_locked; + int hash_reset_n; + int pll_reset_n; + int pll_power_down; +}; + +#define KNC_NONCES_PER_REPORT 5 + +struct knc_report { + int next_state; + int state; + int next_slot; + int active_slot; + uint32_t progress; + struct { + int slot; + uint32_t nonce; + } nonce[KNC_NONCES_PER_REPORT]; +}; + +int knc_prepare_info(uint8_t *request, int die, struct knc_die_info *die_info, int *response_size); +int knc_prepare_report(uint8_t *request, int die, int core); +int knc_prepare_neptune_setwork(uint8_t *request, int die, int core, int slot, struct work *work, int clean); +int knc_prepare_jupiter_setwork(uint8_t *request, int die, int core, int slot, struct work *work); +int knc_prepare_jupiter_halt(uint8_t *request, int die, int core); +int knc_prepare_neptune_halt(uint8_t *request, int die, int core); + +int knc_decode_info(uint8_t *response, struct knc_die_info *die_info); +int knc_decode_report(uint8_t *response, struct knc_report *report, int version); + +void knc_prepare_neptune_message(int request_length, const uint8_t *request, uint8_t *buffer); + +#define KNC_ACCEPTED (1<<0) +#define KNC_ERR_CRC (1<<1) +#define KNC_ERR_ACK (1<<2) +#define KNC_ERR_CRCACK (1<<3) +#define KNC_ERR_UNAVAIL (1<<4) +#define KNC_ERR_MASK (~(KNC_ACCEPTED)) +#define KNC_IS_ERROR(x) (((x) & KNC_ERR_MASK) != 0) + +int knc_prepare_transfer(uint8_t *txbuf, int offset, int size, int channel, int request_length, const uint8_t *request, int response_length); +int knc_decode_response(uint8_t *rxbuf, int request_length, uint8_t **response, int response_length); +int knc_syncronous_transfer(void *ctx, int channel, int request_length, const uint8_t *request, int response_length, uint8_t *response); + +/* Detect ASIC DIE version */ +int knc_detect_die(void *ctx, int channel, int die, struct knc_die_info *die_info); + +/* red, green, blue valid range 0 - 15. No response or checksum from controller */ +int knc_prepare_led(uint8_t *txbuf, int offset, int size, int red, int green, int blue); + +/* Reset controller */ +int knc_prepare_reset(uint8_t *txbuf, int offset, int size); + +#endif diff --git a/knc-transport-spi.c b/knc-transport-spi.c new file mode 100644 index 0000000..5cefdf6 --- /dev/null +++ b/knc-transport-spi.c @@ -0,0 +1,148 @@ +/* + * Direct SPI transport layer for KnCminer Jupiters + * + * Copyright 2014 KnCminer + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#include +#include +#include +#include + +#include "logging.h" +#include "miner.h" +#include "hexdump.c" +#include "knc-transport.h" + +#define SPI_DEVICE_TEMPLATE "/dev/spidev%d.%d" +#define SPI_MODE (SPI_CPHA | SPI_CPOL | SPI_CS_HIGH) +#define SPI_BITS_PER_WORD 8 +#define SPI_MAX_SPEED 3000000 +#define SPI_DELAY_USECS 0 + +struct spidev_context { + int fd; + uint32_t speed; + uint16_t delay; + uint8_t mode; + uint8_t bits; +}; + +/* Init SPI transport */ +void *knc_trnsp_new(int dev_idx) +{ + struct spidev_context *ctx; + char dev_name[PATH_MAX]; + + if (NULL == (ctx = malloc(sizeof(struct spidev_context)))) { + applog(LOG_ERR, "KnC transport: Out of memory"); + goto l_exit_error; + } + ctx->mode = SPI_MODE; + ctx->bits = SPI_BITS_PER_WORD; + ctx->speed = SPI_MAX_SPEED; + ctx->delay = SPI_DELAY_USECS; + + ctx->fd = -1; + sprintf(dev_name, SPI_DEVICE_TEMPLATE, + dev_idx + 1, /* bus */ + 0 /* chipselect */ + ); + if (0 > (ctx->fd = open(dev_name, O_RDWR))) { + applog(LOG_ERR, "KnC transport: Can not open SPI device %s: %m", + dev_name); + goto l_free_exit_error; + } + + /* + * spi mode + */ + if (0 > ioctl(ctx->fd, SPI_IOC_WR_MODE, &ctx->mode)) + goto l_ioctl_error; + if (0 > ioctl(ctx->fd, SPI_IOC_RD_MODE, &ctx->mode)) + goto l_ioctl_error; + + /* + * bits per word + */ + if (0 > ioctl(ctx->fd, SPI_IOC_WR_BITS_PER_WORD, &ctx->bits)) + goto l_ioctl_error; + if (0 > ioctl(ctx->fd, SPI_IOC_RD_BITS_PER_WORD, &ctx->bits)) + goto l_ioctl_error; + + /* + * max speed hz + */ + if (0 > ioctl(ctx->fd, SPI_IOC_WR_MAX_SPEED_HZ, &ctx->speed)) + goto l_ioctl_error; + if (0 > ioctl(ctx->fd, SPI_IOC_RD_MAX_SPEED_HZ, &ctx->speed)) + goto l_ioctl_error; + + applog(LOG_INFO, "KnC transport: SPI device %s uses mode %hhu, bits %hhu, speed %u", + dev_name, ctx->mode, ctx->bits, ctx->speed); + + return ctx; + +l_ioctl_error: + applog(LOG_ERR, "KnC transport: ioctl error on SPI device %s: %m", dev_name); + close(ctx->fd); +l_free_exit_error: + free(ctx); +l_exit_error: + return NULL; +} + +void knc_trnsp_free(void *opaque_ctx) +{ + struct spidev_context *ctx = opaque_ctx; + + if (NULL == ctx) + return; + + close(ctx->fd); + free(ctx); +} + +int knc_trnsp_transfer(void *opaque_ctx, uint8_t *txbuf, uint8_t *rxbuf, int len) +{ + struct spidev_context *ctx = opaque_ctx; + struct spi_ioc_transfer xfr; + int ret; + + memset(rxbuf, 0xff, len); + + ret = len; + + xfr.tx_buf = (unsigned long)txbuf; + xfr.rx_buf = (unsigned long)rxbuf; + xfr.len = len; + xfr.speed_hz = ctx->speed; + xfr.delay_usecs = ctx->delay; + xfr.bits_per_word = ctx->bits; + xfr.cs_change = 0; + xfr.pad = 0; + + applog(LOG_DEBUG, "KnC spi:"); + hexdump(txbuf, len); + if (1 > (ret = ioctl(ctx->fd, SPI_IOC_MESSAGE(1), &xfr))) + applog(LOG_ERR, "KnC spi xfer: ioctl error on SPI device: %m"); + hexdump(rxbuf, len); + + return ret; +} + +bool knc_trnsp_asic_detect(void *opaque_ctx, int chip_id) +{ + return true; +} + +void knc_trnsp_periodic_check(void *opaque_ctx) +{ + return; +} + diff --git a/knc-transport.h b/knc-transport.h new file mode 100644 index 0000000..3db9110 --- /dev/null +++ b/knc-transport.h @@ -0,0 +1,23 @@ +/* + * Transport layer interface for KnCminer devices + * + * Copyright 2014 KnCminer + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#define MAX_ASICS 6 +#define NUM_DIES_IN_ASIC 4 +#define CORES_IN_DIE 48 +#define CORES_PER_ASIC (NUM_DIES_IN_ASIC * CORES_IN_DIE) + +#define MAX_BYTES_IN_SPI_XSFER 4096 + +void *knc_trnsp_new(int dev_idx); +void knc_trnsp_free(void *opaque_ctx); +int knc_trnsp_transfer(void *opaque_ctx, uint8_t *txbuf, uint8_t *rxbuf, int len); +bool knc_trnsp_asic_detect(void *opaque_ctx, int chip_id); +void knc_trnsp_periodic_check(void *opaque_ctx); diff --git a/lib/Makefile.am b/lib/Makefile.am new file mode 100644 index 0000000..bb78703 --- /dev/null +++ b/lib/Makefile.am @@ -0,0 +1,367 @@ +## DO NOT EDIT! GENERATED AUTOMATICALLY! +## Process this file with automake to produce Makefile.in. +# Copyright (C) 2002-2011 Free Software Foundation, Inc. +# +# This file is free software, distributed under the terms of the GNU +# General Public License. As a special exception to the GNU General +# Public License, this file may be distributed as part of a program +# that contains a configuration script generated by Autoconf, under +# the same distribution terms as the rest of that program. +# +# Generated by gnulib-tool. +# Reproduce by: gnulib-tool --import --dir=. --lib=libgnu --source-base=lib --m4-base=m4 --doc-base=doc --tests-base=tests --aux-dir=. --no-conditional-dependencies --no-libtool --macro-prefix=gl --no-vc-files memmem sigaction signal + +AUTOMAKE_OPTIONS = 1.5 gnits + +SUBDIRS = +noinst_HEADERS = +noinst_LIBRARIES = +noinst_LTLIBRARIES = +EXTRA_DIST = +BUILT_SOURCES = +SUFFIXES = +MOSTLYCLEANFILES = core *.stackdump +MOSTLYCLEANDIRS = +CLEANFILES = +DISTCLEANFILES = +MAINTAINERCLEANFILES = + +AM_CPPFLAGS = +AM_CFLAGS = + +noinst_LIBRARIES += libgnu.a + +libgnu_a_SOURCES = +libgnu_a_LIBADD = $(gl_LIBOBJS) +libgnu_a_DEPENDENCIES = $(gl_LIBOBJS) +EXTRA_libgnu_a_SOURCES = + +## begin gnulib module arg-nonnull + +# The BUILT_SOURCES created by this Makefile snippet are not used via #include +# statements but through direct file reference. Therefore this snippet must be +# present in all Makefile.am that need it. This is ensured by the applicability +# 'all' defined above. + +BUILT_SOURCES += arg-nonnull.h +# The arg-nonnull.h that gets inserted into generated .h files is the same as +# build-aux/arg-nonnull.h, except that it has the copyright header cut off. +arg-nonnull.h: $(top_srcdir)/./arg-nonnull.h + $(AM_V_GEN)rm -f $@-t $@ && \ + sed -n -e '/GL_ARG_NONNULL/,$$p' \ + < $(top_srcdir)/./arg-nonnull.h \ + > $@-t && \ + mv $@-t $@ +MOSTLYCLEANFILES += arg-nonnull.h arg-nonnull.h-t + +ARG_NONNULL_H=arg-nonnull.h + +EXTRA_DIST += $(top_srcdir)/./arg-nonnull.h + +## end gnulib module arg-nonnull + +## begin gnulib module c++defs + +# The BUILT_SOURCES created by this Makefile snippet are not used via #include +# statements but through direct file reference. Therefore this snippet must be +# present in all Makefile.am that need it. This is ensured by the applicability +# 'all' defined above. + +BUILT_SOURCES += c++defs.h +# The c++defs.h that gets inserted into generated .h files is the same as +# build-aux/c++defs.h, except that it has the copyright header cut off. +c++defs.h: $(top_srcdir)/./c++defs.h + $(AM_V_GEN)rm -f $@-t $@ && \ + sed -n -e '/_GL_CXXDEFS/,$$p' \ + < $(top_srcdir)/./c++defs.h \ + > $@-t && \ + mv $@-t $@ +MOSTLYCLEANFILES += c++defs.h c++defs.h-t + +CXXDEFS_H=c++defs.h + +EXTRA_DIST += $(top_srcdir)/./c++defs.h + +## end gnulib module c++defs + +## begin gnulib module memchr + + +EXTRA_DIST += memchr.c memchr.valgrind + +EXTRA_libgnu_a_SOURCES += memchr.c + +## end gnulib module memchr + +## begin gnulib module memmem-simple + + +EXTRA_DIST += memmem.c str-two-way.h + +EXTRA_libgnu_a_SOURCES += memmem.c + +## end gnulib module memmem-simple + +## begin gnulib module sigaction + + +EXTRA_DIST += sig-handler.h sigaction.c + +EXTRA_libgnu_a_SOURCES += sigaction.c + +## end gnulib module sigaction + +## begin gnulib module signal + +BUILT_SOURCES += signal.h + +# We need the following in order to create when the system +# doesn't have a complete one. +signal.h: signal.in.h $(top_builddir)/config.status $(CXXDEFS_H) $(ARG_NONNULL_H) $(WARN_ON_USE_H) + $(AM_V_GEN)rm -f $@-t $@ && \ + { echo '/* DO NOT EDIT! GENERATED AUTOMATICALLY! */' && \ + sed -e 's|@''GUARD_PREFIX''@|GL|g' \ + -e 's|@''INCLUDE_NEXT''@|$(INCLUDE_NEXT)|g' \ + -e 's|@''PRAGMA_SYSTEM_HEADER''@|@PRAGMA_SYSTEM_HEADER@|g' \ + -e 's|@''PRAGMA_COLUMNS''@|@PRAGMA_COLUMNS@|g' \ + -e 's|@''NEXT_SIGNAL_H''@|$(NEXT_SIGNAL_H)|g' \ + -e 's/@''GNULIB_SIGNAL_H_SIGPIPE''@/$(GNULIB_SIGNAL_H_SIGPIPE)/g' \ + -e 's/@''GNULIB_SIGPROCMASK''@/$(GNULIB_SIGPROCMASK)/g' \ + -e 's/@''GNULIB_SIGACTION''@/$(GNULIB_SIGACTION)/g' \ + -e 's|@''HAVE_POSIX_SIGNALBLOCKING''@|$(HAVE_POSIX_SIGNALBLOCKING)|g' \ + -e 's|@''HAVE_SIGSET_T''@|$(HAVE_SIGSET_T)|g' \ + -e 's|@''HAVE_SIGINFO_T''@|$(HAVE_SIGINFO_T)|g' \ + -e 's|@''HAVE_SIGACTION''@|$(HAVE_SIGACTION)|g' \ + -e 's|@''HAVE_STRUCT_SIGACTION_SA_SIGACTION''@|$(HAVE_STRUCT_SIGACTION_SA_SIGACTION)|g' \ + -e 's|@''HAVE_TYPE_VOLATILE_SIG_ATOMIC_T''@|$(HAVE_TYPE_VOLATILE_SIG_ATOMIC_T)|g' \ + -e 's|@''HAVE_SIGHANDLER_T''@|$(HAVE_SIGHANDLER_T)|g' \ + -e '/definitions of _GL_FUNCDECL_RPL/r $(CXXDEFS_H)' \ + -e '/definition of _GL_ARG_NONNULL/r $(ARG_NONNULL_H)' \ + -e '/definition of _GL_WARN_ON_USE/r $(WARN_ON_USE_H)' \ + < $(srcdir)/signal.in.h; \ + } > $@-t && \ + mv $@-t $@ +MOSTLYCLEANFILES += signal.h signal.h-t + +EXTRA_DIST += signal.in.h + +## end gnulib module signal + +## begin gnulib module sigprocmask + + +EXTRA_DIST += sigprocmask.c + +EXTRA_libgnu_a_SOURCES += sigprocmask.c + +## end gnulib module sigprocmask + +## begin gnulib module stddef + +BUILT_SOURCES += $(STDDEF_H) + +# We need the following in order to create when the system +# doesn't have one that works with the given compiler. +if GL_GENERATE_STDDEF_H +stddef.h: stddef.in.h $(top_builddir)/config.status + $(AM_V_GEN)rm -f $@-t $@ && \ + { echo '/* DO NOT EDIT! GENERATED AUTOMATICALLY! */' && \ + sed -e 's|@''GUARD_PREFIX''@|GL|g' \ + -e 's|@''INCLUDE_NEXT''@|$(INCLUDE_NEXT)|g' \ + -e 's|@''PRAGMA_SYSTEM_HEADER''@|@PRAGMA_SYSTEM_HEADER@|g' \ + -e 's|@''PRAGMA_COLUMNS''@|@PRAGMA_COLUMNS@|g' \ + -e 's|@''NEXT_STDDEF_H''@|$(NEXT_STDDEF_H)|g' \ + -e 's|@''HAVE_WCHAR_T''@|$(HAVE_WCHAR_T)|g' \ + -e 's|@''REPLACE_NULL''@|$(REPLACE_NULL)|g' \ + < $(srcdir)/stddef.in.h; \ + } > $@-t && \ + mv $@-t $@ +else +stddef.h: $(top_builddir)/config.status + rm -f $@ +endif +MOSTLYCLEANFILES += stddef.h stddef.h-t + +EXTRA_DIST += stddef.in.h + +## end gnulib module stddef + +## begin gnulib module stdint + +BUILT_SOURCES += $(STDINT_H) + +# We need the following in order to create when the system +# doesn't have one that works with the given compiler. +if GL_GENERATE_STDINT_H +stdint.h: stdint.in.h $(top_builddir)/config.status + $(AM_V_GEN)rm -f $@-t $@ && \ + { echo '/* DO NOT EDIT! GENERATED AUTOMATICALLY! */'; \ + sed -e 's|@''GUARD_PREFIX''@|GL|g' \ + -e 's/@''HAVE_STDINT_H''@/$(HAVE_STDINT_H)/g' \ + -e 's|@''INCLUDE_NEXT''@|$(INCLUDE_NEXT)|g' \ + -e 's|@''PRAGMA_SYSTEM_HEADER''@|@PRAGMA_SYSTEM_HEADER@|g' \ + -e 's|@''PRAGMA_COLUMNS''@|@PRAGMA_COLUMNS@|g' \ + -e 's|@''NEXT_STDINT_H''@|$(NEXT_STDINT_H)|g' \ + -e 's/@''HAVE_SYS_TYPES_H''@/$(HAVE_SYS_TYPES_H)/g' \ + -e 's/@''HAVE_INTTYPES_H''@/$(HAVE_INTTYPES_H)/g' \ + -e 's/@''HAVE_SYS_INTTYPES_H''@/$(HAVE_SYS_INTTYPES_H)/g' \ + -e 's/@''HAVE_SYS_BITYPES_H''@/$(HAVE_SYS_BITYPES_H)/g' \ + -e 's/@''HAVE_WCHAR_H''@/$(HAVE_WCHAR_H)/g' \ + -e 's/@''HAVE_LONG_LONG_INT''@/$(HAVE_LONG_LONG_INT)/g' \ + -e 's/@''HAVE_UNSIGNED_LONG_LONG_INT''@/$(HAVE_UNSIGNED_LONG_LONG_INT)/g' \ + -e 's/@''APPLE_UNIVERSAL_BUILD''@/$(APPLE_UNIVERSAL_BUILD)/g' \ + -e 's/@''BITSIZEOF_PTRDIFF_T''@/$(BITSIZEOF_PTRDIFF_T)/g' \ + -e 's/@''PTRDIFF_T_SUFFIX''@/$(PTRDIFF_T_SUFFIX)/g' \ + -e 's/@''BITSIZEOF_SIG_ATOMIC_T''@/$(BITSIZEOF_SIG_ATOMIC_T)/g' \ + -e 's/@''HAVE_SIGNED_SIG_ATOMIC_T''@/$(HAVE_SIGNED_SIG_ATOMIC_T)/g' \ + -e 's/@''SIG_ATOMIC_T_SUFFIX''@/$(SIG_ATOMIC_T_SUFFIX)/g' \ + -e 's/@''BITSIZEOF_SIZE_T''@/$(BITSIZEOF_SIZE_T)/g' \ + -e 's/@''SIZE_T_SUFFIX''@/$(SIZE_T_SUFFIX)/g' \ + -e 's/@''BITSIZEOF_WCHAR_T''@/$(BITSIZEOF_WCHAR_T)/g' \ + -e 's/@''HAVE_SIGNED_WCHAR_T''@/$(HAVE_SIGNED_WCHAR_T)/g' \ + -e 's/@''WCHAR_T_SUFFIX''@/$(WCHAR_T_SUFFIX)/g' \ + -e 's/@''BITSIZEOF_WINT_T''@/$(BITSIZEOF_WINT_T)/g' \ + -e 's/@''HAVE_SIGNED_WINT_T''@/$(HAVE_SIGNED_WINT_T)/g' \ + -e 's/@''WINT_T_SUFFIX''@/$(WINT_T_SUFFIX)/g' \ + < $(srcdir)/stdint.in.h; \ + } > $@-t && \ + mv $@-t $@ +else +stdint.h: $(top_builddir)/config.status + rm -f $@ +endif +MOSTLYCLEANFILES += stdint.h stdint.h-t + +EXTRA_DIST += stdint.in.h + +## end gnulib module stdint + +## begin gnulib module string + +BUILT_SOURCES += string.h + +# We need the following in order to create when the system +# doesn't have one that works with the given compiler. +string.h: string.in.h $(top_builddir)/config.status $(CXXDEFS_H) $(ARG_NONNULL_H) $(WARN_ON_USE_H) + $(AM_V_GEN)rm -f $@-t $@ && \ + { echo '/* DO NOT EDIT! GENERATED AUTOMATICALLY! */' && \ + sed -e 's|@''GUARD_PREFIX''@|GL|g' \ + -e 's|@''INCLUDE_NEXT''@|$(INCLUDE_NEXT)|g' \ + -e 's|@''PRAGMA_SYSTEM_HEADER''@|@PRAGMA_SYSTEM_HEADER@|g' \ + -e 's|@''PRAGMA_COLUMNS''@|@PRAGMA_COLUMNS@|g' \ + -e 's|@''NEXT_STRING_H''@|$(NEXT_STRING_H)|g' \ + -e 's/@''GNULIB_MBSLEN''@/$(GNULIB_MBSLEN)/g' \ + -e 's/@''GNULIB_MBSNLEN''@/$(GNULIB_MBSNLEN)/g' \ + -e 's/@''GNULIB_MBSCHR''@/$(GNULIB_MBSCHR)/g' \ + -e 's/@''GNULIB_MBSRCHR''@/$(GNULIB_MBSRCHR)/g' \ + -e 's/@''GNULIB_MBSSTR''@/$(GNULIB_MBSSTR)/g' \ + -e 's/@''GNULIB_MBSCASECMP''@/$(GNULIB_MBSCASECMP)/g' \ + -e 's/@''GNULIB_MBSNCASECMP''@/$(GNULIB_MBSNCASECMP)/g' \ + -e 's/@''GNULIB_MBSPCASECMP''@/$(GNULIB_MBSPCASECMP)/g' \ + -e 's/@''GNULIB_MBSCASESTR''@/$(GNULIB_MBSCASESTR)/g' \ + -e 's/@''GNULIB_MBSCSPN''@/$(GNULIB_MBSCSPN)/g' \ + -e 's/@''GNULIB_MBSPBRK''@/$(GNULIB_MBSPBRK)/g' \ + -e 's/@''GNULIB_MBSSPN''@/$(GNULIB_MBSSPN)/g' \ + -e 's/@''GNULIB_MBSSEP''@/$(GNULIB_MBSSEP)/g' \ + -e 's/@''GNULIB_MBSTOK_R''@/$(GNULIB_MBSTOK_R)/g' \ + -e 's/@''GNULIB_MEMCHR''@/$(GNULIB_MEMCHR)/g' \ + -e 's/@''GNULIB_MEMMEM''@/$(GNULIB_MEMMEM)/g' \ + -e 's/@''GNULIB_MEMPCPY''@/$(GNULIB_MEMPCPY)/g' \ + -e 's/@''GNULIB_MEMRCHR''@/$(GNULIB_MEMRCHR)/g' \ + -e 's/@''GNULIB_RAWMEMCHR''@/$(GNULIB_RAWMEMCHR)/g' \ + -e 's/@''GNULIB_STPCPY''@/$(GNULIB_STPCPY)/g' \ + -e 's/@''GNULIB_STPNCPY''@/$(GNULIB_STPNCPY)/g' \ + -e 's/@''GNULIB_STRCHRNUL''@/$(GNULIB_STRCHRNUL)/g' \ + -e 's/@''GNULIB_STRDUP''@/$(GNULIB_STRDUP)/g' \ + -e 's/@''GNULIB_STRNCAT''@/$(GNULIB_STRNCAT)/g' \ + -e 's/@''GNULIB_STRNDUP''@/$(GNULIB_STRNDUP)/g' \ + -e 's/@''GNULIB_STRNLEN''@/$(GNULIB_STRNLEN)/g' \ + -e 's/@''GNULIB_STRPBRK''@/$(GNULIB_STRPBRK)/g' \ + -e 's/@''GNULIB_STRSEP''@/$(GNULIB_STRSEP)/g' \ + -e 's/@''GNULIB_STRSTR''@/$(GNULIB_STRSTR)/g' \ + -e 's/@''GNULIB_STRCASESTR''@/$(GNULIB_STRCASESTR)/g' \ + -e 's/@''GNULIB_STRTOK_R''@/$(GNULIB_STRTOK_R)/g' \ + -e 's/@''GNULIB_STRERROR''@/$(GNULIB_STRERROR)/g' \ + -e 's/@''GNULIB_STRERROR_R''@/$(GNULIB_STRERROR_R)/g' \ + -e 's/@''GNULIB_STRSIGNAL''@/$(GNULIB_STRSIGNAL)/g' \ + -e 's/@''GNULIB_STRVERSCMP''@/$(GNULIB_STRVERSCMP)/g' \ + < $(srcdir)/string.in.h | \ + sed -e 's|@''HAVE_MBSLEN''@|$(HAVE_MBSLEN)|g' \ + -e 's|@''HAVE_MEMCHR''@|$(HAVE_MEMCHR)|g' \ + -e 's|@''HAVE_DECL_MEMMEM''@|$(HAVE_DECL_MEMMEM)|g' \ + -e 's|@''HAVE_MEMPCPY''@|$(HAVE_MEMPCPY)|g' \ + -e 's|@''HAVE_DECL_MEMRCHR''@|$(HAVE_DECL_MEMRCHR)|g' \ + -e 's|@''HAVE_RAWMEMCHR''@|$(HAVE_RAWMEMCHR)|g' \ + -e 's|@''HAVE_STPCPY''@|$(HAVE_STPCPY)|g' \ + -e 's|@''HAVE_STPNCPY''@|$(HAVE_STPNCPY)|g' \ + -e 's|@''HAVE_STRCHRNUL''@|$(HAVE_STRCHRNUL)|g' \ + -e 's|@''HAVE_DECL_STRDUP''@|$(HAVE_DECL_STRDUP)|g' \ + -e 's|@''HAVE_DECL_STRNDUP''@|$(HAVE_DECL_STRNDUP)|g' \ + -e 's|@''HAVE_DECL_STRNLEN''@|$(HAVE_DECL_STRNLEN)|g' \ + -e 's|@''HAVE_STRPBRK''@|$(HAVE_STRPBRK)|g' \ + -e 's|@''HAVE_STRSEP''@|$(HAVE_STRSEP)|g' \ + -e 's|@''HAVE_STRCASESTR''@|$(HAVE_STRCASESTR)|g' \ + -e 's|@''HAVE_DECL_STRTOK_R''@|$(HAVE_DECL_STRTOK_R)|g' \ + -e 's|@''HAVE_DECL_STRERROR_R''@|$(HAVE_DECL_STRERROR_R)|g' \ + -e 's|@''HAVE_DECL_STRSIGNAL''@|$(HAVE_DECL_STRSIGNAL)|g' \ + -e 's|@''HAVE_STRVERSCMP''@|$(HAVE_STRVERSCMP)|g' \ + -e 's|@''REPLACE_STPNCPY''@|$(REPLACE_STPNCPY)|g' \ + -e 's|@''REPLACE_MEMCHR''@|$(REPLACE_MEMCHR)|g' \ + -e 's|@''REPLACE_MEMMEM''@|$(REPLACE_MEMMEM)|g' \ + -e 's|@''REPLACE_STRCASESTR''@|$(REPLACE_STRCASESTR)|g' \ + -e 's|@''REPLACE_STRCHRNUL''@|$(REPLACE_STRCHRNUL)|g' \ + -e 's|@''REPLACE_STRDUP''@|$(REPLACE_STRDUP)|g' \ + -e 's|@''REPLACE_STRSTR''@|$(REPLACE_STRSTR)|g' \ + -e 's|@''REPLACE_STRERROR''@|$(REPLACE_STRERROR)|g' \ + -e 's|@''REPLACE_STRERROR_R''@|$(REPLACE_STRERROR_R)|g' \ + -e 's|@''REPLACE_STRNCAT''@|$(REPLACE_STRNCAT)|g' \ + -e 's|@''REPLACE_STRNDUP''@|$(REPLACE_STRNDUP)|g' \ + -e 's|@''REPLACE_STRNLEN''@|$(REPLACE_STRNLEN)|g' \ + -e 's|@''REPLACE_STRSIGNAL''@|$(REPLACE_STRSIGNAL)|g' \ + -e 's|@''REPLACE_STRTOK_R''@|$(REPLACE_STRTOK_R)|g' \ + -e 's|@''UNDEFINE_STRTOK_R''@|$(UNDEFINE_STRTOK_R)|g' \ + -e '/definitions of _GL_FUNCDECL_RPL/r $(CXXDEFS_H)' \ + -e '/definition of _GL_ARG_NONNULL/r $(ARG_NONNULL_H)' \ + -e '/definition of _GL_WARN_ON_USE/r $(WARN_ON_USE_H)'; \ + < $(srcdir)/string.in.h; \ + } > $@-t && \ + mv $@-t $@ +MOSTLYCLEANFILES += string.h string.h-t + +EXTRA_DIST += string.in.h + +## end gnulib module string + +## begin gnulib module warn-on-use + +BUILT_SOURCES += warn-on-use.h +# The warn-on-use.h that gets inserted into generated .h files is the same as +# build-aux/warn-on-use.h, except that it has the copyright header cut off. +warn-on-use.h: $(top_srcdir)/./warn-on-use.h + $(AM_V_GEN)rm -f $@-t $@ && \ + sed -n -e '/^.ifndef/,$$p' \ + < $(top_srcdir)/./warn-on-use.h \ + > $@-t && \ + mv $@-t $@ +MOSTLYCLEANFILES += warn-on-use.h warn-on-use.h-t + +WARN_ON_USE_H=warn-on-use.h + +EXTRA_DIST += $(top_srcdir)/./warn-on-use.h + +## end gnulib module warn-on-use + +## begin gnulib module dummy + +libgnu_a_SOURCES += dummy.c + +## end gnulib module dummy + + +mostlyclean-local: mostlyclean-generic + @for dir in '' $(MOSTLYCLEANDIRS); do \ + if test -n "$$dir" && test -d $$dir; then \ + echo "rmdir $$dir"; rmdir $$dir; \ + fi; \ + done; \ + : diff --git a/lib/dummy.c b/lib/dummy.c new file mode 100644 index 0000000..c958ea0 --- /dev/null +++ b/lib/dummy.c @@ -0,0 +1,42 @@ +/* A dummy file, to prevent empty libraries from breaking builds. + Copyright (C) 2004, 2007, 2009-2011 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +/* Some systems, reportedly OpenBSD and Mac OS X, refuse to create + libraries without any object files. You might get an error like: + + > ar cru .libs/libgl.a + > ar: no archive members specified + + Compiling this file, and adding its object file to the library, will + prevent the library from being empty. */ + +/* Some systems, such as Solaris with cc 5.0, refuse to work with libraries + that don't export any symbol. You might get an error like: + + > cc ... libgnu.a + > ild: (bad file) garbled symbol table in archive ../gllib/libgnu.a + + Compiling this file, and adding its object file to the library, will + prevent the library from exporting no symbols. */ + +#ifdef __sun +/* This declaration ensures that the library will export at least 1 symbol. */ +int gl_dummy_symbol; +#else +/* This declaration is solely to ensure that after preprocessing + this file is never empty. */ +typedef int dummy; +#endif diff --git a/lib/memchr.c b/lib/memchr.c new file mode 100644 index 0000000..6d903b1 --- /dev/null +++ b/lib/memchr.c @@ -0,0 +1,172 @@ +/* Copyright (C) 1991, 1993, 1996-1997, 1999-2000, 2003-2004, 2006, 2008-2011 + Free Software Foundation, Inc. + + Based on strlen implementation by Torbjorn Granlund (tege@sics.se), + with help from Dan Sahlin (dan@sics.se) and + commentary by Jim Blandy (jimb@ai.mit.edu); + adaptation to memchr suggested by Dick Karpinski (dick@cca.ucsf.edu), + and implemented by Roland McGrath (roland@ai.mit.edu). + +NOTE: The canonical source of this file is maintained with the GNU C Library. +Bugs can be reported to bug-glibc@prep.ai.mit.edu. + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the +Free Software Foundation; either version 3 of the License, or any +later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . */ + +#ifndef _LIBC +# include +#endif + +#include + +#include + +#if defined _LIBC +# include +#else +# define reg_char char +#endif + +#include + +#if HAVE_BP_SYM_H || defined _LIBC +# include +#else +# define BP_SYM(sym) sym +#endif + +#undef __memchr +#ifdef _LIBC +# undef memchr +#endif + +#ifndef weak_alias +# define __memchr memchr +#endif + +/* Search no more than N bytes of S for C. */ +void * +__memchr (void const *s, int c_in, size_t n) +{ + /* On 32-bit hardware, choosing longword to be a 32-bit unsigned + long instead of a 64-bit uintmax_t tends to give better + performance. On 64-bit hardware, unsigned long is generally 64 + bits already. Change this typedef to experiment with + performance. */ + typedef unsigned long int longword; + + const unsigned char *char_ptr; + const longword *longword_ptr; + longword repeated_one; + longword repeated_c; + unsigned reg_char c; + + c = (unsigned char) c_in; + + /* Handle the first few bytes by reading one byte at a time. + Do this until CHAR_PTR is aligned on a longword boundary. */ + for (char_ptr = (const unsigned char *) s; + n > 0 && (size_t) char_ptr % sizeof (longword) != 0; + --n, ++char_ptr) + if (*char_ptr == c) + return (void *) char_ptr; + + longword_ptr = (const longword *) char_ptr; + + /* All these elucidatory comments refer to 4-byte longwords, + but the theory applies equally well to any size longwords. */ + + /* Compute auxiliary longword values: + repeated_one is a value which has a 1 in every byte. + repeated_c has c in every byte. */ + repeated_one = 0x01010101; + repeated_c = c | (c << 8); + repeated_c |= repeated_c << 16; + if (0xffffffffU < (longword) -1) + { + repeated_one |= repeated_one << 31 << 1; + repeated_c |= repeated_c << 31 << 1; + if (8 < sizeof (longword)) + { + size_t i; + + for (i = 64; i < sizeof (longword) * 8; i *= 2) + { + repeated_one |= repeated_one << i; + repeated_c |= repeated_c << i; + } + } + } + + /* Instead of the traditional loop which tests each byte, we will test a + longword at a time. The tricky part is testing if *any of the four* + bytes in the longword in question are equal to c. We first use an xor + with repeated_c. This reduces the task to testing whether *any of the + four* bytes in longword1 is zero. + + We compute tmp = + ((longword1 - repeated_one) & ~longword1) & (repeated_one << 7). + That is, we perform the following operations: + 1. Subtract repeated_one. + 2. & ~longword1. + 3. & a mask consisting of 0x80 in every byte. + Consider what happens in each byte: + - If a byte of longword1 is zero, step 1 and 2 transform it into 0xff, + and step 3 transforms it into 0x80. A carry can also be propagated + to more significant bytes. + - If a byte of longword1 is nonzero, let its lowest 1 bit be at + position k (0 <= k <= 7); so the lowest k bits are 0. After step 1, + the byte ends in a single bit of value 0 and k bits of value 1. + After step 2, the result is just k bits of value 1: 2^k - 1. After + step 3, the result is 0. And no carry is produced. + So, if longword1 has only non-zero bytes, tmp is zero. + Whereas if longword1 has a zero byte, call j the position of the least + significant zero byte. Then the result has a zero at positions 0, ..., + j-1 and a 0x80 at position j. We cannot predict the result at the more + significant bytes (positions j+1..3), but it does not matter since we + already have a non-zero bit at position 8*j+7. + + So, the test whether any byte in longword1 is zero is equivalent to + testing whether tmp is nonzero. */ + + while (n >= sizeof (longword)) + { + longword longword1 = *longword_ptr ^ repeated_c; + + if ((((longword1 - repeated_one) & ~longword1) + & (repeated_one << 7)) != 0) + break; + longword_ptr++; + n -= sizeof (longword); + } + + char_ptr = (const unsigned char *) longword_ptr; + + /* At this point, we know that either n < sizeof (longword), or one of the + sizeof (longword) bytes starting at char_ptr is == c. On little-endian + machines, we could determine the first such byte without any further + memory accesses, just by looking at the tmp result from the last loop + iteration. But this does not work on big-endian machines. Choose code + that works in both cases. */ + + for (; n > 0; --n, ++char_ptr) + { + if (*char_ptr == c) + return (void *) char_ptr; + } + + return NULL; +} +#ifdef weak_alias +weak_alias (__memchr, BP_SYM (memchr)) +#endif diff --git a/lib/memchr.valgrind b/lib/memchr.valgrind new file mode 100644 index 0000000..60f247e --- /dev/null +++ b/lib/memchr.valgrind @@ -0,0 +1,14 @@ +# Suppress a valgrind message about use of uninitialized memory in memchr(). +# POSIX states that when the character is found, memchr must not read extra +# bytes in an overestimated length (for example, where memchr is used to +# implement strnlen). However, we use a safe word read to provide a speedup. +{ + memchr-value4 + Memcheck:Value4 + fun:rpl_memchr +} +{ + memchr-value8 + Memcheck:Value8 + fun:rpl_memchr +} diff --git a/lib/memmem.c b/lib/memmem.c new file mode 100644 index 0000000..acd1a3e --- /dev/null +++ b/lib/memmem.c @@ -0,0 +1,76 @@ +/* Copyright (C) 1991-1994, 1996-1998, 2000, 2004, 2007-2011 Free Software + Foundation, Inc. + This file is part of the GNU C Library. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* This particular implementation was written by Eric Blake, 2008. */ + +#ifndef _LIBC +# include +#endif + +/* Specification of memmem. */ +#include + +#ifndef _LIBC +# define __builtin_expect(expr, val) (expr) +#endif + +#define RETURN_TYPE void * +#define AVAILABLE(h, h_l, j, n_l) ((j) <= (h_l) - (n_l)) +#include "str-two-way.h" + +/* Return the first occurrence of NEEDLE in HAYSTACK. Return HAYSTACK + if NEEDLE_LEN is 0, otherwise NULL if NEEDLE is not found in + HAYSTACK. */ +void * +memmem (const void *haystack_start, size_t haystack_len, + const void *needle_start, size_t needle_len) +{ + /* Abstract memory is considered to be an array of 'unsigned char' values, + not an array of 'char' values. See ISO C 99 section 6.2.6.1. */ + const unsigned char *haystack = (const unsigned char *) haystack_start; + const unsigned char *needle = (const unsigned char *) needle_start; + + if (needle_len == 0) + /* The first occurrence of the empty string is deemed to occur at + the beginning of the string. */ + return (void *) haystack; + + /* Sanity check, otherwise the loop might search through the whole + memory. */ + if (__builtin_expect (haystack_len < needle_len, 0)) + return NULL; + + /* Use optimizations in memchr when possible, to reduce the search + size of haystack using a linear algorithm with a smaller + coefficient. However, avoid memchr for long needles, since we + can often achieve sublinear performance. */ + if (needle_len < LONG_NEEDLE_THRESHOLD) + { + haystack = memchr (haystack, *needle, haystack_len); + if (!haystack || __builtin_expect (needle_len == 1, 0)) + return (void *) haystack; + haystack_len -= haystack - (const unsigned char *) haystack_start; + if (haystack_len < needle_len) + return NULL; + return two_way_short_needle (haystack, haystack_len, needle, needle_len); + } + else + return two_way_long_needle (haystack, haystack_len, needle, needle_len); +} + +#undef LONG_NEEDLE_THRESHOLD diff --git a/lib/sig-handler.h b/lib/sig-handler.h new file mode 100644 index 0000000..abb660c --- /dev/null +++ b/lib/sig-handler.h @@ -0,0 +1,44 @@ +/* Convenience declarations when working with . + + Copyright (C) 2008-2011 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _GL_SIG_HANDLER_H +#define _GL_SIG_HANDLER_H + +#include + +/* Convenience type when working with signal handlers. */ +typedef void (*sa_handler_t) (int); + +/* Return the handler of a signal, as a sa_handler_t value regardless + of its true type. The resulting function can be compared to + special values like SIG_IGN but it is not portable to call it. */ +static inline sa_handler_t +get_handler (struct sigaction const *a) +{ +#ifdef SA_SIGINFO + /* POSIX says that special values like SIG_IGN can only occur when + action.sa_flags does not contain SA_SIGINFO. But in Linux 2.4, + for example, sa_sigaction and sa_handler are aliases and a signal + is ignored if sa_sigaction (after casting) equals SIG_IGN. So + use (and cast) sa_sigaction in that case. */ + if (a->sa_flags & SA_SIGINFO) + return (sa_handler_t) a->sa_sigaction; +#endif + return a->sa_handler; +} + +#endif /* _GL_SIG_HANDLER_H */ diff --git a/lib/sigaction.c b/lib/sigaction.c new file mode 100644 index 0000000..e6a55da --- /dev/null +++ b/lib/sigaction.c @@ -0,0 +1,204 @@ +/* POSIX compatible signal blocking. + Copyright (C) 2008-2011 Free Software Foundation, Inc. + Written by Eric Blake , 2008. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#include + +/* Specification. */ +#include + +#include +#include +#include + +/* This implementation of sigaction is tailored to Woe32 behavior: + signal() has SysV semantics (ie. the handler is uninstalled before + it is invoked). This is an inherent data race if an asynchronous + signal is sent twice in a row before we can reinstall our handler, + but there's nothing we can do about it. Meanwhile, sigprocmask() + is not present, and while we can use the gnulib replacement to + provide critical sections, it too suffers from potential data races + in the face of an ill-timed asynchronous signal. And we compound + the situation by reading static storage in a signal handler, which + POSIX warns is not generically async-signal-safe. Oh well. + + Additionally: + - We don't implement SA_NOCLDSTOP or SA_NOCLDWAIT, because SIGCHLD + is not defined. + - We don't implement SA_ONSTACK, because sigaltstack() is not present. + - We ignore SA_RESTART, because blocking Win32 calls are not interrupted + anyway when an asynchronous signal occurs, and the MSVCRT runtime + never sets errno to EINTR. + - We don't implement SA_SIGINFO because it is impossible to do so + portably. + + POSIX states that an application should not mix signal() and + sigaction(). We support the use of signal() within the gnulib + sigprocmask() substitute, but all other application code linked + with this module should stick with only sigaction(). */ + +/* Check some of our assumptions. */ +#if defined SIGCHLD || defined HAVE_SIGALTSTACK || defined HAVE_SIGINTERRUPT +# error "Revisit the assumptions made in the sigaction module" +#endif + +/* Out-of-range substitutes make a good fallback for uncatchable + signals. */ +#ifndef SIGKILL +# define SIGKILL (-1) +#endif +#ifndef SIGSTOP +# define SIGSTOP (-1) +#endif + +/* On native Windows, as of 2008, the signal SIGABRT_COMPAT is an alias + for the signal SIGABRT. Only one signal handler is stored for both + SIGABRT and SIGABRT_COMPAT. SIGABRT_COMPAT is not a signal of its own. */ +#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__ +# undef SIGABRT_COMPAT +# define SIGABRT_COMPAT 6 +#endif + +/* A signal handler. */ +typedef void (*handler_t) (int signal); + +/* Set of current actions. If sa_handler for an entry is NULL, then + that signal is not currently handled by the sigaction handler. */ +static struct sigaction volatile action_array[NSIG] /* = 0 */; + +/* Signal handler that is installed for signals. */ +static void +sigaction_handler (int sig) +{ + handler_t handler; + sigset_t mask; + sigset_t oldmask; + int saved_errno = errno; + if (sig < 0 || NSIG <= sig || !action_array[sig].sa_handler) + { + /* Unexpected situation; be careful to avoid recursive abort. */ + if (sig == SIGABRT) + signal (SIGABRT, SIG_DFL); + abort (); + } + + /* Reinstall the signal handler when required; otherwise update the + bookkeeping so that the user's handler may call sigaction and get + accurate results. We know the signal isn't currently blocked, or + we wouldn't be in its handler, therefore we know that we are not + interrupting a sigaction() call. There is a race where any + asynchronous instance of the same signal occurring before we + reinstall the handler will trigger the default handler; oh + well. */ + handler = action_array[sig].sa_handler; + if ((action_array[sig].sa_flags & SA_RESETHAND) == 0) + signal (sig, sigaction_handler); + else + action_array[sig].sa_handler = NULL; + + /* Block appropriate signals. */ + mask = action_array[sig].sa_mask; + if ((action_array[sig].sa_flags & SA_NODEFER) == 0) + sigaddset (&mask, sig); + sigprocmask (SIG_BLOCK, &mask, &oldmask); + + /* Invoke the user's handler, then restore prior mask. */ + errno = saved_errno; + handler (sig); + saved_errno = errno; + sigprocmask (SIG_SETMASK, &oldmask, NULL); + errno = saved_errno; +} + +/* Change and/or query the action that will be taken on delivery of + signal SIG. If not NULL, ACT describes the new behavior. If not + NULL, OACT is set to the prior behavior. Return 0 on success, or + set errno and return -1 on failure. */ +int +sigaction (int sig, const struct sigaction *restrict act, + struct sigaction *restrict oact) +{ + sigset_t mask; + sigset_t oldmask; + int saved_errno; + + if (sig < 0 || NSIG <= sig || sig == SIGKILL || sig == SIGSTOP + || (act && act->sa_handler == SIG_ERR)) + { + errno = EINVAL; + return -1; + } + +#ifdef SIGABRT_COMPAT + if (sig == SIGABRT_COMPAT) + sig = SIGABRT; +#endif + + /* POSIX requires sigaction() to be async-signal-safe. In other + words, if an asynchronous signal can occur while we are anywhere + inside this function, the user's handler could then call + sigaction() recursively and expect consistent results. We meet + this rule by using sigprocmask to block all signals before + modifying any data structure that could be read from a signal + handler; this works since we know that the gnulib sigprocmask + replacement does not try to use sigaction() from its handler. */ + if (!act && !oact) + return 0; + sigfillset (&mask); + sigprocmask (SIG_BLOCK, &mask, &oldmask); + if (oact) + { + if (action_array[sig].sa_handler) + *oact = action_array[sig]; + else + { + /* Safe to change the handler at will here, since all + signals are currently blocked. */ + oact->sa_handler = signal (sig, SIG_DFL); + if (oact->sa_handler == SIG_ERR) + goto failure; + signal (sig, oact->sa_handler); + oact->sa_flags = SA_RESETHAND | SA_NODEFER; + sigemptyset (&oact->sa_mask); + } + } + + if (act) + { + /* Safe to install the handler before updating action_array, + since all signals are currently blocked. */ + if (act->sa_handler == SIG_DFL || act->sa_handler == SIG_IGN) + { + if (signal (sig, act->sa_handler) == SIG_ERR) + goto failure; + action_array[sig].sa_handler = NULL; + } + else + { + if (signal (sig, sigaction_handler) == SIG_ERR) + goto failure; + action_array[sig] = *act; + } + } + sigprocmask (SIG_SETMASK, &oldmask, NULL); + return 0; + + failure: + saved_errno = errno; + sigprocmask (SIG_SETMASK, &oldmask, NULL); + errno = saved_errno; + return -1; +} diff --git a/lib/signal.in.h b/lib/signal.in.h new file mode 100644 index 0000000..9669215 --- /dev/null +++ b/lib/signal.in.h @@ -0,0 +1,378 @@ +/* A GNU-like . + + Copyright (C) 2006-2011 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#if __GNUC__ >= 3 +@PRAGMA_SYSTEM_HEADER@ +#endif +@PRAGMA_COLUMNS@ + +#include "config.h" + +#if defined __need_sig_atomic_t || defined __need_sigset_t +/* Special invocation convention inside glibc header files. */ + +# @INCLUDE_NEXT@ @NEXT_SIGNAL_H@ + +#else +/* Normal invocation convention. */ + +#ifndef _@GUARD_PREFIX@_SIGNAL_H + +/* The include_next requires a split double-inclusion guard. */ +#@INCLUDE_NEXT@ @NEXT_SIGNAL_H@ + +#ifndef _@GUARD_PREFIX@_SIGNAL_H +#define _@GUARD_PREFIX@_SIGNAL_H + +/* The definitions of _GL_FUNCDECL_RPL etc. are copied here. */ + +/* The definition of _GL_ARG_NONNULL is copied here. */ + +/* The definition of _GL_WARN_ON_USE is copied here. */ + +/* Define pid_t, uid_t. + Also, mingw defines sigset_t not in , but in . */ +#include + +/* On AIX, sig_atomic_t already includes volatile. C99 requires that + 'volatile sig_atomic_t' ignore the extra modifier, but C89 did not. + Hence, redefine this to a non-volatile type as needed. */ +#if ! @HAVE_TYPE_VOLATILE_SIG_ATOMIC_T@ +# if !GNULIB_defined_sig_atomic_t +typedef int rpl_sig_atomic_t; +# undef sig_atomic_t +# define sig_atomic_t rpl_sig_atomic_t +# define GNULIB_defined_sig_atomic_t 1 +# endif +#endif + +/* A set or mask of signals. */ +#if !@HAVE_SIGSET_T@ +# if !GNULIB_defined_sigset_t +typedef unsigned int sigset_t; +# define GNULIB_defined_sigset_t 1 +# endif +#endif + +/* Define sighandler_t, the type of signal handlers. A GNU extension. */ +#if !@HAVE_SIGHANDLER_T@ +# ifdef __cplusplus +extern "C" { +# endif +# if !GNULIB_defined_sighandler_t +typedef void (*sighandler_t) (int); +# define GNULIB_defined_sighandler_t 1 +# endif +# ifdef __cplusplus +} +# endif +#endif + + +#if @GNULIB_SIGNAL_H_SIGPIPE@ +# ifndef SIGPIPE +/* Define SIGPIPE to a value that does not overlap with other signals. */ +# define SIGPIPE 13 +# define GNULIB_defined_SIGPIPE 1 +/* To actually use SIGPIPE, you also need the gnulib modules 'sigprocmask', + 'write', 'stdio'. */ +# endif +#endif + + +/* Maximum signal number + 1. */ +#ifndef NSIG +# if defined __TANDEM +# define NSIG 32 +# endif +#endif + + +#if @GNULIB_SIGPROCMASK@ +# if !@HAVE_POSIX_SIGNALBLOCKING@ + +/* Maximum signal number + 1. */ +# ifndef NSIG +# define NSIG 32 +# endif + +/* This code supports only 32 signals. */ +# if !GNULIB_defined_verify_NSIG_constraint +typedef int verify_NSIG_constraint[NSIG <= 32 ? 1 : -1]; +# define GNULIB_defined_verify_NSIG_constraint 1 +# endif + +# endif + +/* Test whether a given signal is contained in a signal set. */ +# if @HAVE_POSIX_SIGNALBLOCKING@ +/* This function is defined as a macro on MacOS X. */ +# if defined __cplusplus && defined GNULIB_NAMESPACE +# undef sigismember +# endif +# else +_GL_FUNCDECL_SYS (sigismember, int, (const sigset_t *set, int sig) + _GL_ARG_NONNULL ((1))); +# endif +_GL_CXXALIAS_SYS (sigismember, int, (const sigset_t *set, int sig)); +_GL_CXXALIASWARN (sigismember); + +/* Initialize a signal set to the empty set. */ +# if @HAVE_POSIX_SIGNALBLOCKING@ +/* This function is defined as a macro on MacOS X. */ +# if defined __cplusplus && defined GNULIB_NAMESPACE +# undef sigemptyset +# endif +# else +_GL_FUNCDECL_SYS (sigemptyset, int, (sigset_t *set) _GL_ARG_NONNULL ((1))); +# endif +_GL_CXXALIAS_SYS (sigemptyset, int, (sigset_t *set)); +_GL_CXXALIASWARN (sigemptyset); + +/* Add a signal to a signal set. */ +# if @HAVE_POSIX_SIGNALBLOCKING@ +/* This function is defined as a macro on MacOS X. */ +# if defined __cplusplus && defined GNULIB_NAMESPACE +# undef sigaddset +# endif +# else +_GL_FUNCDECL_SYS (sigaddset, int, (sigset_t *set, int sig) + _GL_ARG_NONNULL ((1))); +# endif +_GL_CXXALIAS_SYS (sigaddset, int, (sigset_t *set, int sig)); +_GL_CXXALIASWARN (sigaddset); + +/* Remove a signal from a signal set. */ +# if @HAVE_POSIX_SIGNALBLOCKING@ +/* This function is defined as a macro on MacOS X. */ +# if defined __cplusplus && defined GNULIB_NAMESPACE +# undef sigdelset +# endif +# else +_GL_FUNCDECL_SYS (sigdelset, int, (sigset_t *set, int sig) + _GL_ARG_NONNULL ((1))); +# endif +_GL_CXXALIAS_SYS (sigdelset, int, (sigset_t *set, int sig)); +_GL_CXXALIASWARN (sigdelset); + +/* Fill a signal set with all possible signals. */ +# if @HAVE_POSIX_SIGNALBLOCKING@ +/* This function is defined as a macro on MacOS X. */ +# if defined __cplusplus && defined GNULIB_NAMESPACE +# undef sigfillset +# endif +# else +_GL_FUNCDECL_SYS (sigfillset, int, (sigset_t *set) _GL_ARG_NONNULL ((1))); +# endif +_GL_CXXALIAS_SYS (sigfillset, int, (sigset_t *set)); +_GL_CXXALIASWARN (sigfillset); + +/* Return the set of those blocked signals that are pending. */ +# if !@HAVE_POSIX_SIGNALBLOCKING@ +_GL_FUNCDECL_SYS (sigpending, int, (sigset_t *set) _GL_ARG_NONNULL ((1))); +# endif +_GL_CXXALIAS_SYS (sigpending, int, (sigset_t *set)); +_GL_CXXALIASWARN (sigpending); + +/* If OLD_SET is not NULL, put the current set of blocked signals in *OLD_SET. + Then, if SET is not NULL, affect the current set of blocked signals by + combining it with *SET as indicated in OPERATION. + In this implementation, you are not allowed to change a signal handler + while the signal is blocked. */ +# if !@HAVE_POSIX_SIGNALBLOCKING@ +# define SIG_BLOCK 0 /* blocked_set = blocked_set | *set; */ +# define SIG_SETMASK 1 /* blocked_set = *set; */ +# define SIG_UNBLOCK 2 /* blocked_set = blocked_set & ~*set; */ +_GL_FUNCDECL_SYS (sigprocmask, int, + (int operation, const sigset_t *set, sigset_t *old_set)); +# endif +_GL_CXXALIAS_SYS (sigprocmask, int, + (int operation, const sigset_t *set, sigset_t *old_set)); +_GL_CXXALIASWARN (sigprocmask); + +/* Install the handler FUNC for signal SIG, and return the previous + handler. */ +# ifdef __cplusplus +extern "C" { +# endif +# if !GNULIB_defined_function_taking_int_returning_void_t +typedef void (*_gl_function_taking_int_returning_void_t) (int); +# define GNULIB_defined_function_taking_int_returning_void_t 1 +# endif +# ifdef __cplusplus +} +# endif +# if !@HAVE_POSIX_SIGNALBLOCKING@ +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# define signal rpl_signal +# endif +_GL_FUNCDECL_RPL (signal, _gl_function_taking_int_returning_void_t, + (int sig, _gl_function_taking_int_returning_void_t func)); +_GL_CXXALIAS_RPL (signal, _gl_function_taking_int_returning_void_t, + (int sig, _gl_function_taking_int_returning_void_t func)); +# else +_GL_CXXALIAS_SYS (signal, _gl_function_taking_int_returning_void_t, + (int sig, _gl_function_taking_int_returning_void_t func)); +# endif +_GL_CXXALIASWARN (signal); + +/* Raise signal SIG. */ +# if !@HAVE_POSIX_SIGNALBLOCKING@ && GNULIB_defined_SIGPIPE +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# undef raise +# define raise rpl_raise +# endif +_GL_FUNCDECL_RPL (raise, int, (int sig)); +_GL_CXXALIAS_RPL (raise, int, (int sig)); +# else +_GL_CXXALIAS_SYS (raise, int, (int sig)); +# endif +_GL_CXXALIASWARN (raise); + +#elif defined GNULIB_POSIXCHECK +# undef sigaddset +# if HAVE_RAW_DECL_SIGADDSET +_GL_WARN_ON_USE (sigaddset, "sigaddset is unportable - " + "use the gnulib module sigprocmask for portability"); +# endif +# undef sigdelset +# if HAVE_RAW_DECL_SIGDELSET +_GL_WARN_ON_USE (sigdelset, "sigdelset is unportable - " + "use the gnulib module sigprocmask for portability"); +# endif +# undef sigemptyset +# if HAVE_RAW_DECL_SIGEMPTYSET +_GL_WARN_ON_USE (sigemptyset, "sigemptyset is unportable - " + "use the gnulib module sigprocmask for portability"); +# endif +# undef sigfillset +# if HAVE_RAW_DECL_SIGFILLSET +_GL_WARN_ON_USE (sigfillset, "sigfillset is unportable - " + "use the gnulib module sigprocmask for portability"); +# endif +# undef sigismember +# if HAVE_RAW_DECL_SIGISMEMBER +_GL_WARN_ON_USE (sigismember, "sigismember is unportable - " + "use the gnulib module sigprocmask for portability"); +# endif +# undef sigpending +# if HAVE_RAW_DECL_SIGPENDING +_GL_WARN_ON_USE (sigpending, "sigpending is unportable - " + "use the gnulib module sigprocmask for portability"); +# endif +# undef sigprocmask +# if HAVE_RAW_DECL_SIGPROCMASK +_GL_WARN_ON_USE (sigprocmask, "sigprocmask is unportable - " + "use the gnulib module sigprocmask for portability"); +# endif +#endif /* @GNULIB_SIGPROCMASK@ */ + + +#if @GNULIB_SIGACTION@ +# if !@HAVE_SIGACTION@ + +# if !@HAVE_SIGINFO_T@ + +# if !GNULIB_defined_siginfo_types + +/* Present to allow compilation, but unsupported by gnulib. */ +union sigval +{ + int sival_int; + void *sival_ptr; +}; + +/* Present to allow compilation, but unsupported by gnulib. */ +struct siginfo_t +{ + int si_signo; + int si_code; + int si_errno; + pid_t si_pid; + uid_t si_uid; + void *si_addr; + int si_status; + long si_band; + union sigval si_value; +}; +typedef struct siginfo_t siginfo_t; + +# define GNULIB_defined_siginfo_types 1 +# endif + +# endif /* !@HAVE_SIGINFO_T@ */ + +/* We assume that platforms which lack the sigaction() function also lack + the 'struct sigaction' type, and vice versa. */ + +# if !GNULIB_defined_struct_sigaction + +struct sigaction +{ + union + { + void (*_sa_handler) (int); + /* Present to allow compilation, but unsupported by gnulib. POSIX + says that implementations may, but not must, make sa_sigaction + overlap with sa_handler, but we know of no implementation where + they do not overlap. */ + void (*_sa_sigaction) (int, siginfo_t *, void *); + } _sa_func; + sigset_t sa_mask; + /* Not all POSIX flags are supported. */ + int sa_flags; +}; +# define sa_handler _sa_func._sa_handler +# define sa_sigaction _sa_func._sa_sigaction +/* Unsupported flags are not present. */ +# define SA_RESETHAND 1 +# define SA_NODEFER 2 +# define SA_RESTART 4 + +# define GNULIB_defined_struct_sigaction 1 +# endif + +_GL_FUNCDECL_SYS (sigaction, int, (int, const struct sigaction *restrict, + struct sigaction *restrict)); + +# elif !@HAVE_STRUCT_SIGACTION_SA_SIGACTION@ + +# define sa_sigaction sa_handler + +# endif /* !@HAVE_SIGACTION@, !@HAVE_STRUCT_SIGACTION_SA_SIGACTION@ */ + +_GL_CXXALIAS_SYS (sigaction, int, (int, const struct sigaction *restrict, + struct sigaction *restrict)); +_GL_CXXALIASWARN (sigaction); + +#elif defined GNULIB_POSIXCHECK +# undef sigaction +# if HAVE_RAW_DECL_SIGACTION +_GL_WARN_ON_USE (sigaction, "sigaction is unportable - " + "use the gnulib module sigaction for portability"); +# endif +#endif + +/* Some systems don't have SA_NODEFER. */ +#ifndef SA_NODEFER +# define SA_NODEFER 0 +#endif + + +#endif /* _@GUARD_PREFIX@_SIGNAL_H */ +#endif /* _@GUARD_PREFIX@_SIGNAL_H */ +#endif diff --git a/lib/sigprocmask.c b/lib/sigprocmask.c new file mode 100644 index 0000000..6780a37 --- /dev/null +++ b/lib/sigprocmask.c @@ -0,0 +1,329 @@ +/* POSIX compatible signal blocking. + Copyright (C) 2006-2011 Free Software Foundation, Inc. + Written by Bruno Haible , 2006. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#include + +/* Specification. */ +#include + +#include +#include +#include + +/* We assume that a platform without POSIX signal blocking functions + also does not have the POSIX sigaction() function, only the + signal() function. We also assume signal() has SysV semantics, + where any handler is uninstalled prior to being invoked. This is + true for Woe32 platforms. */ + +/* We use raw signal(), but also provide a wrapper rpl_signal() so + that applications can query or change a blocked signal. */ +#undef signal + +/* Provide invalid signal numbers as fallbacks if the uncatchable + signals are not defined. */ +#ifndef SIGKILL +# define SIGKILL (-1) +#endif +#ifndef SIGSTOP +# define SIGSTOP (-1) +#endif + +/* On native Windows, as of 2008, the signal SIGABRT_COMPAT is an alias + for the signal SIGABRT. Only one signal handler is stored for both + SIGABRT and SIGABRT_COMPAT. SIGABRT_COMPAT is not a signal of its own. */ +#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__ +# undef SIGABRT_COMPAT +# define SIGABRT_COMPAT 6 +#endif +#ifdef SIGABRT_COMPAT +# define SIGABRT_COMPAT_MASK (1U << SIGABRT_COMPAT) +#else +# define SIGABRT_COMPAT_MASK 0 +#endif + +typedef void (*handler_t) (int); + +/* Handling of gnulib defined signals. */ + +#if GNULIB_defined_SIGPIPE +static handler_t SIGPIPE_handler = SIG_DFL; +#endif + +#if GNULIB_defined_SIGPIPE +static handler_t +ext_signal (int sig, handler_t handler) +{ + switch (sig) + { + case SIGPIPE: + { + handler_t old_handler = SIGPIPE_handler; + SIGPIPE_handler = handler; + return old_handler; + } + default: /* System defined signal */ + return signal (sig, handler); + } +} +# define signal ext_signal +#endif + +int +sigismember (const sigset_t *set, int sig) +{ + if (sig >= 0 && sig < NSIG) + { + #ifdef SIGABRT_COMPAT + if (sig == SIGABRT_COMPAT) + sig = SIGABRT; + #endif + + return (*set >> sig) & 1; + } + else + return 0; +} + +int +sigemptyset (sigset_t *set) +{ + *set = 0; + return 0; +} + +int +sigaddset (sigset_t *set, int sig) +{ + if (sig >= 0 && sig < NSIG) + { + #ifdef SIGABRT_COMPAT + if (sig == SIGABRT_COMPAT) + sig = SIGABRT; + #endif + + *set |= 1U << sig; + return 0; + } + else + { + errno = EINVAL; + return -1; + } +} + +int +sigdelset (sigset_t *set, int sig) +{ + if (sig >= 0 && sig < NSIG) + { + #ifdef SIGABRT_COMPAT + if (sig == SIGABRT_COMPAT) + sig = SIGABRT; + #endif + + *set &= ~(1U << sig); + return 0; + } + else + { + errno = EINVAL; + return -1; + } +} + + +int +sigfillset (sigset_t *set) +{ + *set = ((2U << (NSIG - 1)) - 1) & ~ SIGABRT_COMPAT_MASK; + return 0; +} + +/* Set of currently blocked signals. */ +static volatile sigset_t blocked_set /* = 0 */; + +/* Set of currently blocked and pending signals. */ +static volatile sig_atomic_t pending_array[NSIG] /* = { 0 } */; + +/* Signal handler that is installed for blocked signals. */ +static void +blocked_handler (int sig) +{ + /* Reinstall the handler, in case the signal occurs multiple times + while blocked. There is an inherent race where an asynchronous + signal in between when the kernel uninstalled the handler and + when we reinstall it will trigger the default handler; oh + well. */ + signal (sig, blocked_handler); + if (sig >= 0 && sig < NSIG) + pending_array[sig] = 1; +} + +int +sigpending (sigset_t *set) +{ + sigset_t pending = 0; + int sig; + + for (sig = 0; sig < NSIG; sig++) + if (pending_array[sig]) + pending |= 1U << sig; + *set = pending; + return 0; +} + +/* The previous signal handlers. + Only the array elements corresponding to blocked signals are relevant. */ +static volatile handler_t old_handlers[NSIG]; + +int +sigprocmask (int operation, const sigset_t *set, sigset_t *old_set) +{ + if (old_set != NULL) + *old_set = blocked_set; + + if (set != NULL) + { + sigset_t new_blocked_set; + sigset_t to_unblock; + sigset_t to_block; + + switch (operation) + { + case SIG_BLOCK: + new_blocked_set = blocked_set | *set; + break; + case SIG_SETMASK: + new_blocked_set = *set; + break; + case SIG_UNBLOCK: + new_blocked_set = blocked_set & ~*set; + break; + default: + errno = EINVAL; + return -1; + } + to_unblock = blocked_set & ~new_blocked_set; + to_block = new_blocked_set & ~blocked_set; + + if (to_block != 0) + { + int sig; + + for (sig = 0; sig < NSIG; sig++) + if ((to_block >> sig) & 1) + { + pending_array[sig] = 0; + if ((old_handlers[sig] = signal (sig, blocked_handler)) != SIG_ERR) + blocked_set |= 1U << sig; + } + } + + if (to_unblock != 0) + { + sig_atomic_t received[NSIG]; + int sig; + + for (sig = 0; sig < NSIG; sig++) + if ((to_unblock >> sig) & 1) + { + if (signal (sig, old_handlers[sig]) != blocked_handler) + /* The application changed a signal handler while the signal + was blocked, bypassing our rpl_signal replacement. + We don't support this. */ + abort (); + received[sig] = pending_array[sig]; + blocked_set &= ~(1U << sig); + pending_array[sig] = 0; + } + else + received[sig] = 0; + + for (sig = 0; sig < NSIG; sig++) + if (received[sig]) + raise (sig); + } + } + return 0; +} + +/* Install the handler FUNC for signal SIG, and return the previous + handler. */ +handler_t +rpl_signal (int sig, handler_t handler) +{ + /* We must provide a wrapper, so that a user can query what handler + they installed even if that signal is currently blocked. */ + if (sig >= 0 && sig < NSIG && sig != SIGKILL && sig != SIGSTOP + && handler != SIG_ERR) + { + #ifdef SIGABRT_COMPAT + if (sig == SIGABRT_COMPAT) + sig = SIGABRT; + #endif + + if (blocked_set & (1U << sig)) + { + /* POSIX states that sigprocmask and signal are both + async-signal-safe. This is not true of our + implementation - there is a slight data race where an + asynchronous interrupt on signal A can occur after we + install blocked_handler but before we have updated + old_handlers for signal B, such that handler A can see + stale information if it calls signal(B). Oh well - + signal handlers really shouldn't try to manipulate the + installed handlers of unrelated signals. */ + handler_t result = old_handlers[sig]; + old_handlers[sig] = handler; + return result; + } + else + return signal (sig, handler); + } + else + { + errno = EINVAL; + return SIG_ERR; + } +} + +#if GNULIB_defined_SIGPIPE +/* Raise the signal SIG. */ +int +rpl_raise (int sig) +# undef raise +{ + switch (sig) + { + case SIGPIPE: + if (blocked_set & (1U << sig)) + pending_array[sig] = 1; + else + { + handler_t handler = SIGPIPE_handler; + if (handler == SIG_DFL) + exit (128 + SIGPIPE); + else if (handler != SIG_IGN) + (*handler) (sig); + } + return 0; + default: /* System defined signal */ + return raise (sig); + } +} +#endif diff --git a/lib/stddef.in.h b/lib/stddef.in.h new file mode 100644 index 0000000..c7b98e7 --- /dev/null +++ b/lib/stddef.in.h @@ -0,0 +1,87 @@ +/* A substitute for POSIX 2008 , for platforms that have issues. + + Copyright (C) 2009-2011 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by Eric Blake. */ + +/* + * POSIX 2008 for platforms that have issues. + * + */ + +#if __GNUC__ >= 3 +@PRAGMA_SYSTEM_HEADER@ +#endif +@PRAGMA_COLUMNS@ + +#if defined __need_wchar_t || defined __need_size_t \ + || defined __need_ptrdiff_t || defined __need_NULL \ + || defined __need_wint_t +/* Special invocation convention inside gcc header files. In + particular, gcc provides a version of that blindly + redefines NULL even when __need_wint_t was defined, even though + wint_t is not normally provided by . Hence, we must + remember if special invocation has ever been used to obtain wint_t, + in which case we need to clean up NULL yet again. */ + +# if !(defined _@GUARD_PREFIX@_STDDEF_H && defined _GL_STDDEF_WINT_T) +# ifdef __need_wint_t +# undef _@GUARD_PREFIX@_STDDEF_H +# define _GL_STDDEF_WINT_T +# endif +# @INCLUDE_NEXT@ @NEXT_STDDEF_H@ +# endif + +#else +/* Normal invocation convention. */ + +# ifndef _@GUARD_PREFIX@_STDDEF_H + +/* The include_next requires a split double-inclusion guard. */ + +# @INCLUDE_NEXT@ @NEXT_STDDEF_H@ + +# ifndef _@GUARD_PREFIX@_STDDEF_H +# define _@GUARD_PREFIX@_STDDEF_H + +/* On NetBSD 5.0, the definition of NULL lacks proper parentheses. */ +#if @REPLACE_NULL@ +# undef NULL +# ifdef __cplusplus + /* ISO C++ says that the macro NULL must expand to an integer constant + expression, hence '((void *) 0)' is not allowed in C++. */ +# if __GNUG__ >= 3 + /* GNU C++ has a __null macro that behaves like an integer ('int' or + 'long') but has the same size as a pointer. Use that, to avoid + warnings. */ +# define NULL __null +# else +# define NULL 0L +# endif +# else +# define NULL ((void *) 0) +# endif +#endif + +/* Some platforms lack wchar_t. */ +#if !@HAVE_WCHAR_T@ +# define wchar_t int +#endif + +# endif /* _@GUARD_PREFIX@_STDDEF_H */ +# endif /* _@GUARD_PREFIX@_STDDEF_H */ +#endif /* __need_XXX */ diff --git a/lib/stdint.in.h b/lib/stdint.in.h new file mode 100644 index 0000000..09ac138 --- /dev/null +++ b/lib/stdint.in.h @@ -0,0 +1,592 @@ +/* Copyright (C) 2001-2002, 2004-2011 Free Software Foundation, Inc. + Written by Paul Eggert, Bruno Haible, Sam Steingold, Peter Burwood. + This file is part of gnulib. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* + * ISO C 99 for platforms that lack it. + * + */ + +#ifndef _@GUARD_PREFIX@_STDINT_H + +#if __GNUC__ >= 3 +@PRAGMA_SYSTEM_HEADER@ +#endif +@PRAGMA_COLUMNS@ + +/* When including a system file that in turn includes , + use the system , not our substitute. This avoids + problems with (for example) VMS, whose includes + . */ +#define _GL_JUST_INCLUDE_SYSTEM_INTTYPES_H + +/* Get those types that are already defined in other system include + files, so that we can "#define int8_t signed char" below without + worrying about a later system include file containing a "typedef + signed char int8_t;" that will get messed up by our macro. Our + macros should all be consistent with the system versions, except + for the "fast" types and macros, which we recommend against using + in public interfaces due to compiler differences. */ + +#if @HAVE_STDINT_H@ +# if defined __sgi && ! defined __c99 + /* Bypass IRIX's if in C89 mode, since it merely annoys users + with "This header file is to be used only for c99 mode compilations" + diagnostics. */ +# define __STDINT_H__ +# endif + /* Other systems may have an incomplete or buggy . + Include it before , since any "#include " + in would reinclude us, skipping our contents because + _@GUARD_PREFIX@_STDINT_H is defined. + The include_next requires a split double-inclusion guard. */ +# @INCLUDE_NEXT@ @NEXT_STDINT_H@ +#endif + +#if ! defined _@GUARD_PREFIX@_STDINT_H && ! defined _GL_JUST_INCLUDE_SYSTEM_STDINT_H +#define _@GUARD_PREFIX@_STDINT_H + +/* defines some of the stdint.h types as well, on glibc, + IRIX 6.5, and OpenBSD 3.8 (via ). + AIX 5.2 isn't needed and causes troubles. + MacOS X 10.4.6 includes (which is us), but + relies on the system definitions, so include + after @NEXT_STDINT_H@. */ +#if @HAVE_SYS_TYPES_H@ && ! defined _AIX +# include +#endif + +/* Get LONG_MIN, LONG_MAX, ULONG_MAX. */ +#include + +#if @HAVE_INTTYPES_H@ + /* In OpenBSD 3.8, includes , which defines + int{8,16,32,64}_t, uint{8,16,32,64}_t and __BIT_TYPES_DEFINED__. + also defines intptr_t and uintptr_t. */ +# include +#elif @HAVE_SYS_INTTYPES_H@ + /* Solaris 7 has the types except the *_fast*_t types, and + the macros except for *_FAST*_*, INTPTR_MIN, PTRDIFF_MIN, PTRDIFF_MAX. */ +# include +#endif + +#if @HAVE_SYS_BITYPES_H@ && ! defined __BIT_TYPES_DEFINED__ + /* Linux libc4 >= 4.6.7 and libc5 have a that defines + int{8,16,32,64}_t and __BIT_TYPES_DEFINED__. In libc5 >= 5.2.2 it is + included by . */ +# include +#endif + +#undef _GL_JUST_INCLUDE_SYSTEM_INTTYPES_H + +/* Minimum and maximum values for an integer type under the usual assumption. + Return an unspecified value if BITS == 0, adding a check to pacify + picky compilers. */ + +#define _STDINT_MIN(signed, bits, zero) \ + ((signed) ? (- ((zero) + 1) << ((bits) ? (bits) - 1 : 0)) : (zero)) + +#define _STDINT_MAX(signed, bits, zero) \ + ((signed) \ + ? ~ _STDINT_MIN (signed, bits, zero) \ + : /* The expression for the unsigned case. The subtraction of (signed) \ + is a nop in the unsigned case and avoids "signed integer overflow" \ + warnings in the signed case. */ \ + ((((zero) + 1) << ((bits) ? (bits) - 1 - (signed) : 0)) - 1) * 2 + 1) + +#if !GNULIB_defined_stdint_types + +/* 7.18.1.1. Exact-width integer types */ + +/* Here we assume a standard architecture where the hardware integer + types have 8, 16, 32, optionally 64 bits. */ + +#undef int8_t +#undef uint8_t +typedef signed char gl_int8_t; +typedef unsigned char gl_uint8_t; +#define int8_t gl_int8_t +#define uint8_t gl_uint8_t + +#undef int16_t +#undef uint16_t +typedef short int gl_int16_t; +typedef unsigned short int gl_uint16_t; +#define int16_t gl_int16_t +#define uint16_t gl_uint16_t + +#undef int32_t +#undef uint32_t +typedef int gl_int32_t; +typedef unsigned int gl_uint32_t; +#define int32_t gl_int32_t +#define uint32_t gl_uint32_t + +/* If the system defines INT64_MAX, assume int64_t works. That way, + if the underlying platform defines int64_t to be a 64-bit long long + int, the code below won't mistakenly define it to be a 64-bit long + int, which would mess up C++ name mangling. We must use #ifdef + rather than #if, to avoid an error with HP-UX 10.20 cc. */ + +#ifdef INT64_MAX +# define GL_INT64_T +#else +/* Do not undefine int64_t if gnulib is not being used with 64-bit + types, since otherwise it breaks platforms like Tandem/NSK. */ +# if LONG_MAX >> 31 >> 31 == 1 +# undef int64_t +typedef long int gl_int64_t; +# define int64_t gl_int64_t +# define GL_INT64_T +# elif defined _MSC_VER +# undef int64_t +typedef __int64 gl_int64_t; +# define int64_t gl_int64_t +# define GL_INT64_T +# elif @HAVE_LONG_LONG_INT@ +# undef int64_t +typedef long long int gl_int64_t; +# define int64_t gl_int64_t +# define GL_INT64_T +# endif +#endif + +#ifdef UINT64_MAX +# define GL_UINT64_T +#else +# if ULONG_MAX >> 31 >> 31 >> 1 == 1 +# undef uint64_t +typedef unsigned long int gl_uint64_t; +# define uint64_t gl_uint64_t +# define GL_UINT64_T +# elif defined _MSC_VER +# undef uint64_t +typedef unsigned __int64 gl_uint64_t; +# define uint64_t gl_uint64_t +# define GL_UINT64_T +# elif @HAVE_UNSIGNED_LONG_LONG_INT@ +# undef uint64_t +typedef unsigned long long int gl_uint64_t; +# define uint64_t gl_uint64_t +# define GL_UINT64_T +# endif +#endif + +/* Avoid collision with Solaris 2.5.1 etc. */ +#define _UINT8_T +#define _UINT32_T +#define _UINT64_T + + +/* 7.18.1.2. Minimum-width integer types */ + +/* Here we assume a standard architecture where the hardware integer + types have 8, 16, 32, optionally 64 bits. Therefore the leastN_t types + are the same as the corresponding N_t types. */ + +#undef int_least8_t +#undef uint_least8_t +#undef int_least16_t +#undef uint_least16_t +#undef int_least32_t +#undef uint_least32_t +#undef int_least64_t +#undef uint_least64_t +#define int_least8_t int8_t +#define uint_least8_t uint8_t +#define int_least16_t int16_t +#define uint_least16_t uint16_t +#define int_least32_t int32_t +#define uint_least32_t uint32_t +#ifdef GL_INT64_T +# define int_least64_t int64_t +#endif +#ifdef GL_UINT64_T +# define uint_least64_t uint64_t +#endif + +/* 7.18.1.3. Fastest minimum-width integer types */ + +/* Note: Other substitutes may define these types differently. + It is not recommended to use these types in public header files. */ + +/* Here we assume a standard architecture where the hardware integer + types have 8, 16, 32, optionally 64 bits. Therefore the fastN_t types + are taken from the same list of types. Assume that 'long int' + is fast enough for all narrower integers. */ + +#undef int_fast8_t +#undef uint_fast8_t +#undef int_fast16_t +#undef uint_fast16_t +#undef int_fast32_t +#undef uint_fast32_t +#undef int_fast64_t +#undef uint_fast64_t +typedef long int gl_int_fast8_t; +typedef unsigned long int gl_uint_fast8_t; +typedef long int gl_int_fast16_t; +typedef unsigned long int gl_uint_fast16_t; +typedef long int gl_int_fast32_t; +typedef unsigned long int gl_uint_fast32_t; +#define int_fast8_t gl_int_fast8_t +#define uint_fast8_t gl_uint_fast8_t +#define int_fast16_t gl_int_fast16_t +#define uint_fast16_t gl_uint_fast16_t +#define int_fast32_t gl_int_fast32_t +#define uint_fast32_t gl_uint_fast32_t +#ifdef GL_INT64_T +# define int_fast64_t int64_t +#endif +#ifdef GL_UINT64_T +# define uint_fast64_t uint64_t +#endif + +/* 7.18.1.4. Integer types capable of holding object pointers */ + +#undef intptr_t +#undef uintptr_t +typedef long int gl_intptr_t; +typedef unsigned long int gl_uintptr_t; +#define intptr_t gl_intptr_t +#define uintptr_t gl_uintptr_t + +/* 7.18.1.5. Greatest-width integer types */ + +/* Note: These types are compiler dependent. It may be unwise to use them in + public header files. */ + +#undef intmax_t +#if @HAVE_LONG_LONG_INT@ && LONG_MAX >> 30 == 1 +typedef long long int gl_intmax_t; +# define intmax_t gl_intmax_t +#elif defined GL_INT64_T +# define intmax_t int64_t +#else +typedef long int gl_intmax_t; +# define intmax_t gl_intmax_t +#endif + +#undef uintmax_t +#if @HAVE_UNSIGNED_LONG_LONG_INT@ && ULONG_MAX >> 31 == 1 +typedef unsigned long long int gl_uintmax_t; +# define uintmax_t gl_uintmax_t +#elif defined GL_UINT64_T +# define uintmax_t uint64_t +#else +typedef unsigned long int gl_uintmax_t; +# define uintmax_t gl_uintmax_t +#endif + +/* Verify that intmax_t and uintmax_t have the same size. Too much code + breaks if this is not the case. If this check fails, the reason is likely + to be found in the autoconf macros. */ +typedef int _verify_intmax_size[sizeof (intmax_t) == sizeof (uintmax_t) + ? 1 : -1]; + +#define GNULIB_defined_stdint_types 1 +#endif /* !GNULIB_defined_stdint_types */ + +/* 7.18.2. Limits of specified-width integer types */ + +#if ! defined __cplusplus || defined __STDC_LIMIT_MACROS + +/* 7.18.2.1. Limits of exact-width integer types */ + +/* Here we assume a standard architecture where the hardware integer + types have 8, 16, 32, optionally 64 bits. */ + +#undef INT8_MIN +#undef INT8_MAX +#undef UINT8_MAX +#define INT8_MIN (~ INT8_MAX) +#define INT8_MAX 127 +#define UINT8_MAX 255 + +#undef INT16_MIN +#undef INT16_MAX +#undef UINT16_MAX +#define INT16_MIN (~ INT16_MAX) +#define INT16_MAX 32767 +#define UINT16_MAX 65535 + +#undef INT32_MIN +#undef INT32_MAX +#undef UINT32_MAX +#define INT32_MIN (~ INT32_MAX) +#define INT32_MAX 2147483647 +#define UINT32_MAX 4294967295U + +#if defined GL_INT64_T && ! defined INT64_MAX +/* Prefer (- INTMAX_C (1) << 63) over (~ INT64_MAX) because SunPRO C 5.0 + evaluates the latter incorrectly in preprocessor expressions. */ +# define INT64_MIN (- INTMAX_C (1) << 63) +# define INT64_MAX INTMAX_C (9223372036854775807) +#endif + +#if defined GL_UINT64_T && ! defined UINT64_MAX +# define UINT64_MAX UINTMAX_C (18446744073709551615) +#endif + +/* 7.18.2.2. Limits of minimum-width integer types */ + +/* Here we assume a standard architecture where the hardware integer + types have 8, 16, 32, optionally 64 bits. Therefore the leastN_t types + are the same as the corresponding N_t types. */ + +#undef INT_LEAST8_MIN +#undef INT_LEAST8_MAX +#undef UINT_LEAST8_MAX +#define INT_LEAST8_MIN INT8_MIN +#define INT_LEAST8_MAX INT8_MAX +#define UINT_LEAST8_MAX UINT8_MAX + +#undef INT_LEAST16_MIN +#undef INT_LEAST16_MAX +#undef UINT_LEAST16_MAX +#define INT_LEAST16_MIN INT16_MIN +#define INT_LEAST16_MAX INT16_MAX +#define UINT_LEAST16_MAX UINT16_MAX + +#undef INT_LEAST32_MIN +#undef INT_LEAST32_MAX +#undef UINT_LEAST32_MAX +#define INT_LEAST32_MIN INT32_MIN +#define INT_LEAST32_MAX INT32_MAX +#define UINT_LEAST32_MAX UINT32_MAX + +#undef INT_LEAST64_MIN +#undef INT_LEAST64_MAX +#ifdef GL_INT64_T +# define INT_LEAST64_MIN INT64_MIN +# define INT_LEAST64_MAX INT64_MAX +#endif + +#undef UINT_LEAST64_MAX +#ifdef GL_UINT64_T +# define UINT_LEAST64_MAX UINT64_MAX +#endif + +/* 7.18.2.3. Limits of fastest minimum-width integer types */ + +/* Here we assume a standard architecture where the hardware integer + types have 8, 16, 32, optionally 64 bits. Therefore the fastN_t types + are taken from the same list of types. */ + +#undef INT_FAST8_MIN +#undef INT_FAST8_MAX +#undef UINT_FAST8_MAX +#define INT_FAST8_MIN LONG_MIN +#define INT_FAST8_MAX LONG_MAX +#define UINT_FAST8_MAX ULONG_MAX + +#undef INT_FAST16_MIN +#undef INT_FAST16_MAX +#undef UINT_FAST16_MAX +#define INT_FAST16_MIN LONG_MIN +#define INT_FAST16_MAX LONG_MAX +#define UINT_FAST16_MAX ULONG_MAX + +#undef INT_FAST32_MIN +#undef INT_FAST32_MAX +#undef UINT_FAST32_MAX +#define INT_FAST32_MIN LONG_MIN +#define INT_FAST32_MAX LONG_MAX +#define UINT_FAST32_MAX ULONG_MAX + +#undef INT_FAST64_MIN +#undef INT_FAST64_MAX +#ifdef GL_INT64_T +# define INT_FAST64_MIN INT64_MIN +# define INT_FAST64_MAX INT64_MAX +#endif + +#undef UINT_FAST64_MAX +#ifdef GL_UINT64_T +# define UINT_FAST64_MAX UINT64_MAX +#endif + +/* 7.18.2.4. Limits of integer types capable of holding object pointers */ + +#undef INTPTR_MIN +#undef INTPTR_MAX +#undef UINTPTR_MAX +#define INTPTR_MIN LONG_MIN +#define INTPTR_MAX LONG_MAX +#define UINTPTR_MAX ULONG_MAX + +/* 7.18.2.5. Limits of greatest-width integer types */ + +#undef INTMAX_MIN +#undef INTMAX_MAX +#ifdef INT64_MAX +# define INTMAX_MIN INT64_MIN +# define INTMAX_MAX INT64_MAX +#else +# define INTMAX_MIN INT32_MIN +# define INTMAX_MAX INT32_MAX +#endif + +#undef UINTMAX_MAX +#ifdef UINT64_MAX +# define UINTMAX_MAX UINT64_MAX +#else +# define UINTMAX_MAX UINT32_MAX +#endif + +/* 7.18.3. Limits of other integer types */ + +/* ptrdiff_t limits */ +#undef PTRDIFF_MIN +#undef PTRDIFF_MAX +#if @APPLE_UNIVERSAL_BUILD@ +# ifdef _LP64 +# define PTRDIFF_MIN _STDINT_MIN (1, 64, 0l) +# define PTRDIFF_MAX _STDINT_MAX (1, 64, 0l) +# else +# define PTRDIFF_MIN _STDINT_MIN (1, 32, 0) +# define PTRDIFF_MAX _STDINT_MAX (1, 32, 0) +# endif +#else +# define PTRDIFF_MIN \ + _STDINT_MIN (1, @BITSIZEOF_PTRDIFF_T@, 0@PTRDIFF_T_SUFFIX@) +# define PTRDIFF_MAX \ + _STDINT_MAX (1, @BITSIZEOF_PTRDIFF_T@, 0@PTRDIFF_T_SUFFIX@) +#endif + +/* sig_atomic_t limits */ +#undef SIG_ATOMIC_MIN +#undef SIG_ATOMIC_MAX +#define SIG_ATOMIC_MIN \ + _STDINT_MIN (@HAVE_SIGNED_SIG_ATOMIC_T@, @BITSIZEOF_SIG_ATOMIC_T@, \ + 0@SIG_ATOMIC_T_SUFFIX@) +#define SIG_ATOMIC_MAX \ + _STDINT_MAX (@HAVE_SIGNED_SIG_ATOMIC_T@, @BITSIZEOF_SIG_ATOMIC_T@, \ + 0@SIG_ATOMIC_T_SUFFIX@) + + +/* size_t limit */ +#undef SIZE_MAX +#if @APPLE_UNIVERSAL_BUILD@ +# ifdef _LP64 +# define SIZE_MAX _STDINT_MAX (0, 64, 0ul) +# else +# define SIZE_MAX _STDINT_MAX (0, 32, 0ul) +# endif +#else +# define SIZE_MAX _STDINT_MAX (0, @BITSIZEOF_SIZE_T@, 0@SIZE_T_SUFFIX@) +#endif + +/* wchar_t limits */ +/* Get WCHAR_MIN, WCHAR_MAX. + This include is not on the top, above, because on OSF/1 4.0 we have a + sequence of nested includes + -> -> -> , and the latter includes + and assumes its types are already defined. */ +#if @HAVE_WCHAR_H@ && ! (defined WCHAR_MIN && defined WCHAR_MAX) + /* BSD/OS 4.0.1 has a bug: , and must be + included before . */ +# include +# include +# include +# define _GL_JUST_INCLUDE_SYSTEM_WCHAR_H +# include +# undef _GL_JUST_INCLUDE_SYSTEM_WCHAR_H +#endif +#undef WCHAR_MIN +#undef WCHAR_MAX +#define WCHAR_MIN \ + _STDINT_MIN (@HAVE_SIGNED_WCHAR_T@, @BITSIZEOF_WCHAR_T@, 0@WCHAR_T_SUFFIX@) +#define WCHAR_MAX \ + _STDINT_MAX (@HAVE_SIGNED_WCHAR_T@, @BITSIZEOF_WCHAR_T@, 0@WCHAR_T_SUFFIX@) + +/* wint_t limits */ +#undef WINT_MIN +#undef WINT_MAX +#define WINT_MIN \ + _STDINT_MIN (@HAVE_SIGNED_WINT_T@, @BITSIZEOF_WINT_T@, 0@WINT_T_SUFFIX@) +#define WINT_MAX \ + _STDINT_MAX (@HAVE_SIGNED_WINT_T@, @BITSIZEOF_WINT_T@, 0@WINT_T_SUFFIX@) + +#endif /* !defined __cplusplus || defined __STDC_LIMIT_MACROS */ + +/* 7.18.4. Macros for integer constants */ + +#if ! defined __cplusplus || defined __STDC_CONSTANT_MACROS + +/* 7.18.4.1. Macros for minimum-width integer constants */ +/* According to ISO C 99 Technical Corrigendum 1 */ + +/* Here we assume a standard architecture where the hardware integer + types have 8, 16, 32, optionally 64 bits, and int is 32 bits. */ + +#undef INT8_C +#undef UINT8_C +#define INT8_C(x) x +#define UINT8_C(x) x + +#undef INT16_C +#undef UINT16_C +#define INT16_C(x) x +#define UINT16_C(x) x + +#undef INT32_C +#undef UINT32_C +#define INT32_C(x) x +#define UINT32_C(x) x ## U + +#undef INT64_C +#undef UINT64_C +#if LONG_MAX >> 31 >> 31 == 1 +# define INT64_C(x) x##L +#elif defined _MSC_VER +# define INT64_C(x) x##i64 +#elif @HAVE_LONG_LONG_INT@ +# define INT64_C(x) x##LL +#endif +#if ULONG_MAX >> 31 >> 31 >> 1 == 1 +# define UINT64_C(x) x##UL +#elif defined _MSC_VER +# define UINT64_C(x) x##ui64 +#elif @HAVE_UNSIGNED_LONG_LONG_INT@ +# define UINT64_C(x) x##ULL +#endif + +/* 7.18.4.2. Macros for greatest-width integer constants */ + +#undef INTMAX_C +#if @HAVE_LONG_LONG_INT@ && LONG_MAX >> 30 == 1 +# define INTMAX_C(x) x##LL +#elif defined GL_INT64_T +# define INTMAX_C(x) INT64_C(x) +#else +# define INTMAX_C(x) x##L +#endif + +#undef UINTMAX_C +#if @HAVE_UNSIGNED_LONG_LONG_INT@ && ULONG_MAX >> 31 == 1 +# define UINTMAX_C(x) x##ULL +#elif defined GL_UINT64_T +# define UINTMAX_C(x) UINT64_C(x) +#else +# define UINTMAX_C(x) x##UL +#endif + +#endif /* !defined __cplusplus || defined __STDC_CONSTANT_MACROS */ + +#endif /* _@GUARD_PREFIX@_STDINT_H */ +#endif /* !defined _@GUARD_PREFIX@_STDINT_H && !defined _GL_JUST_INCLUDE_SYSTEM_STDINT_H */ diff --git a/lib/str-two-way.h b/lib/str-two-way.h new file mode 100644 index 0000000..08a6cd3 --- /dev/null +++ b/lib/str-two-way.h @@ -0,0 +1,453 @@ +/* Byte-wise substring search, using the Two-Way algorithm. + Copyright (C) 2008-2011 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Written by Eric Blake , 2008. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Before including this file, you need to include and + , and define: + RESULT_TYPE A macro that expands to the return type. + AVAILABLE(h, h_l, j, n_l) + A macro that returns nonzero if there are + at least N_L bytes left starting at H[J]. + H is 'unsigned char *', H_L, J, and N_L + are 'size_t'; H_L is an lvalue. For + NUL-terminated searches, H_L can be + modified each iteration to avoid having + to compute the end of H up front. + + For case-insensitivity, you may optionally define: + CMP_FUNC(p1, p2, l) A macro that returns 0 iff the first L + characters of P1 and P2 are equal. + CANON_ELEMENT(c) A macro that canonicalizes an element right after + it has been fetched from one of the two strings. + The argument is an 'unsigned char'; the result + must be an 'unsigned char' as well. + + This file undefines the macros documented above, and defines + LONG_NEEDLE_THRESHOLD. +*/ + +#include +#include + +/* We use the Two-Way string matching algorithm (also known as + Chrochemore-Perrin), which guarantees linear complexity with + constant space. Additionally, for long needles, we also use a bad + character shift table similar to the Boyer-Moore algorithm to + achieve improved (potentially sub-linear) performance. + + See http://www-igm.univ-mlv.fr/~lecroq/string/node26.html#SECTION00260, + http://en.wikipedia.org/wiki/Boyer-Moore_string_search_algorithm, + http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.34.6641&rep=rep1&type=pdf +*/ + +/* Point at which computing a bad-byte shift table is likely to be + worthwhile. Small needles should not compute a table, since it + adds (1 << CHAR_BIT) + NEEDLE_LEN computations of preparation for a + speedup no greater than a factor of NEEDLE_LEN. The larger the + needle, the better the potential performance gain. On the other + hand, on non-POSIX systems with CHAR_BIT larger than eight, the + memory required for the table is prohibitive. */ +#if CHAR_BIT < 10 +# define LONG_NEEDLE_THRESHOLD 32U +#else +# define LONG_NEEDLE_THRESHOLD SIZE_MAX +#endif + +#ifndef MAX +# define MAX(a, b) ((a < b) ? (b) : (a)) +#endif + +#ifndef CANON_ELEMENT +# define CANON_ELEMENT(c) c +#endif +#ifndef CMP_FUNC +# define CMP_FUNC memcmp +#endif + +/* Perform a critical factorization of NEEDLE, of length NEEDLE_LEN. + Return the index of the first byte in the right half, and set + *PERIOD to the global period of the right half. + + The global period of a string is the smallest index (possibly its + length) at which all remaining bytes in the string are repetitions + of the prefix (the last repetition may be a subset of the prefix). + + When NEEDLE is factored into two halves, a local period is the + length of the smallest word that shares a suffix with the left half + and shares a prefix with the right half. All factorizations of a + non-empty NEEDLE have a local period of at least 1 and no greater + than NEEDLE_LEN. + + A critical factorization has the property that the local period + equals the global period. All strings have at least one critical + factorization with the left half smaller than the global period. + And while some strings have more than one critical factorization, + it is provable that with an ordered alphabet, at least one of the + critical factorizations corresponds to a maximal suffix. + + Given an ordered alphabet, a critical factorization can be computed + in linear time, with 2 * NEEDLE_LEN comparisons, by computing the + shorter of two ordered maximal suffixes. The ordered maximal + suffixes are determined by lexicographic comparison while tracking + periodicity. */ +static size_t +critical_factorization (const unsigned char *needle, size_t needle_len, + size_t *period) +{ + /* Index of last byte of left half, or SIZE_MAX. */ + size_t max_suffix, max_suffix_rev; + size_t j; /* Index into NEEDLE for current candidate suffix. */ + size_t k; /* Offset into current period. */ + size_t p; /* Intermediate period. */ + unsigned char a, b; /* Current comparison bytes. */ + + /* Special case NEEDLE_LEN of 1 or 2 (all callers already filtered + out 0-length needles. */ + if (needle_len < 3) + { + *period = 1; + return needle_len - 1; + } + + /* Invariants: + 0 <= j < NEEDLE_LEN - 1 + -1 <= max_suffix{,_rev} < j (treating SIZE_MAX as if it were signed) + min(max_suffix, max_suffix_rev) < global period of NEEDLE + 1 <= p <= global period of NEEDLE + p == global period of the substring NEEDLE[max_suffix{,_rev}+1...j] + 1 <= k <= p + */ + + /* Perform lexicographic search. */ + max_suffix = SIZE_MAX; + j = 0; + k = p = 1; + while (j + k < needle_len) + { + a = CANON_ELEMENT (needle[j + k]); + b = CANON_ELEMENT (needle[max_suffix + k]); + if (a < b) + { + /* Suffix is smaller, period is entire prefix so far. */ + j += k; + k = 1; + p = j - max_suffix; + } + else if (a == b) + { + /* Advance through repetition of the current period. */ + if (k != p) + ++k; + else + { + j += p; + k = 1; + } + } + else /* b < a */ + { + /* Suffix is larger, start over from current location. */ + max_suffix = j++; + k = p = 1; + } + } + *period = p; + + /* Perform reverse lexicographic search. */ + max_suffix_rev = SIZE_MAX; + j = 0; + k = p = 1; + while (j + k < needle_len) + { + a = CANON_ELEMENT (needle[j + k]); + b = CANON_ELEMENT (needle[max_suffix_rev + k]); + if (b < a) + { + /* Suffix is smaller, period is entire prefix so far. */ + j += k; + k = 1; + p = j - max_suffix_rev; + } + else if (a == b) + { + /* Advance through repetition of the current period. */ + if (k != p) + ++k; + else + { + j += p; + k = 1; + } + } + else /* a < b */ + { + /* Suffix is larger, start over from current location. */ + max_suffix_rev = j++; + k = p = 1; + } + } + + /* Choose the shorter suffix. Return the index of the first byte of + the right half, rather than the last byte of the left half. + + For some examples, 'banana' has two critical factorizations, both + exposed by the two lexicographic extreme suffixes of 'anana' and + 'nana', where both suffixes have a period of 2. On the other + hand, with 'aab' and 'bba', both strings have a single critical + factorization of the last byte, with the suffix having a period + of 1. While the maximal lexicographic suffix of 'aab' is 'b', + the maximal lexicographic suffix of 'bba' is 'ba', which is not a + critical factorization. Conversely, the maximal reverse + lexicographic suffix of 'a' works for 'bba', but not 'ab' for + 'aab'. The shorter suffix of the two will always be a critical + factorization. */ + if (max_suffix_rev + 1 < max_suffix + 1) + return max_suffix + 1; + *period = p; + return max_suffix_rev + 1; +} + +/* Return the first location of non-empty NEEDLE within HAYSTACK, or + NULL. HAYSTACK_LEN is the minimum known length of HAYSTACK. This + method is optimized for NEEDLE_LEN < LONG_NEEDLE_THRESHOLD. + Performance is guaranteed to be linear, with an initialization cost + of 2 * NEEDLE_LEN comparisons. + + If AVAILABLE does not modify HAYSTACK_LEN (as in memmem), then at + most 2 * HAYSTACK_LEN - NEEDLE_LEN comparisons occur in searching. + If AVAILABLE modifies HAYSTACK_LEN (as in strstr), then at most 3 * + HAYSTACK_LEN - NEEDLE_LEN comparisons occur in searching. */ +static RETURN_TYPE +two_way_short_needle (const unsigned char *haystack, size_t haystack_len, + const unsigned char *needle, size_t needle_len) +{ + size_t i; /* Index into current byte of NEEDLE. */ + size_t j; /* Index into current window of HAYSTACK. */ + size_t period; /* The period of the right half of needle. */ + size_t suffix; /* The index of the right half of needle. */ + + /* Factor the needle into two halves, such that the left half is + smaller than the global period, and the right half is + periodic (with a period as large as NEEDLE_LEN - suffix). */ + suffix = critical_factorization (needle, needle_len, &period); + + /* Perform the search. Each iteration compares the right half + first. */ + if (CMP_FUNC (needle, needle + period, suffix) == 0) + { + /* Entire needle is periodic; a mismatch in the left half can + only advance by the period, so use memory to avoid rescanning + known occurrences of the period in the right half. */ + size_t memory = 0; + j = 0; + while (AVAILABLE (haystack, haystack_len, j, needle_len)) + { + /* Scan for matches in right half. */ + i = MAX (suffix, memory); + while (i < needle_len && (CANON_ELEMENT (needle[i]) + == CANON_ELEMENT (haystack[i + j]))) + ++i; + if (needle_len <= i) + { + /* Scan for matches in left half. */ + i = suffix - 1; + while (memory < i + 1 && (CANON_ELEMENT (needle[i]) + == CANON_ELEMENT (haystack[i + j]))) + --i; + if (i + 1 < memory + 1) + return (RETURN_TYPE) (haystack + j); + /* No match, so remember how many repetitions of period + on the right half were scanned. */ + j += period; + memory = needle_len - period; + } + else + { + j += i - suffix + 1; + memory = 0; + } + } + } + else + { + /* The two halves of needle are distinct; no extra memory is + required, and any mismatch results in a maximal shift. */ + period = MAX (suffix, needle_len - suffix) + 1; + j = 0; + while (AVAILABLE (haystack, haystack_len, j, needle_len)) + { + /* Scan for matches in right half. */ + i = suffix; + while (i < needle_len && (CANON_ELEMENT (needle[i]) + == CANON_ELEMENT (haystack[i + j]))) + ++i; + if (needle_len <= i) + { + /* Scan for matches in left half. */ + i = suffix - 1; + while (i != SIZE_MAX && (CANON_ELEMENT (needle[i]) + == CANON_ELEMENT (haystack[i + j]))) + --i; + if (i == SIZE_MAX) + return (RETURN_TYPE) (haystack + j); + j += period; + } + else + j += i - suffix + 1; + } + } + return NULL; +} + +/* Return the first location of non-empty NEEDLE within HAYSTACK, or + NULL. HAYSTACK_LEN is the minimum known length of HAYSTACK. This + method is optimized for LONG_NEEDLE_THRESHOLD <= NEEDLE_LEN. + Performance is guaranteed to be linear, with an initialization cost + of 3 * NEEDLE_LEN + (1 << CHAR_BIT) operations. + + If AVAILABLE does not modify HAYSTACK_LEN (as in memmem), then at + most 2 * HAYSTACK_LEN - NEEDLE_LEN comparisons occur in searching, + and sublinear performance O(HAYSTACK_LEN / NEEDLE_LEN) is possible. + If AVAILABLE modifies HAYSTACK_LEN (as in strstr), then at most 3 * + HAYSTACK_LEN - NEEDLE_LEN comparisons occur in searching, and + sublinear performance is not possible. */ +static RETURN_TYPE +two_way_long_needle (const unsigned char *haystack, size_t haystack_len, + const unsigned char *needle, size_t needle_len) +{ + size_t i; /* Index into current byte of NEEDLE. */ + size_t j; /* Index into current window of HAYSTACK. */ + size_t period; /* The period of the right half of needle. */ + size_t suffix; /* The index of the right half of needle. */ + size_t shift_table[1U << CHAR_BIT]; /* See below. */ + + /* Factor the needle into two halves, such that the left half is + smaller than the global period, and the right half is + periodic (with a period as large as NEEDLE_LEN - suffix). */ + suffix = critical_factorization (needle, needle_len, &period); + + /* Populate shift_table. For each possible byte value c, + shift_table[c] is the distance from the last occurrence of c to + the end of NEEDLE, or NEEDLE_LEN if c is absent from the NEEDLE. + shift_table[NEEDLE[NEEDLE_LEN - 1]] contains the only 0. */ + for (i = 0; i < 1U << CHAR_BIT; i++) + shift_table[i] = needle_len; + for (i = 0; i < needle_len; i++) + shift_table[CANON_ELEMENT (needle[i])] = needle_len - i - 1; + + /* Perform the search. Each iteration compares the right half + first. */ + if (CMP_FUNC (needle, needle + period, suffix) == 0) + { + /* Entire needle is periodic; a mismatch in the left half can + only advance by the period, so use memory to avoid rescanning + known occurrences of the period in the right half. */ + size_t memory = 0; + size_t shift; + j = 0; + while (AVAILABLE (haystack, haystack_len, j, needle_len)) + { + /* Check the last byte first; if it does not match, then + shift to the next possible match location. */ + shift = shift_table[CANON_ELEMENT (haystack[j + needle_len - 1])]; + if (0 < shift) + { + if (memory && shift < period) + { + /* Since needle is periodic, but the last period has + a byte out of place, there can be no match until + after the mismatch. */ + shift = needle_len - period; + } + memory = 0; + j += shift; + continue; + } + /* Scan for matches in right half. The last byte has + already been matched, by virtue of the shift table. */ + i = MAX (suffix, memory); + while (i < needle_len - 1 && (CANON_ELEMENT (needle[i]) + == CANON_ELEMENT (haystack[i + j]))) + ++i; + if (needle_len - 1 <= i) + { + /* Scan for matches in left half. */ + i = suffix - 1; + while (memory < i + 1 && (CANON_ELEMENT (needle[i]) + == CANON_ELEMENT (haystack[i + j]))) + --i; + if (i + 1 < memory + 1) + return (RETURN_TYPE) (haystack + j); + /* No match, so remember how many repetitions of period + on the right half were scanned. */ + j += period; + memory = needle_len - period; + } + else + { + j += i - suffix + 1; + memory = 0; + } + } + } + else + { + /* The two halves of needle are distinct; no extra memory is + required, and any mismatch results in a maximal shift. */ + size_t shift; + period = MAX (suffix, needle_len - suffix) + 1; + j = 0; + while (AVAILABLE (haystack, haystack_len, j, needle_len)) + { + /* Check the last byte first; if it does not match, then + shift to the next possible match location. */ + shift = shift_table[CANON_ELEMENT (haystack[j + needle_len - 1])]; + if (0 < shift) + { + j += shift; + continue; + } + /* Scan for matches in right half. The last byte has + already been matched, by virtue of the shift table. */ + i = suffix; + while (i < needle_len - 1 && (CANON_ELEMENT (needle[i]) + == CANON_ELEMENT (haystack[i + j]))) + ++i; + if (needle_len - 1 <= i) + { + /* Scan for matches in left half. */ + i = suffix - 1; + while (i != SIZE_MAX && (CANON_ELEMENT (needle[i]) + == CANON_ELEMENT (haystack[i + j]))) + --i; + if (i == SIZE_MAX) + return (RETURN_TYPE) (haystack + j); + j += period; + } + else + j += i - suffix + 1; + } + } + return NULL; +} + +#undef AVAILABLE +#undef CANON_ELEMENT +#undef CMP_FUNC +#undef MAX +#undef RETURN_TYPE diff --git a/lib/string.in.h b/lib/string.in.h new file mode 100644 index 0000000..d9c95a4 --- /dev/null +++ b/lib/string.in.h @@ -0,0 +1,981 @@ +/* A GNU-like . + + Copyright (C) 1995-1996, 2001-2011 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#ifndef _@GUARD_PREFIX@_STRING_H + +#if __GNUC__ >= 3 +@PRAGMA_SYSTEM_HEADER@ +#endif +@PRAGMA_COLUMNS@ + +/* The include_next requires a split double-inclusion guard. */ +#@INCLUDE_NEXT@ @NEXT_STRING_H@ + +#ifndef _@GUARD_PREFIX@_STRING_H +#define _@GUARD_PREFIX@_STRING_H + +/* NetBSD 5.0 mis-defines NULL. */ +#include + +/* MirBSD defines mbslen as a macro. */ +#if @GNULIB_MBSLEN@ && defined __MirBSD__ +# include +#endif + +/* The __attribute__ feature is available in gcc versions 2.5 and later. + The attribute __pure__ was added in gcc 2.96. */ +#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 96) +# define _GL_ATTRIBUTE_PURE __attribute__ ((__pure__)) +#else +# define _GL_ATTRIBUTE_PURE /* empty */ +#endif + +/* NetBSD 5.0 declares strsignal in , not in . */ +/* But in any case avoid namespace pollution on glibc systems. */ +#if (@GNULIB_STRSIGNAL@ || defined GNULIB_POSIXCHECK) && defined __NetBSD__ \ + && ! defined __GLIBC__ +# include +#endif + +/* The definitions of _GL_FUNCDECL_RPL etc. are copied here. */ + +/* The definition of _GL_ARG_NONNULL is copied here. */ + +/* The definition of _GL_WARN_ON_USE is copied here. */ + + +/* Return the first instance of C within N bytes of S, or NULL. */ +#if @GNULIB_MEMCHR@ +# if @REPLACE_MEMCHR@ +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# define memchr rpl_memchr +# endif +_GL_FUNCDECL_RPL (memchr, void *, (void const *__s, int __c, size_t __n) + _GL_ATTRIBUTE_PURE + _GL_ARG_NONNULL ((1))); +_GL_CXXALIAS_RPL (memchr, void *, (void const *__s, int __c, size_t __n)); +# else +# if ! @HAVE_MEMCHR@ +_GL_FUNCDECL_SYS (memchr, void *, (void const *__s, int __c, size_t __n) + _GL_ATTRIBUTE_PURE + _GL_ARG_NONNULL ((1))); +# endif + /* On some systems, this function is defined as an overloaded function: + extern "C" { const void * std::memchr (const void *, int, size_t); } + extern "C++" { void * std::memchr (void *, int, size_t); } */ +_GL_CXXALIAS_SYS_CAST2 (memchr, + void *, (void const *__s, int __c, size_t __n), + void const *, (void const *__s, int __c, size_t __n)); +# endif +# if ((__GLIBC__ == 2 && __GLIBC_MINOR__ >= 10) && !defined __UCLIBC__) \ + && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4)) +_GL_CXXALIASWARN1 (memchr, void *, (void *__s, int __c, size_t __n)); +_GL_CXXALIASWARN1 (memchr, void const *, + (void const *__s, int __c, size_t __n)); +# else +_GL_CXXALIASWARN (memchr); +# endif +#elif defined GNULIB_POSIXCHECK +# undef memchr +/* Assume memchr is always declared. */ +_GL_WARN_ON_USE (memchr, "memchr has platform-specific bugs - " + "use gnulib module memchr for portability" ); +#endif + +/* Return the first occurrence of NEEDLE in HAYSTACK. */ +#if @GNULIB_MEMMEM@ +# if @REPLACE_MEMMEM@ +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# define memmem rpl_memmem +# endif +_GL_FUNCDECL_RPL (memmem, void *, + (void const *__haystack, size_t __haystack_len, + void const *__needle, size_t __needle_len) + _GL_ATTRIBUTE_PURE + _GL_ARG_NONNULL ((1, 3))); +_GL_CXXALIAS_RPL (memmem, void *, + (void const *__haystack, size_t __haystack_len, + void const *__needle, size_t __needle_len)); +# else +# if ! @HAVE_DECL_MEMMEM@ +_GL_FUNCDECL_SYS (memmem, void *, + (void const *__haystack, size_t __haystack_len, + void const *__needle, size_t __needle_len) + _GL_ATTRIBUTE_PURE + _GL_ARG_NONNULL ((1, 3))); +# endif +_GL_CXXALIAS_SYS (memmem, void *, + (void const *__haystack, size_t __haystack_len, + void const *__needle, size_t __needle_len)); +# endif +_GL_CXXALIASWARN (memmem); +#elif defined GNULIB_POSIXCHECK +# undef memmem +# if HAVE_RAW_DECL_MEMMEM +_GL_WARN_ON_USE (memmem, "memmem is unportable and often quadratic - " + "use gnulib module memmem-simple for portability, " + "and module memmem for speed" ); +# endif +#endif + +/* Copy N bytes of SRC to DEST, return pointer to bytes after the + last written byte. */ +#if @GNULIB_MEMPCPY@ +# if ! @HAVE_MEMPCPY@ +_GL_FUNCDECL_SYS (mempcpy, void *, + (void *restrict __dest, void const *restrict __src, + size_t __n) + _GL_ARG_NONNULL ((1, 2))); +# endif +_GL_CXXALIAS_SYS (mempcpy, void *, + (void *restrict __dest, void const *restrict __src, + size_t __n)); +_GL_CXXALIASWARN (mempcpy); +#elif defined GNULIB_POSIXCHECK +# undef mempcpy +# if HAVE_RAW_DECL_MEMPCPY +_GL_WARN_ON_USE (mempcpy, "mempcpy is unportable - " + "use gnulib module mempcpy for portability"); +# endif +#endif + +/* Search backwards through a block for a byte (specified as an int). */ +#if @GNULIB_MEMRCHR@ +# if ! @HAVE_DECL_MEMRCHR@ +_GL_FUNCDECL_SYS (memrchr, void *, (void const *, int, size_t) + _GL_ATTRIBUTE_PURE + _GL_ARG_NONNULL ((1))); +# endif + /* On some systems, this function is defined as an overloaded function: + extern "C++" { const void * std::memrchr (const void *, int, size_t); } + extern "C++" { void * std::memrchr (void *, int, size_t); } */ +_GL_CXXALIAS_SYS_CAST2 (memrchr, + void *, (void const *, int, size_t), + void const *, (void const *, int, size_t)); +# if ((__GLIBC__ == 2 && __GLIBC_MINOR__ >= 10) && !defined __UCLIBC__) \ + && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4)) +_GL_CXXALIASWARN1 (memrchr, void *, (void *, int, size_t)); +_GL_CXXALIASWARN1 (memrchr, void const *, (void const *, int, size_t)); +# else +_GL_CXXALIASWARN (memrchr); +# endif +#elif defined GNULIB_POSIXCHECK +# undef memrchr +# if HAVE_RAW_DECL_MEMRCHR +_GL_WARN_ON_USE (memrchr, "memrchr is unportable - " + "use gnulib module memrchr for portability"); +# endif +#endif + +/* Find the first occurrence of C in S. More efficient than + memchr(S,C,N), at the expense of undefined behavior if C does not + occur within N bytes. */ +#if @GNULIB_RAWMEMCHR@ +# if ! @HAVE_RAWMEMCHR@ +_GL_FUNCDECL_SYS (rawmemchr, void *, (void const *__s, int __c_in) + _GL_ATTRIBUTE_PURE + _GL_ARG_NONNULL ((1))); +# endif + /* On some systems, this function is defined as an overloaded function: + extern "C++" { const void * std::rawmemchr (const void *, int); } + extern "C++" { void * std::rawmemchr (void *, int); } */ +_GL_CXXALIAS_SYS_CAST2 (rawmemchr, + void *, (void const *__s, int __c_in), + void const *, (void const *__s, int __c_in)); +# if ((__GLIBC__ == 2 && __GLIBC_MINOR__ >= 10) && !defined __UCLIBC__) \ + && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4)) +_GL_CXXALIASWARN1 (rawmemchr, void *, (void *__s, int __c_in)); +_GL_CXXALIASWARN1 (rawmemchr, void const *, (void const *__s, int __c_in)); +# else +_GL_CXXALIASWARN (rawmemchr); +# endif +#elif defined GNULIB_POSIXCHECK +# undef rawmemchr +# if HAVE_RAW_DECL_RAWMEMCHR +_GL_WARN_ON_USE (rawmemchr, "rawmemchr is unportable - " + "use gnulib module rawmemchr for portability"); +# endif +#endif + +/* Copy SRC to DST, returning the address of the terminating '\0' in DST. */ +#if @GNULIB_STPCPY@ +# if ! @HAVE_STPCPY@ +_GL_FUNCDECL_SYS (stpcpy, char *, + (char *restrict __dst, char const *restrict __src) + _GL_ARG_NONNULL ((1, 2))); +# endif +_GL_CXXALIAS_SYS (stpcpy, char *, + (char *restrict __dst, char const *restrict __src)); +_GL_CXXALIASWARN (stpcpy); +#elif defined GNULIB_POSIXCHECK +# undef stpcpy +# if HAVE_RAW_DECL_STPCPY +_GL_WARN_ON_USE (stpcpy, "stpcpy is unportable - " + "use gnulib module stpcpy for portability"); +# endif +#endif + +/* Copy no more than N bytes of SRC to DST, returning a pointer past the + last non-NUL byte written into DST. */ +#if @GNULIB_STPNCPY@ +# if @REPLACE_STPNCPY@ +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# undef stpncpy +# define stpncpy rpl_stpncpy +# endif +_GL_FUNCDECL_RPL (stpncpy, char *, + (char *restrict __dst, char const *restrict __src, + size_t __n) + _GL_ARG_NONNULL ((1, 2))); +_GL_CXXALIAS_RPL (stpncpy, char *, + (char *restrict __dst, char const *restrict __src, + size_t __n)); +# else +# if ! @HAVE_STPNCPY@ +_GL_FUNCDECL_SYS (stpncpy, char *, + (char *restrict __dst, char const *restrict __src, + size_t __n) + _GL_ARG_NONNULL ((1, 2))); +# endif +_GL_CXXALIAS_SYS (stpncpy, char *, + (char *restrict __dst, char const *restrict __src, + size_t __n)); +# endif +_GL_CXXALIASWARN (stpncpy); +#elif defined GNULIB_POSIXCHECK +# undef stpncpy +# if HAVE_RAW_DECL_STPNCPY +_GL_WARN_ON_USE (stpncpy, "stpncpy is unportable - " + "use gnulib module stpncpy for portability"); +# endif +#endif + +#if defined GNULIB_POSIXCHECK +/* strchr() does not work with multibyte strings if the locale encoding is + GB18030 and the character to be searched is a digit. */ +# undef strchr +/* Assume strchr is always declared. */ +_GL_WARN_ON_USE (strchr, "strchr cannot work correctly on character strings " + "in some multibyte locales - " + "use mbschr if you care about internationalization"); +#endif + +/* Find the first occurrence of C in S or the final NUL byte. */ +#if @GNULIB_STRCHRNUL@ +# if @REPLACE_STRCHRNUL@ +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# define strchrnul rpl_strchrnul +# endif +_GL_FUNCDECL_RPL (strchrnul, char *, (const char *__s, int __c_in) + _GL_ATTRIBUTE_PURE + _GL_ARG_NONNULL ((1))); +_GL_CXXALIAS_RPL (strchrnul, char *, + (const char *str, int ch)); +# else +# if ! @HAVE_STRCHRNUL@ +_GL_FUNCDECL_SYS (strchrnul, char *, (char const *__s, int __c_in) + _GL_ATTRIBUTE_PURE + _GL_ARG_NONNULL ((1))); +# endif + /* On some systems, this function is defined as an overloaded function: + extern "C++" { const char * std::strchrnul (const char *, int); } + extern "C++" { char * std::strchrnul (char *, int); } */ +_GL_CXXALIAS_SYS_CAST2 (strchrnul, + char *, (char const *__s, int __c_in), + char const *, (char const *__s, int __c_in)); +# endif +# if ((__GLIBC__ == 2 && __GLIBC_MINOR__ >= 10) && !defined __UCLIBC__) \ + && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4)) +_GL_CXXALIASWARN1 (strchrnul, char *, (char *__s, int __c_in)); +_GL_CXXALIASWARN1 (strchrnul, char const *, (char const *__s, int __c_in)); +# else +_GL_CXXALIASWARN (strchrnul); +# endif +#elif defined GNULIB_POSIXCHECK +# undef strchrnul +# if HAVE_RAW_DECL_STRCHRNUL +_GL_WARN_ON_USE (strchrnul, "strchrnul is unportable - " + "use gnulib module strchrnul for portability"); +# endif +#endif + +/* Duplicate S, returning an identical malloc'd string. */ +#if @GNULIB_STRDUP@ +# if @REPLACE_STRDUP@ +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# undef strdup +# define strdup rpl_strdup +# endif +_GL_FUNCDECL_RPL (strdup, char *, (char const *__s) _GL_ARG_NONNULL ((1))); +_GL_CXXALIAS_RPL (strdup, char *, (char const *__s)); +# else +# if defined __cplusplus && defined GNULIB_NAMESPACE && defined strdup + /* strdup exists as a function and as a macro. Get rid of the macro. */ +# undef strdup +# endif +# if !(@HAVE_DECL_STRDUP@ || defined strdup) +_GL_FUNCDECL_SYS (strdup, char *, (char const *__s) _GL_ARG_NONNULL ((1))); +# endif +_GL_CXXALIAS_SYS (strdup, char *, (char const *__s)); +# endif +_GL_CXXALIASWARN (strdup); +#elif defined GNULIB_POSIXCHECK +# undef strdup +# if HAVE_RAW_DECL_STRDUP +_GL_WARN_ON_USE (strdup, "strdup is unportable - " + "use gnulib module strdup for portability"); +# endif +#endif + +/* Append no more than N characters from SRC onto DEST. */ +#if @GNULIB_STRNCAT@ +# if @REPLACE_STRNCAT@ +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# undef strncat +# define strncat rpl_strncat +# endif +_GL_FUNCDECL_RPL (strncat, char *, (char *dest, const char *src, size_t n) + _GL_ARG_NONNULL ((1, 2))); +_GL_CXXALIAS_RPL (strncat, char *, (char *dest, const char *src, size_t n)); +# else +_GL_CXXALIAS_SYS (strncat, char *, (char *dest, const char *src, size_t n)); +# endif +_GL_CXXALIASWARN (strncat); +#elif defined GNULIB_POSIXCHECK +# undef strncat +# if HAVE_RAW_DECL_STRNCAT +_GL_WARN_ON_USE (strncat, "strncat is unportable - " + "use gnulib module strncat for portability"); +# endif +#endif + +/* Return a newly allocated copy of at most N bytes of STRING. */ +#if @GNULIB_STRNDUP@ +# if @REPLACE_STRNDUP@ +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# undef strndup +# define strndup rpl_strndup +# endif +_GL_FUNCDECL_RPL (strndup, char *, (char const *__string, size_t __n) + _GL_ARG_NONNULL ((1))); +_GL_CXXALIAS_RPL (strndup, char *, (char const *__string, size_t __n)); +# else +# if ! @HAVE_DECL_STRNDUP@ +_GL_FUNCDECL_SYS (strndup, char *, (char const *__string, size_t __n) + _GL_ARG_NONNULL ((1))); +# endif +_GL_CXXALIAS_SYS (strndup, char *, (char const *__string, size_t __n)); +# endif +_GL_CXXALIASWARN (strndup); +#elif defined GNULIB_POSIXCHECK +# undef strndup +# if HAVE_RAW_DECL_STRNDUP +_GL_WARN_ON_USE (strndup, "strndup is unportable - " + "use gnulib module strndup for portability"); +# endif +#endif + +/* Find the length (number of bytes) of STRING, but scan at most + MAXLEN bytes. If no '\0' terminator is found in that many bytes, + return MAXLEN. */ +#if @GNULIB_STRNLEN@ +# if @REPLACE_STRNLEN@ +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# undef strnlen +# define strnlen rpl_strnlen +# endif +_GL_FUNCDECL_RPL (strnlen, size_t, (char const *__string, size_t __maxlen) + _GL_ATTRIBUTE_PURE + _GL_ARG_NONNULL ((1))); +_GL_CXXALIAS_RPL (strnlen, size_t, (char const *__string, size_t __maxlen)); +# else +# if ! @HAVE_DECL_STRNLEN@ +_GL_FUNCDECL_SYS (strnlen, size_t, (char const *__string, size_t __maxlen) + _GL_ATTRIBUTE_PURE + _GL_ARG_NONNULL ((1))); +# endif +_GL_CXXALIAS_SYS (strnlen, size_t, (char const *__string, size_t __maxlen)); +# endif +_GL_CXXALIASWARN (strnlen); +#elif defined GNULIB_POSIXCHECK +# undef strnlen +# if HAVE_RAW_DECL_STRNLEN +_GL_WARN_ON_USE (strnlen, "strnlen is unportable - " + "use gnulib module strnlen for portability"); +# endif +#endif + +#if defined GNULIB_POSIXCHECK +/* strcspn() assumes the second argument is a list of single-byte characters. + Even in this simple case, it does not work with multibyte strings if the + locale encoding is GB18030 and one of the characters to be searched is a + digit. */ +# undef strcspn +/* Assume strcspn is always declared. */ +_GL_WARN_ON_USE (strcspn, "strcspn cannot work correctly on character strings " + "in multibyte locales - " + "use mbscspn if you care about internationalization"); +#endif + +/* Find the first occurrence in S of any character in ACCEPT. */ +#if @GNULIB_STRPBRK@ +# if ! @HAVE_STRPBRK@ +_GL_FUNCDECL_SYS (strpbrk, char *, (char const *__s, char const *__accept) + _GL_ATTRIBUTE_PURE + _GL_ARG_NONNULL ((1, 2))); +# endif + /* On some systems, this function is defined as an overloaded function: + extern "C" { const char * strpbrk (const char *, const char *); } + extern "C++" { char * strpbrk (char *, const char *); } */ +_GL_CXXALIAS_SYS_CAST2 (strpbrk, + char *, (char const *__s, char const *__accept), + const char *, (char const *__s, char const *__accept)); +# if ((__GLIBC__ == 2 && __GLIBC_MINOR__ >= 10) && !defined __UCLIBC__) \ + && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4)) +_GL_CXXALIASWARN1 (strpbrk, char *, (char *__s, char const *__accept)); +_GL_CXXALIASWARN1 (strpbrk, char const *, + (char const *__s, char const *__accept)); +# else +_GL_CXXALIASWARN (strpbrk); +# endif +# if defined GNULIB_POSIXCHECK +/* strpbrk() assumes the second argument is a list of single-byte characters. + Even in this simple case, it does not work with multibyte strings if the + locale encoding is GB18030 and one of the characters to be searched is a + digit. */ +# undef strpbrk +_GL_WARN_ON_USE (strpbrk, "strpbrk cannot work correctly on character strings " + "in multibyte locales - " + "use mbspbrk if you care about internationalization"); +# endif +#elif defined GNULIB_POSIXCHECK +# undef strpbrk +# if HAVE_RAW_DECL_STRPBRK +_GL_WARN_ON_USE (strpbrk, "strpbrk is unportable - " + "use gnulib module strpbrk for portability"); +# endif +#endif + +#if defined GNULIB_POSIXCHECK +/* strspn() assumes the second argument is a list of single-byte characters. + Even in this simple case, it cannot work with multibyte strings. */ +# undef strspn +/* Assume strspn is always declared. */ +_GL_WARN_ON_USE (strspn, "strspn cannot work correctly on character strings " + "in multibyte locales - " + "use mbsspn if you care about internationalization"); +#endif + +#if defined GNULIB_POSIXCHECK +/* strrchr() does not work with multibyte strings if the locale encoding is + GB18030 and the character to be searched is a digit. */ +# undef strrchr +/* Assume strrchr is always declared. */ +_GL_WARN_ON_USE (strrchr, "strrchr cannot work correctly on character strings " + "in some multibyte locales - " + "use mbsrchr if you care about internationalization"); +#endif + +/* Search the next delimiter (char listed in DELIM) starting at *STRINGP. + If one is found, overwrite it with a NUL, and advance *STRINGP + to point to the next char after it. Otherwise, set *STRINGP to NULL. + If *STRINGP was already NULL, nothing happens. + Return the old value of *STRINGP. + + This is a variant of strtok() that is multithread-safe and supports + empty fields. + + Caveat: It modifies the original string. + Caveat: These functions cannot be used on constant strings. + Caveat: The identity of the delimiting character is lost. + Caveat: It doesn't work with multibyte strings unless all of the delimiter + characters are ASCII characters < 0x30. + + See also strtok_r(). */ +#if @GNULIB_STRSEP@ +# if ! @HAVE_STRSEP@ +_GL_FUNCDECL_SYS (strsep, char *, + (char **restrict __stringp, char const *restrict __delim) + _GL_ARG_NONNULL ((1, 2))); +# endif +_GL_CXXALIAS_SYS (strsep, char *, + (char **restrict __stringp, char const *restrict __delim)); +_GL_CXXALIASWARN (strsep); +# if defined GNULIB_POSIXCHECK +# undef strsep +_GL_WARN_ON_USE (strsep, "strsep cannot work correctly on character strings " + "in multibyte locales - " + "use mbssep if you care about internationalization"); +# endif +#elif defined GNULIB_POSIXCHECK +# undef strsep +# if HAVE_RAW_DECL_STRSEP +_GL_WARN_ON_USE (strsep, "strsep is unportable - " + "use gnulib module strsep for portability"); +# endif +#endif + +#if @GNULIB_STRSTR@ +# if @REPLACE_STRSTR@ +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# define strstr rpl_strstr +# endif +_GL_FUNCDECL_RPL (strstr, char *, (const char *haystack, const char *needle) + _GL_ATTRIBUTE_PURE + _GL_ARG_NONNULL ((1, 2))); +_GL_CXXALIAS_RPL (strstr, char *, (const char *haystack, const char *needle)); +# else + /* On some systems, this function is defined as an overloaded function: + extern "C++" { const char * strstr (const char *, const char *); } + extern "C++" { char * strstr (char *, const char *); } */ +_GL_CXXALIAS_SYS_CAST2 (strstr, + char *, (const char *haystack, const char *needle), + const char *, (const char *haystack, const char *needle)); +# endif +# if ((__GLIBC__ == 2 && __GLIBC_MINOR__ >= 10) && !defined __UCLIBC__) \ + && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4)) +_GL_CXXALIASWARN1 (strstr, char *, (char *haystack, const char *needle)); +_GL_CXXALIASWARN1 (strstr, const char *, + (const char *haystack, const char *needle)); +# else +_GL_CXXALIASWARN (strstr); +# endif +#elif defined GNULIB_POSIXCHECK +/* strstr() does not work with multibyte strings if the locale encoding is + different from UTF-8: + POSIX says that it operates on "strings", and "string" in POSIX is defined + as a sequence of bytes, not of characters. */ +# undef strstr +/* Assume strstr is always declared. */ +_GL_WARN_ON_USE (strstr, "strstr is quadratic on many systems, and cannot " + "work correctly on character strings in most " + "multibyte locales - " + "use mbsstr if you care about internationalization, " + "or use strstr if you care about speed"); +#endif + +/* Find the first occurrence of NEEDLE in HAYSTACK, using case-insensitive + comparison. */ +#if @GNULIB_STRCASESTR@ +# if @REPLACE_STRCASESTR@ +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# define strcasestr rpl_strcasestr +# endif +_GL_FUNCDECL_RPL (strcasestr, char *, + (const char *haystack, const char *needle) + _GL_ATTRIBUTE_PURE + _GL_ARG_NONNULL ((1, 2))); +_GL_CXXALIAS_RPL (strcasestr, char *, + (const char *haystack, const char *needle)); +# else +# if ! @HAVE_STRCASESTR@ +_GL_FUNCDECL_SYS (strcasestr, char *, + (const char *haystack, const char *needle) + _GL_ATTRIBUTE_PURE + _GL_ARG_NONNULL ((1, 2))); +# endif + /* On some systems, this function is defined as an overloaded function: + extern "C++" { const char * strcasestr (const char *, const char *); } + extern "C++" { char * strcasestr (char *, const char *); } */ +_GL_CXXALIAS_SYS_CAST2 (strcasestr, + char *, (const char *haystack, const char *needle), + const char *, (const char *haystack, const char *needle)); +# endif +# if ((__GLIBC__ == 2 && __GLIBC_MINOR__ >= 10) && !defined __UCLIBC__) \ + && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4)) +_GL_CXXALIASWARN1 (strcasestr, char *, (char *haystack, const char *needle)); +_GL_CXXALIASWARN1 (strcasestr, const char *, + (const char *haystack, const char *needle)); +# else +_GL_CXXALIASWARN (strcasestr); +# endif +#elif defined GNULIB_POSIXCHECK +/* strcasestr() does not work with multibyte strings: + It is a glibc extension, and glibc implements it only for unibyte + locales. */ +# undef strcasestr +# if HAVE_RAW_DECL_STRCASESTR +_GL_WARN_ON_USE (strcasestr, "strcasestr does work correctly on character " + "strings in multibyte locales - " + "use mbscasestr if you care about " + "internationalization, or use c-strcasestr if you want " + "a locale independent function"); +# endif +#endif + +/* Parse S into tokens separated by characters in DELIM. + If S is NULL, the saved pointer in SAVE_PTR is used as + the next starting point. For example: + char s[] = "-abc-=-def"; + char *sp; + x = strtok_r(s, "-", &sp); // x = "abc", sp = "=-def" + x = strtok_r(NULL, "-=", &sp); // x = "def", sp = NULL + x = strtok_r(NULL, "=", &sp); // x = NULL + // s = "abc\0-def\0" + + This is a variant of strtok() that is multithread-safe. + + For the POSIX documentation for this function, see: + http://www.opengroup.org/susv3xsh/strtok.html + + Caveat: It modifies the original string. + Caveat: These functions cannot be used on constant strings. + Caveat: The identity of the delimiting character is lost. + Caveat: It doesn't work with multibyte strings unless all of the delimiter + characters are ASCII characters < 0x30. + + See also strsep(). */ +#if @GNULIB_STRTOK_R@ +# if @REPLACE_STRTOK_R@ +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# undef strtok_r +# define strtok_r rpl_strtok_r +# endif +_GL_FUNCDECL_RPL (strtok_r, char *, + (char *restrict s, char const *restrict delim, + char **restrict save_ptr) + _GL_ARG_NONNULL ((2, 3))); +_GL_CXXALIAS_RPL (strtok_r, char *, + (char *restrict s, char const *restrict delim, + char **restrict save_ptr)); +# else +# if @UNDEFINE_STRTOK_R@ || defined GNULIB_POSIXCHECK +# undef strtok_r +# endif +# if ! @HAVE_DECL_STRTOK_R@ +_GL_FUNCDECL_SYS (strtok_r, char *, + (char *restrict s, char const *restrict delim, + char **restrict save_ptr) + _GL_ARG_NONNULL ((2, 3))); +# endif +_GL_CXXALIAS_SYS (strtok_r, char *, + (char *restrict s, char const *restrict delim, + char **restrict save_ptr)); +# endif +_GL_CXXALIASWARN (strtok_r); +# if defined GNULIB_POSIXCHECK +_GL_WARN_ON_USE (strtok_r, "strtok_r cannot work correctly on character " + "strings in multibyte locales - " + "use mbstok_r if you care about internationalization"); +# endif +#elif defined GNULIB_POSIXCHECK +# undef strtok_r +# if HAVE_RAW_DECL_STRTOK_R +_GL_WARN_ON_USE (strtok_r, "strtok_r is unportable - " + "use gnulib module strtok_r for portability"); +# endif +#endif + + +/* The following functions are not specified by POSIX. They are gnulib + extensions. */ + +#if @GNULIB_MBSLEN@ +/* Return the number of multibyte characters in the character string STRING. + This considers multibyte characters, unlike strlen, which counts bytes. */ +# ifdef __MirBSD__ /* MirBSD defines mbslen as a macro. Override it. */ +# undef mbslen +# endif +# if @HAVE_MBSLEN@ /* AIX, OSF/1, MirBSD define mbslen already in libc. */ +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# define mbslen rpl_mbslen +# endif +_GL_FUNCDECL_RPL (mbslen, size_t, (const char *string) _GL_ARG_NONNULL ((1))); +_GL_CXXALIAS_RPL (mbslen, size_t, (const char *string)); +# else +_GL_FUNCDECL_SYS (mbslen, size_t, (const char *string) _GL_ARG_NONNULL ((1))); +_GL_CXXALIAS_SYS (mbslen, size_t, (const char *string)); +# endif +_GL_CXXALIASWARN (mbslen); +#endif + +#if @GNULIB_MBSNLEN@ +/* Return the number of multibyte characters in the character string starting + at STRING and ending at STRING + LEN. */ +_GL_EXTERN_C size_t mbsnlen (const char *string, size_t len) + _GL_ARG_NONNULL ((1)); +#endif + +#if @GNULIB_MBSCHR@ +/* Locate the first single-byte character C in the character string STRING, + and return a pointer to it. Return NULL if C is not found in STRING. + Unlike strchr(), this function works correctly in multibyte locales with + encodings such as GB18030. */ +# if defined __hpux +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# define mbschr rpl_mbschr /* avoid collision with HP-UX function */ +# endif +_GL_FUNCDECL_RPL (mbschr, char *, (const char *string, int c) + _GL_ARG_NONNULL ((1))); +_GL_CXXALIAS_RPL (mbschr, char *, (const char *string, int c)); +# else +_GL_FUNCDECL_SYS (mbschr, char *, (const char *string, int c) + _GL_ARG_NONNULL ((1))); +_GL_CXXALIAS_SYS (mbschr, char *, (const char *string, int c)); +# endif +_GL_CXXALIASWARN (mbschr); +#endif + +#if @GNULIB_MBSRCHR@ +/* Locate the last single-byte character C in the character string STRING, + and return a pointer to it. Return NULL if C is not found in STRING. + Unlike strrchr(), this function works correctly in multibyte locales with + encodings such as GB18030. */ +# if defined __hpux || defined __INTERIX +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# define mbsrchr rpl_mbsrchr /* avoid collision with system function */ +# endif +_GL_FUNCDECL_RPL (mbsrchr, char *, (const char *string, int c) + _GL_ARG_NONNULL ((1))); +_GL_CXXALIAS_RPL (mbsrchr, char *, (const char *string, int c)); +# else +_GL_FUNCDECL_SYS (mbsrchr, char *, (const char *string, int c) + _GL_ARG_NONNULL ((1))); +_GL_CXXALIAS_SYS (mbsrchr, char *, (const char *string, int c)); +# endif +_GL_CXXALIASWARN (mbsrchr); +#endif + +#if @GNULIB_MBSSTR@ +/* Find the first occurrence of the character string NEEDLE in the character + string HAYSTACK. Return NULL if NEEDLE is not found in HAYSTACK. + Unlike strstr(), this function works correctly in multibyte locales with + encodings different from UTF-8. */ +_GL_EXTERN_C char * mbsstr (const char *haystack, const char *needle) + _GL_ARG_NONNULL ((1, 2)); +#endif + +#if @GNULIB_MBSCASECMP@ +/* Compare the character strings S1 and S2, ignoring case, returning less than, + equal to or greater than zero if S1 is lexicographically less than, equal to + or greater than S2. + Note: This function may, in multibyte locales, return 0 for strings of + different lengths! + Unlike strcasecmp(), this function works correctly in multibyte locales. */ +_GL_EXTERN_C int mbscasecmp (const char *s1, const char *s2) + _GL_ARG_NONNULL ((1, 2)); +#endif + +#if @GNULIB_MBSNCASECMP@ +/* Compare the initial segment of the character string S1 consisting of at most + N characters with the initial segment of the character string S2 consisting + of at most N characters, ignoring case, returning less than, equal to or + greater than zero if the initial segment of S1 is lexicographically less + than, equal to or greater than the initial segment of S2. + Note: This function may, in multibyte locales, return 0 for initial segments + of different lengths! + Unlike strncasecmp(), this function works correctly in multibyte locales. + But beware that N is not a byte count but a character count! */ +_GL_EXTERN_C int mbsncasecmp (const char *s1, const char *s2, size_t n) + _GL_ARG_NONNULL ((1, 2)); +#endif + +#if @GNULIB_MBSPCASECMP@ +/* Compare the initial segment of the character string STRING consisting of + at most mbslen (PREFIX) characters with the character string PREFIX, + ignoring case. If the two match, return a pointer to the first byte + after this prefix in STRING. Otherwise, return NULL. + Note: This function may, in multibyte locales, return non-NULL if STRING + is of smaller length than PREFIX! + Unlike strncasecmp(), this function works correctly in multibyte + locales. */ +_GL_EXTERN_C char * mbspcasecmp (const char *string, const char *prefix) + _GL_ARG_NONNULL ((1, 2)); +#endif + +#if @GNULIB_MBSCASESTR@ +/* Find the first occurrence of the character string NEEDLE in the character + string HAYSTACK, using case-insensitive comparison. + Note: This function may, in multibyte locales, return success even if + strlen (haystack) < strlen (needle) ! + Unlike strcasestr(), this function works correctly in multibyte locales. */ +_GL_EXTERN_C char * mbscasestr (const char *haystack, const char *needle) + _GL_ARG_NONNULL ((1, 2)); +#endif + +#if @GNULIB_MBSCSPN@ +/* Find the first occurrence in the character string STRING of any character + in the character string ACCEPT. Return the number of bytes from the + beginning of the string to this occurrence, or to the end of the string + if none exists. + Unlike strcspn(), this function works correctly in multibyte locales. */ +_GL_EXTERN_C size_t mbscspn (const char *string, const char *accept) + _GL_ARG_NONNULL ((1, 2)); +#endif + +#if @GNULIB_MBSPBRK@ +/* Find the first occurrence in the character string STRING of any character + in the character string ACCEPT. Return the pointer to it, or NULL if none + exists. + Unlike strpbrk(), this function works correctly in multibyte locales. */ +# if defined __hpux +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# define mbspbrk rpl_mbspbrk /* avoid collision with HP-UX function */ +# endif +_GL_FUNCDECL_RPL (mbspbrk, char *, (const char *string, const char *accept) + _GL_ARG_NONNULL ((1, 2))); +_GL_CXXALIAS_RPL (mbspbrk, char *, (const char *string, const char *accept)); +# else +_GL_FUNCDECL_SYS (mbspbrk, char *, (const char *string, const char *accept) + _GL_ARG_NONNULL ((1, 2))); +_GL_CXXALIAS_SYS (mbspbrk, char *, (const char *string, const char *accept)); +# endif +_GL_CXXALIASWARN (mbspbrk); +#endif + +#if @GNULIB_MBSSPN@ +/* Find the first occurrence in the character string STRING of any character + not in the character string REJECT. Return the number of bytes from the + beginning of the string to this occurrence, or to the end of the string + if none exists. + Unlike strspn(), this function works correctly in multibyte locales. */ +_GL_EXTERN_C size_t mbsspn (const char *string, const char *reject) + _GL_ARG_NONNULL ((1, 2)); +#endif + +#if @GNULIB_MBSSEP@ +/* Search the next delimiter (multibyte character listed in the character + string DELIM) starting at the character string *STRINGP. + If one is found, overwrite it with a NUL, and advance *STRINGP to point + to the next multibyte character after it. Otherwise, set *STRINGP to NULL. + If *STRINGP was already NULL, nothing happens. + Return the old value of *STRINGP. + + This is a variant of mbstok_r() that supports empty fields. + + Caveat: It modifies the original string. + Caveat: These functions cannot be used on constant strings. + Caveat: The identity of the delimiting character is lost. + + See also mbstok_r(). */ +_GL_EXTERN_C char * mbssep (char **stringp, const char *delim) + _GL_ARG_NONNULL ((1, 2)); +#endif + +#if @GNULIB_MBSTOK_R@ +/* Parse the character string STRING into tokens separated by characters in + the character string DELIM. + If STRING is NULL, the saved pointer in SAVE_PTR is used as + the next starting point. For example: + char s[] = "-abc-=-def"; + char *sp; + x = mbstok_r(s, "-", &sp); // x = "abc", sp = "=-def" + x = mbstok_r(NULL, "-=", &sp); // x = "def", sp = NULL + x = mbstok_r(NULL, "=", &sp); // x = NULL + // s = "abc\0-def\0" + + Caveat: It modifies the original string. + Caveat: These functions cannot be used on constant strings. + Caveat: The identity of the delimiting character is lost. + + See also mbssep(). */ +_GL_EXTERN_C char * mbstok_r (char *string, const char *delim, char **save_ptr) + _GL_ARG_NONNULL ((2, 3)); +#endif + +/* Map any int, typically from errno, into an error message. */ +#if @GNULIB_STRERROR@ +# if @REPLACE_STRERROR@ +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# undef strerror +# define strerror rpl_strerror +# endif +_GL_FUNCDECL_RPL (strerror, char *, (int)); +_GL_CXXALIAS_RPL (strerror, char *, (int)); +# else +_GL_CXXALIAS_SYS (strerror, char *, (int)); +# endif +_GL_CXXALIASWARN (strerror); +#elif defined GNULIB_POSIXCHECK +# undef strerror +/* Assume strerror is always declared. */ +_GL_WARN_ON_USE (strerror, "strerror is unportable - " + "use gnulib module strerror to guarantee non-NULL result"); +#endif + +/* Map any int, typically from errno, into an error message. Multithread-safe. + Uses the POSIX declaration, not the glibc declaration. */ +#if @GNULIB_STRERROR_R@ +# if @REPLACE_STRERROR_R@ +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# undef strerror_r +# define strerror_r rpl_strerror_r +# endif +_GL_FUNCDECL_RPL (strerror_r, int, (int errnum, char *buf, size_t buflen) + _GL_ARG_NONNULL ((2))); +_GL_CXXALIAS_RPL (strerror_r, int, (int errnum, char *buf, size_t buflen)); +# else +# if !@HAVE_DECL_STRERROR_R@ +_GL_FUNCDECL_SYS (strerror_r, int, (int errnum, char *buf, size_t buflen) + _GL_ARG_NONNULL ((2))); +# endif +_GL_CXXALIAS_SYS (strerror_r, int, (int errnum, char *buf, size_t buflen)); +# endif +# if @HAVE_DECL_STRERROR_R@ +_GL_CXXALIASWARN (strerror_r); +# endif +#elif defined GNULIB_POSIXCHECK +# undef strerror_r +# if HAVE_RAW_DECL_STRERROR_R +_GL_WARN_ON_USE (strerror_r, "strerror_r is unportable - " + "use gnulib module strerror_r-posix for portability"); +# endif +#endif + +#if @GNULIB_STRSIGNAL@ +# if @REPLACE_STRSIGNAL@ +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# define strsignal rpl_strsignal +# endif +_GL_FUNCDECL_RPL (strsignal, char *, (int __sig)); +_GL_CXXALIAS_RPL (strsignal, char *, (int __sig)); +# else +# if ! @HAVE_DECL_STRSIGNAL@ +_GL_FUNCDECL_SYS (strsignal, char *, (int __sig)); +# endif +/* Need to cast, because on Cygwin 1.5.x systems, the return type is + 'const char *'. */ +_GL_CXXALIAS_SYS_CAST (strsignal, char *, (int __sig)); +# endif +_GL_CXXALIASWARN (strsignal); +#elif defined GNULIB_POSIXCHECK +# undef strsignal +# if HAVE_RAW_DECL_STRSIGNAL +_GL_WARN_ON_USE (strsignal, "strsignal is unportable - " + "use gnulib module strsignal for portability"); +# endif +#endif + +#if @GNULIB_STRVERSCMP@ +# if !@HAVE_STRVERSCMP@ +_GL_FUNCDECL_SYS (strverscmp, int, (const char *, const char *) + _GL_ARG_NONNULL ((1, 2))); +# endif +_GL_CXXALIAS_SYS (strverscmp, int, (const char *, const char *)); +_GL_CXXALIASWARN (strverscmp); +#elif defined GNULIB_POSIXCHECK +# undef strverscmp +# if HAVE_RAW_DECL_STRVERSCMP +_GL_WARN_ON_USE (strverscmp, "strverscmp is unportable - " + "use gnulib module strverscmp for portability"); +# endif +#endif + + +#endif /* _@GUARD_PREFIX@_STRING_H */ +#endif /* _@GUARD_PREFIX@_STRING_H */ diff --git a/libbitfury.c b/libbitfury.c new file mode 100644 index 0000000..78f982c --- /dev/null +++ b/libbitfury.c @@ -0,0 +1,387 @@ +/* + * Copyright 2014 Con Kolivas + * Copyright 2013 Andrew Smith + * Copyright 2013 bitfury + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#include "miner.h" +#include "driver-bitfury.h" +#include "libbitfury.h" +#include "sha2.h" + +void ms3steps(uint32_t *p) +{ + uint32_t a, b, c, d, e, f, g, h, new_e, new_a; + int i; + + a = p[0]; + b = p[1]; + c = p[2]; + d = p[3]; + e = p[4]; + f = p[5]; + g = p[6]; + h = p[7]; + for (i = 0; i < 3; i++) { + new_e = p[i+16] + sha256_k[i] + h + CH(e,f,g) + SHA256_F2(e) + d; + new_a = p[i+16] + sha256_k[i] + h + CH(e,f,g) + SHA256_F2(e) + + SHA256_F1(a) + MAJ(a,b,c); + d = c; + c = b; + b = a; + a = new_a; + h = g; + g = f; + f = e; + e = new_e; + } + p[15] = a; + p[14] = b; + p[13] = c; + p[12] = d; + p[11] = e; + p[10] = f; + p[9] = g; + p[8] = h; +} + +uint32_t decnonce(uint32_t in) +{ + uint32_t out; + + /* First part load */ + out = (in & 0xFF) << 24; + in >>= 8; + + /* Byte reversal */ + in = (((in & 0xaaaaaaaa) >> 1) | ((in & 0x55555555) << 1)); + in = (((in & 0xcccccccc) >> 2) | ((in & 0x33333333) << 2)); + in = (((in & 0xf0f0f0f0) >> 4) | ((in & 0x0f0f0f0f) << 4)); + + out |= (in >> 2) & 0x3FFFFF; + + /* Extraction */ + if (in & 1) + out |= (1 << 23); + if (in & 2) + out |= (1 << 22); + + out -= 0x800004; + return out; +} + +/* Test vectors to calculate (using address-translated loads) */ +static unsigned int atrvec[] = { + 0xb0e72d8e, 0x1dc5b862, 0xe9e7c4a6, 0x3050f1f5, 0x8a1a6b7e, 0x7ec384e8, 0x42c1c3fc, 0x8ed158a1, /* MIDSTATE */ + 0,0,0,0,0,0,0,0, + 0x8a0bb7b7, 0x33af304f, 0x0b290c1a, 0xf0c4e61f, /* WDATA: hashMerleRoot[7], nTime, nBits, nNonce */ +}; +static bool atrvec_set; + +void bitfury_work_to_payload(struct bitfury_payload *p, struct work *work) +{ + memcpy(p->midstate, work->midstate, 32); + p->m7 = *(unsigned int *)(work->data + 64); + p->ntime = *(unsigned int *)(work->data + 68); + p->nbits = *(unsigned int *)(work->data + 72); + applog(LOG_INFO, "INFO nonc: %08x bitfury_scanHash MS0: %08x, ", p->nnonce, + ((unsigned int *)work->midstate)[0]); + applog(LOG_INFO, "INFO merkle[7]: %08x, ntime: %08x, nbits: %08x", p->m7, + p->ntime, p->nbits); +} + +/* Configuration registers - control oscillators and such stuff. PROGRAMMED when + * magic number matches, UNPROGRAMMED (default) otherwise */ +void spi_config_reg(struct bitfury_info *info, int cfgreg, int ena) +{ + static const uint8_t enaconf[4] = { 0xc1, 0x6a, 0x59, 0xe3 }; + static const uint8_t disconf[4] = { 0, 0, 0, 0 }; + + if (ena) + spi_add_data(info, 0x7000 + cfgreg * 32, enaconf, 4); + else + spi_add_data(info, 0x7000 + cfgreg * 32, disconf, 4); +} + +void spi_set_freq(struct bitfury_info *info) +{ + uint64_t freq; + const uint8_t *osc6 = (unsigned char *)&freq; + + freq = (1ULL << info->osc6_bits) - 1ULL; + spi_add_data(info, 0x6000, osc6, 8); /* Program internal on-die slow oscillator frequency */ +} + +#define FIRST_BASE 61 +#define SECOND_BASE 4 + +void spi_send_conf(struct bitfury_info *info) +{ + const int8_t nfu_counters[16] = { 64, 64, SECOND_BASE, SECOND_BASE+4, SECOND_BASE+2, + SECOND_BASE+2+16, SECOND_BASE, SECOND_BASE+1, (FIRST_BASE)%65, (FIRST_BASE+1)%65, + (FIRST_BASE+3)%65, (FIRST_BASE+3+16)%65, (FIRST_BASE+4)%65, (FIRST_BASE+4+4)%65, + (FIRST_BASE+3+3)%65, (FIRST_BASE+3+1+3)%65 }; + int i; + + for (i = 7; i <= 11; i++) + spi_config_reg(info, i, 0); + spi_config_reg(info, 6, 1); /* disable OUTSLK */ + spi_config_reg(info, 4, 1); /* Enable slow oscillator */ + for (i = 1; i <= 3; ++i) + spi_config_reg(info, i, 0); + /* Program counters correctly for rounds processing, here it should + * start consuming power */ + spi_add_data(info, 0x0100, nfu_counters, 16); +} + +void spi_send_init(struct bitfury_info *info) +{ + /* Prepare internal buffers */ + /* PREPARE BUFFERS (INITIAL PROGRAMMING) */ + unsigned int w[16]; + + if (!atrvec_set) { + atrvec_set = true; + ms3steps(atrvec); + } + memset(w, 0, sizeof(w)); + w[3] = 0xffffffff; + w[4] = 0x80000000; + w[15] = 0x00000280; + spi_add_data(info, 0x1000, w, 16 * 4); + spi_add_data(info, 0x1400, w, 8 * 4); + memset(w, 0, sizeof(w)); + w[0] = 0x80000000; + w[7] = 0x100; + spi_add_data(info, 0x1900, w, 8 * 4); /* Prepare MS and W buffers! */ + spi_add_data(info, 0x3000, atrvec, 19 * 4); +} +void spi_clear_buf(struct bitfury_info *info) +{ + info->spibufsz = 0; +} + +void spi_add_buf(struct bitfury_info *info, const void *buf, const int sz) +{ + if (unlikely(info->spibufsz + sz > SPIBUF_SIZE)) { + applog(LOG_WARNING, "SPI bufsize overflow!"); + return; + } + memcpy(&info->spibuf[info->spibufsz], buf, sz); + info->spibufsz += sz; +} + +void spi_add_break(struct bitfury_info *info) +{ + spi_add_buf(info, "\x4", 1); +} + +void spi_add_fasync(struct bitfury_info *info, int n) +{ + int i; + + for (i = 0; i < n; i++) + spi_add_buf(info, "\x5", 1); +} + +static void spi_add_buf_reverse(struct bitfury_info *info, const char *buf, const int sz) +{ + int i; + + for (i = 0; i < sz; i++) { // Reverse bit order in each byte! + unsigned char p = buf[i]; + + p = ((p & 0xaa) >> 1) | ((p & 0x55) << 1); + p = ((p & 0xcc) >> 2) | ((p & 0x33) << 2); + p = ((p & 0xf0) >> 4) | ((p & 0x0f) << 4); + info->spibuf[info->spibufsz + i] = p; + } + info->spibufsz += sz; +} + +void spi_add_data(struct bitfury_info *info, uint16_t addr, const void *buf, int len) +{ + unsigned char otmp[3]; + + if (len < 4 || len > 128) { + applog(LOG_WARNING, "Can't add SPI data size %d", len); + return; + } + len /= 4; /* Strip */ + otmp[0] = (len - 1) | 0xE0; + otmp[1] = (addr >> 8) & 0xFF; + otmp[2] = addr & 0xFF; + spi_add_buf(info, otmp, 3); + len *= 4; + spi_add_buf_reverse(info, buf, len); +} + +// Bit-banging reset... Each 3 reset cycles reset first chip in chain +bool spi_reset(struct cgpu_info *bitfury, struct bitfury_info *info) +{ + struct mcp_settings *mcp = &info->mcp; + int r; + + // SCK_OVRRIDE + mcp->value.pin[NFU_PIN_SCK_OVR] = MCP2210_GPIO_PIN_HIGH; + mcp->direction.pin[NFU_PIN_SCK_OVR] = MCP2210_GPIO_OUTPUT; + mcp->designation.pin[NFU_PIN_SCK_OVR] = MCP2210_PIN_GPIO; + if (!mcp2210_set_gpio_settings(bitfury, mcp)) + return false; + + for (r = 0; r < 16; ++r) { + char buf[1] = {0x81}; // will send this waveform: - _ _ _ _ _ _ - + unsigned int length = 1; + + if (!mcp2210_spi_transfer(bitfury, &info->mcp, buf, &length)) + return false; + } + + // Deactivate override + mcp->direction.pin[NFU_PIN_SCK_OVR] = MCP2210_GPIO_INPUT; + if (!mcp2210_set_gpio_settings(bitfury, mcp)) + return false; + + return true; +} + +bool mcp_spi_txrx(struct cgpu_info *bitfury, struct bitfury_info *info) +{ + unsigned int length, sendrcv; + int offset = 0; + + length = info->spibufsz; + applog(LOG_DEBUG, "%s %d: SPI sending %u bytes total", bitfury->drv->name, + bitfury->device_id, length); + while (length > MCP2210_TRANSFER_MAX) { + sendrcv = MCP2210_TRANSFER_MAX; + if (!mcp2210_spi_transfer(bitfury, &info->mcp, info->spibuf + offset, &sendrcv)) + return false; + if (sendrcv != MCP2210_TRANSFER_MAX) { + applog(LOG_DEBUG, "%s %d: Send/Receive size mismatch sent %d received %d", + bitfury->drv->name, bitfury->device_id, MCP2210_TRANSFER_MAX, sendrcv); + } + length -= MCP2210_TRANSFER_MAX; + offset += MCP2210_TRANSFER_MAX; + } + sendrcv = length; + if (!mcp2210_spi_transfer(bitfury, &info->mcp, info->spibuf + offset, &sendrcv)) + return false; + if (sendrcv != length) { + applog(LOG_WARNING, "%s %d: Send/Receive size mismatch sent %d received %d", + bitfury->drv->name, bitfury->device_id, length, sendrcv); + return false; + } + return true; +} + +#define READ_WRITE_BYTES_SPI0 0x31 + +bool ftdi_spi_txrx(struct cgpu_info *bitfury, struct bitfury_info *info) +{ + int err, amount, len; + uint16_t length; + char buf[1024]; + + len = info->spibufsz; + length = info->spibufsz - 1; //FTDI length is shifted by one 0x0000 = one byte + buf[0] = READ_WRITE_BYTES_SPI0; + buf[1] = length & 0x00FF; + buf[2] = (length & 0xFF00) >> 8; + memcpy(&buf[3], info->spibuf, info->spibufsz); + info->spibufsz += 3; + err = usb_write(bitfury, buf, info->spibufsz, &amount, C_BXM_SPITX); + if (err || amount != (int)info->spibufsz) { + applog(LOG_ERR, "%s %d: SPI TX error %d, sent %d of %d", bitfury->drv->name, + bitfury->device_id, err, amount, info->spibufsz); + return false; + } + info->spibufsz = len; + /* We shouldn't even get a timeout error on reads in spi mode */ + err = usb_read(bitfury, info->spibuf, len, &amount, C_BXM_SPIRX); + if (err || amount != len) { + applog(LOG_ERR, "%s %d: SPI RX error %d, read %d of %d", bitfury->drv->name, + bitfury->device_id, err, amount, info->spibufsz); + return false; + } + amount = usb_buffer_size(bitfury); + if (amount) { + applog(LOG_ERR, "%s %d: SPI RX Extra read buffer size %d", bitfury->drv->name, + bitfury->device_id, amount); + usb_buffer_clear(bitfury); + return false; + } + return true; +} + +#define BT_OFFSETS 3 + +bool bitfury_checkresults(struct thr_info *thr, struct work *work, uint32_t nonce) +{ + const uint32_t bf_offsets[] = {-0x800000, 0, -0x400000}; + int i; + + for (i = 0; i < BT_OFFSETS; i++) { + uint32_t noffset = nonce + bf_offsets[i]; + + if (test_nonce(work, noffset)) { + submit_tested_work(thr, work); + return true; + } + } + return false; +} + +/* Currently really only supports 2 chips, so chip_n can only be 0 or 1 */ +bool libbitfury_sendHashData(struct thr_info *thr, struct cgpu_info *bitfury, + struct bitfury_info *info, int chip_n) +{ + unsigned newbuf[17]; + unsigned *oldbuf = &info->oldbuf[17 * chip_n]; + struct bitfury_payload *p = &info->payload[chip_n]; + unsigned int localvec[20]; + + /* Programming next value */ + memcpy(localvec, p, 20 * 4); + ms3steps(localvec); + + spi_clear_buf(info); + spi_add_break(info); + spi_add_fasync(info, chip_n); + spi_add_data(info, 0x3000, (void*)localvec, 19 * 4); + if (!info->spi_txrx(bitfury, info)) + return false; + + memcpy(newbuf, info->spibuf + 4 + chip_n, 17 * 4); + + info->job_switched[chip_n] = newbuf[16] != oldbuf[16]; + + if (likely(info->second_run[chip_n])) { + if (info->job_switched[chip_n]) { + int i; + + for (i = 0; i < 16; i++) { + if (oldbuf[i] != newbuf[i] && info->owork[chip_n]) { + uint32_t nonce; //possible nonce + + nonce = decnonce(newbuf[i]); + if (bitfury_checkresults(thr, info->owork[chip_n], nonce)) { + info->submits[chip_n]++; + info->nonces++; + } + } + } + memcpy(oldbuf, newbuf, 17 * 4); + } + } else + info->second_run[chip_n] = true; + + cgsleep_ms(BITFURY_REFRESH_DELAY); + + return true; +} diff --git a/libbitfury.h b/libbitfury.h new file mode 100644 index 0000000..ad89632 --- /dev/null +++ b/libbitfury.h @@ -0,0 +1,34 @@ +/* + * Copyright 2014 Con Kolivas + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#ifndef LIBBITFURY_H +#define LIBBITFURY_H +#include "miner.h" +#include "driver-bitfury.h" + +void ms3steps(uint32_t *p); +uint32_t decnonce(uint32_t in); +void bitfury_work_to_payload(struct bitfury_payload *p, struct work *work); +void spi_config_reg(struct bitfury_info *info, int cfgreg, int ena); +void spi_set_freq(struct bitfury_info *info); +void spi_send_conf(struct bitfury_info *info); +void spi_send_init(struct bitfury_info *info); +void spi_clear_buf(struct bitfury_info *info); +void spi_add_buf(struct bitfury_info *info, const void *buf, const int sz); +void spi_add_break(struct bitfury_info *info); +void spi_add_fasync(struct bitfury_info *info, int n); +void spi_add_data(struct bitfury_info *info, uint16_t addr, const void *buf, int len); +bool spi_reset(struct cgpu_info *bitfury, struct bitfury_info *info); +bool mcp_spi_txrx(struct cgpu_info *bitfury, struct bitfury_info *info); +bool ftdi_spi_txrx(struct cgpu_info *bitfury, struct bitfury_info *info); +bool bitfury_checkresults(struct thr_info *thr, struct work *work, uint32_t nonce); +bool libbitfury_sendHashData(struct thr_info *thr, struct cgpu_info *bitfury, + struct bitfury_info *info, int chip_n); + +#endif /* LIBBITFURY_H */ diff --git a/linux-usb-cgminer b/linux-usb-cgminer new file mode 100644 index 0000000..f3d3b54 --- /dev/null +++ b/linux-usb-cgminer @@ -0,0 +1,305 @@ +How to setup a cgminer using xubuntu 11.04 live on a USB + +The master version of this document is here: + https://github.com/kanoi/linux-usb-cgminer + +The actual file is: + https://github.com/kanoi/linux-usb-cgminer/blob/master/linux-usb-cgminer + +The copy in cgminer (check to make sure it isn't older) is: + https://github.com/ckolivas/cgminer/blob/master/linux-usb-cgminer + +The original old verion on bitcointalk is: + https://bitcointalk.org/index.php?topic=28402.msg426741#msg426741 + +======== + +I have said to select English for the install process for 2 reasons: +1) I don't know any other spoken language very well +and +2) I'm not sure what problems installing under a different language +might cause (it will probably cause no problems but I don't know) + +Software +======== +Short hardware comment: +Your mining computer doesn't need any HDD or CD/DVD/BD as long as it has at +least 2GB of RAM, can boot USB, has some network connection to the internet +and of course a reasonable mining ATI graphics card +... Or you can boot a windows PC with the USB to only do mining ... and ignore +the system HDD ... wasting energy running the HDD (roughly 10 Watts per HDD) :) + +If you wish to install to an HDD instead of a USB, + see the changes to the instructions at the end + +To create the USB, you need of course a 4GB USB and temporarily need a PC +with a CD (or DVD/BD) writer, a USB port and of course an internet +connection to the PC + +1) Download the xubuntu 11.04 desktop live CD iso for amd64 + ( look here for mirrors: http://www.xubuntu.org/getubuntu ) + +2) Burn it to CD then boot that temporarily on any PC with a CD/DVD/BD and + a USB port (this and the next 2 step won't effect that PC) + Select "English" then select "Try Xubuntu without installing" + and wait for the desktop to appear + (this happens by default if you wait for the timeouts) + +3) Plug in your 4GB USB device and it should appear on the desktop - you can + leave it's contents as long as there is at least 2.8GB free + +4) Now run "Startup Disk Creator" in "Applications->System" + (the system menu is the little rat in the top left corner) + +(if you have no mouse you can get the menu with and navigate +the menu with the arrow keys and key) + +From here select the boot CD as the "Source" and the USB as the "Disk to use" +lastly move the slider to 2GB for reserved extra space + +The 2GB should be enough for modifications + +Click: "Make Install Disk" +After about 10-15 minutes you have a base xubuntu 11.04 boot USB +(you can shut down this computer now) + +5) Boot your cgminer PC with this USB stick, select "English" + then select "Try Xubuntu without installing" and wait for the desktop to + appear (this happens by default if you wait for the timeouts) + +6) Start a terminal + "Applications->Accessories->Terminal Emulator" + +7) sudo apt-get install openssh-server screen + + if you have a problem here then it's probably coz the internet isn't + available ... sort that out by reading elsewhere about routers etc + +8) sudo apt-get install fglrx fglrx-amdcccle fglrx-dev + sudo sync + sudo shutdown -r now + +N.B. always do a "sudo sync" and wait for it to finish every time before +shutting down the PC to ensure all data is written to the USB + +9) sudo aticonfig --lsa + this lists your ATI cards so you can see them + sudo aticonfig --adapter=all --odgt + this checks it can access all the cards ... + +10) sudo aticonfig --adapter=all --initial + this gets an error - no idea why but the xorg.conf is OK + sudo sync + sudo shutdown -r now + +11) sudo aticonfig --adapter=all --odgt + this checks it can access all the cards ... + +12) get AMD-APP-SDK-v2.4-lnx64.tgz from + http://developer.amd.com/sdks/amdappsdk/downloads/pages/default.aspx + ( http://developer.amd.com/Downloads/AMD-APP-SDK-v2.4-lnx64.tgz ) + + sudo su + cd /opt + (replace /home/ubuntu/ with wherever you put the file: ) + tar -xvzf /home/ubuntu/AMD-APP-SDK-v2.4-lnx64.tgz + + cd AMD-APP-SDK-v2.4-lnx64/ + cp -pv lib/x86_64/* /usr/lib/ + rsync -avl include/CL/ /usr/include/CL/ + tar -xvzf icd-registration.tgz + rsync -avl etc/OpenCL/ /etc/OpenCL/ + ldconfig + sync + shutdown -r now + + You now have an OpenCL enabled xubuntu + +13) cgminer: + sudo apt-get install curl + + get the binary linux cgminer + (see the bitcoin forum cgminer thread for where to get it) + https://bitcointalk.org/index.php?topic=28402.0 + + ./cgminer -n + this shows you the GPU's it found on your PC + See further below if you get an error regarding libtinfo.so.5 + +14) An OC option: + This is no longer needed since cgminer 2.* includes OC, however: + + sudo apt-get install libwxbase2.8-0 libwxgtk2.8-0 + + http://sourceforge.net/projects/amdovdrvctrl/ + for an Over/underclocking application and get the file listed below then: + sudo dpkg -i amdoverdrivectrl_1.2.1_amd64.deb + +15) set the screen saver to ONLY blank ... + + Move the mouse to the bottom of the screen and you see a set of icons like + on an Apple PC + Click on Settings, then in the Settings window "Screensaver" + Set "Mode:" to "Blank Screen Only" + +16) apt-get install ntpd + An accurate clock is always a good idea :) + +17) if you wish to ssh into the box you must set a password + to do this you simply have to be logged into it at the screen and type + + sudo passwd ubuntu + + it will prompt you (twice) to enter a password for the ubuntu account + + +Initial setup complete. + +======== + +If you want to SSH into the machine and run cgminer: + From a terminal on the miner display each time after you boot: + xhost + + + 'xhost +' isn't needed if you ssh into the machine with the same + username that the GUI boots into (which is 'ubuntu' in this case) + +Then after you ssh into the machine: + export DISPLAY=:0 +before running cgminer + +Also note, that you should force the screen to blank when mining if +the ATI card is displaying the screen (using the screen saver +application menu) +In my case it takes away 50Mh/s when the screen isn't blanked +It will auto blank - but make sure the blank is of course just blank +as mentioned above at 15) + + +This is of course just the basics ... but it should get you a computer +up and running and able to run cgminer + +======== + +You should keep an eye on USB disk space +The system logger writes log files in the /var/log/ directory +The two main ones that grow large are 'kern.log' and 'syslog' +If you want to keep them, save them away to some other computer +When space is low, just delete them e.g. + + sudo rm -i /var/log/syslog + sudo rm -i /var/log/kern.log + +The 'df' command will show you the current space e.g.: + + sudo df + +Filesystem 1K-blocks Used Available Use% Mounted on +aufs 2099420 892024 1100748 45% / +none 1015720 628 1015092 1% /dev +/dev/sda1 3909348 2837248 1072100 73% /cdrom +/dev/loop0 670848 670848 0 100% /rofs +none 1023772 136 1023636 1% /dev/shm +tmpfs 1023772 16 1023756 1% /tmp +none 1023772 124 1023648 1% /var/run +none 1023772 0 1023772 0% /var/lock + + +This shows the 2GB space allocated when you setup the USB as '/' (aufs) +In this example, it's currently 45% full with almost 1.1GB of free space + +======== + +The latest version (2.0.8) of cgminer is built with 11.10 (not 11.04) +If you get the following error when running the prebuilt version in 11.04: + + ./cgminer: error while loading shared libraries: libtinfo.so.5: cannot open shared object file: No such file or directory + +The fix is to simply link the old curses library to the new name e.g.: + + cd /lib64/ + sudo ln -s libncurses.so.5 libtinfo.so.5 + +======== + +If you wish to install to an HDD instead of a USB: +-------------------------------------------------- + +As per before: + +1) Download the xubuntu 11.04 desktop live CD iso for amd64 + ( look here for mirrors: http://www.xubuntu.org/getubuntu ) + +Then: + +2) Burn it to CD then boot that on your new mining PC + Select "English" then select "Install Xubuntu" + (you have 30 seconds to do this) + +3) When the Install window comes up - again select "English" and click "Forward" + +4) The next page will show you if you meet certain install requirements + (make sure you do meet them all) + Don't select the download option + The 3rd party option isn't needed for mining so ignore that also + + Click "Forward" + +5) With "Allocate drive space" it's probably easiest to say to use the + "Erase" option. + + This is just for mining right? :) + + However, if you have anything on the HDD that you want to keep - the + "Erase" install process will delete it - so back it up (quit the install) + Also make sure there are no OTHER HDD attached that it may erase also + i.e. only have attached the one HDD that you want to install onto unless + you know exactly what you are doing + + If you see the "Install Xubuntu 11.04 alongside 'something'" then that + just means that the HDD wasn't blank. + If you want to try this option - do that yourself and then skip to step + 7) below when you get to that. + + There are plenty of other options available if you select "Something else" + but I'm not going to go into all the details here other than to say that + my preferred partioning is: /boot = 1GB = ext2, swap = twice memory size, + / = 100GB = ext3 and the rest: /extra = ext3 + + Click "Forward" + +6) If you selected "Erase" then it allows you to choose the drive to install to + Then click "Install Now" + +7) "Where are you?" sort that out then click "Forward" + +8) "Keyboard layout" sort that out (use the default) then click "Forward" + +9) "Who are you?" The important one here is "Pick a username:" coz that's + the name you will need to ssh into, to access it remotely (and of course + the "Choose a Password" you set) + + If you set the "username" to anything but "ubuntu" then: wherever in this + document I have mentioned the username "ubuntu" you must of course use the + username you chose here instead of "ubuntu" + + Important: set it to "log in automatically" if you ever want to be able + to start cgminer without being in front of the computer since 'X' must + be running to use cgminer properly + That does of course mean that the computer isn't secure from anyone who + has access to it - but then again no computer that can automatically + reboot is secure from anyone who has access to the actual computer itself + + Then click "Forward" + +10) Of course when it completes click on "Restart Now" + ... and remove the Xubuntu CD when it asks you + +11) Wait for it to finish rebooting ... and it will auto login + (unless you didn't do step 9) "Important:") + +12) After it logs in, an upgrade popup for 11.10 (or later) will appear + Select "Don't Upgrade" + +13) Now go to step 6) of the USB script above for what to do next and that + covers everything else needed diff --git a/logging.c b/logging.c new file mode 100644 index 0000000..c54cbef --- /dev/null +++ b/logging.c @@ -0,0 +1,123 @@ +/* + * Copyright 2011-2012 Con Kolivas + * Copyright 2013 Andrew Smith + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#include "config.h" + +#include + +#include "logging.h" +#include "miner.h" + +bool opt_debug = false; +bool opt_log_output = false; + +/* per default priorities higher than LOG_NOTICE are logged */ +int opt_log_level = LOG_NOTICE; +FILE * g_log_file = NULL; + +bool g_logfile_enable = false; +char g_logfile_path[256] = {0}; +char g_logfile_openflag[32] = {0}; + +static void my_log_curses(int prio, const char *datetime, const char *str, bool force) +{ + if (opt_quiet && prio != LOG_ERR) + return; + + /* Mutex could be locked by dead thread on shutdown so forcelog will + * invalidate any console lock status. */ + if (force) { + mutex_trylock(&console_lock); + mutex_unlock(&console_lock); + } +#ifdef HAVE_CURSES + extern bool use_curses; + if (use_curses && log_curses_only(prio, datetime, str)) + ; + else +#endif + { + mutex_lock(&console_lock); + printf("%s%s%s", datetime, str, " \n"); + mutex_unlock(&console_lock); + } +} + +/* high-level logging function, based on global opt_log_level */ + +/* + * log function + */ +void _applog(int prio, const char *str, bool force) +{ +#ifdef HAVE_SYSLOG_H + if (use_syslog) { + syslog(LOG_LOCAL0 | prio, "%s", str); + } +#else + if (0) {} +#endif + else { + char datetime[64]; + struct timeval tv = {0, 0}; + struct tm *tm; + + cgtime(&tv); + + const time_t tmp_time = tv.tv_sec; + tm = localtime(&tmp_time); + + snprintf(datetime, sizeof(datetime), " [%d-%02d-%02d %02d:%02d:%02d] ", + tm->tm_year + 1900, + tm->tm_mon + 1, + tm->tm_mday, + tm->tm_hour, + tm->tm_min, + tm->tm_sec); + + /* Only output to stderr if it's not going to the screen as well */ + if (!isatty(fileno((FILE *)stderr))) { + fprintf(stderr, "%s%s\n", datetime, str); /* atomic write to stderr */ + fflush(stderr); + } + if(g_logfile_enable) { + if(!g_log_file) { + g_log_file = fopen(g_logfile_path, g_logfile_openflag); + } + if(g_log_file) { + fwrite(datetime, strlen(datetime), 1, g_log_file); + fwrite(str, strlen(str), 1, g_log_file); + fwrite("\n", 1, 1, g_log_file); + fflush(g_log_file); + } + } + my_log_curses(prio, datetime, str, force); + } +} + +void _simplelog(int prio, const char *str, bool force) +{ +#ifdef HAVE_SYSLOG_H + if (use_syslog) { + syslog(LOG_LOCAL0 | prio, "%s", str); + } +#else + if (0) {} +#endif + else { + /* Only output to stderr if it's not going to the screen as well */ + if (!isatty(fileno((FILE *)stderr))) { + fprintf(stderr, "%s\n", str); /* atomic write to stderr */ + fflush(stderr); + } + + my_log_curses(prio, "", str, force); + } +} diff --git a/logging.h b/logging.h new file mode 100644 index 0000000..4483098 --- /dev/null +++ b/logging.h @@ -0,0 +1,130 @@ +#ifndef __LOGGING_H__ +#define __LOGGING_H__ + +#include "config.h" +#include +#include + +#ifdef HAVE_SYSLOG_H +#include +#else +enum { + LOG_ERR, + LOG_WARNING, + LOG_NOTICE, + LOG_INFO, + LOG_DEBUG, +}; +#endif + +/* debug flags */ +extern bool opt_debug; +extern bool opt_log_output; +extern bool opt_realquiet; +extern bool want_per_device_stats; + +/* global log_level, messages with lower or equal prio are logged */ +extern int opt_log_level; + +#define LOGBUFSIZ 2048 + +extern void _applog(int prio, const char *str, bool force); +extern void _simplelog(int prio, const char *str, bool force); + +#define IN_FMT_FFL " in %s %s():%d" + +#define applog(prio, fmt, ...) do { \ + if (opt_debug || prio != LOG_DEBUG) { \ + if (use_syslog || opt_log_output || prio <= opt_log_level) { \ + char tmp42[LOGBUFSIZ]; \ + snprintf(tmp42, sizeof(tmp42), fmt, ##__VA_ARGS__); \ + _applog(prio, tmp42, false); \ + } \ + } \ +} while (0) + +#define simplelog(prio, fmt, ...) do { \ + if (opt_debug || prio != LOG_DEBUG) { \ + if (use_syslog || opt_log_output || prio <= opt_log_level) { \ + char tmp42[LOGBUFSIZ]; \ + snprintf(tmp42, sizeof(tmp42), fmt, ##__VA_ARGS__); \ + _simplelog(prio, tmp42, false); \ + } \ + } \ +} while (0) + +#define applogsiz(prio, _SIZ, fmt, ...) do { \ + if (opt_debug || prio != LOG_DEBUG) { \ + if (use_syslog || opt_log_output || prio <= opt_log_level) { \ + char tmp42[_SIZ]; \ + snprintf(tmp42, sizeof(tmp42), fmt, ##__VA_ARGS__); \ + _applog(prio, tmp42, false); \ + } \ + } \ +} while (0) + +#define forcelog(prio, fmt, ...) do { \ + if (opt_debug || prio != LOG_DEBUG) { \ + if (use_syslog || opt_log_output || prio <= opt_log_level) { \ + char tmp42[LOGBUFSIZ]; \ + snprintf(tmp42, sizeof(tmp42), fmt, ##__VA_ARGS__); \ + _applog(prio, tmp42, true); \ + } \ + } \ +} while (0) + +#define quit(status, fmt, ...) do { \ + if (fmt) { \ + char tmp42[LOGBUFSIZ]; \ + snprintf(tmp42, sizeof(tmp42), fmt, ##__VA_ARGS__); \ + _applog(LOG_ERR, tmp42, true); \ + } \ + _quit(status); \ +} while (0) + +#define early_quit(status, fmt, ...) do { \ + if (fmt) { \ + char tmp42[LOGBUFSIZ]; \ + snprintf(tmp42, sizeof(tmp42), fmt, ##__VA_ARGS__); \ + _applog(LOG_ERR, tmp42, true); \ + } \ + __quit(status, false); \ +} while (0) + +#define quithere(status, fmt, ...) do { \ + if (fmt) { \ + char tmp42[LOGBUFSIZ]; \ + snprintf(tmp42, sizeof(tmp42), fmt IN_FMT_FFL, \ + ##__VA_ARGS__, __FILE__, __func__, __LINE__); \ + _applog(LOG_ERR, tmp42, true); \ + } \ + _quit(status); \ +} while (0) + +#define quitfrom(status, _file, _func, _line, fmt, ...) do { \ + if (fmt) { \ + char tmp42[LOGBUFSIZ]; \ + snprintf(tmp42, sizeof(tmp42), fmt IN_FMT_FFL, \ + ##__VA_ARGS__, _file, _func, _line); \ + _applog(LOG_ERR, tmp42, true); \ + } \ + _quit(status); \ +} while (0) + +#ifdef HAVE_CURSES + +#define wlog(fmt, ...) do { \ + char tmp42[LOGBUFSIZ]; \ + snprintf(tmp42, sizeof(tmp42), fmt, ##__VA_ARGS__); \ + _wlog(tmp42); \ +} while (0) + +#define wlogprint(fmt, ...) do { \ + char tmp42[LOGBUFSIZ]; \ + snprintf(tmp42, sizeof(tmp42), fmt, ##__VA_ARGS__); \ + _wlogprint(tmp42); \ +} while (0) + +#endif + +#endif /* __LOGGING_H__ */ diff --git a/m4/00gnulib.m4 b/m4/00gnulib.m4 new file mode 100644 index 0000000..7feed46 --- /dev/null +++ b/m4/00gnulib.m4 @@ -0,0 +1,30 @@ +# 00gnulib.m4 serial 2 +dnl Copyright (C) 2009-2011 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +dnl This file must be named something that sorts before all other +dnl gnulib-provided .m4 files. It is needed until such time as we can +dnl assume Autoconf 2.64, with its improved AC_DEFUN_ONCE semantics. + +# AC_DEFUN_ONCE([NAME], VALUE) +# ---------------------------- +# Define NAME to expand to VALUE on the first use (whether by direct +# expansion, or by AC_REQUIRE), and to nothing on all subsequent uses. +# Avoid bugs in AC_REQUIRE in Autoconf 2.63 and earlier. This +# definition is slower than the version in Autoconf 2.64, because it +# can only use interfaces that existed since 2.59; but it achieves the +# same effect. Quoting is necessary to avoid confusing Automake. +m4_version_prereq([2.63.263], [], +[m4_define([AC][_DEFUN_ONCE], + [AC][_DEFUN([$1], + [AC_REQUIRE([_gl_DEFUN_ONCE([$1])], + [m4_indir([_gl_DEFUN_ONCE([$1])])])])]dnl +[AC][_DEFUN([_gl_DEFUN_ONCE([$1])], [$2])])]) + +# gl_00GNULIB +# ----------- +# Witness macro that this file has been included. Needed to force +# Automake to include this file prior to all other gnulib .m4 files. +AC_DEFUN([gl_00GNULIB]) diff --git a/m4/extensions.m4 b/m4/extensions.m4 new file mode 100644 index 0000000..1330503 --- /dev/null +++ b/m4/extensions.m4 @@ -0,0 +1,118 @@ +# serial 9 -*- Autoconf -*- +# Enable extensions on systems that normally disable them. + +# Copyright (C) 2003, 2006-2011 Free Software Foundation, Inc. +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This definition of AC_USE_SYSTEM_EXTENSIONS is stolen from CVS +# Autoconf. Perhaps we can remove this once we can assume Autoconf +# 2.62 or later everywhere, but since CVS Autoconf mutates rapidly +# enough in this area it's likely we'll need to redefine +# AC_USE_SYSTEM_EXTENSIONS for quite some time. + +# If autoconf reports a warning +# warning: AC_COMPILE_IFELSE was called before AC_USE_SYSTEM_EXTENSIONS +# or warning: AC_RUN_IFELSE was called before AC_USE_SYSTEM_EXTENSIONS +# the fix is +# 1) to ensure that AC_USE_SYSTEM_EXTENSIONS is never directly invoked +# but always AC_REQUIREd, +# 2) to ensure that for each occurrence of +# AC_REQUIRE([AC_USE_SYSTEM_EXTENSIONS]) +# or +# AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS]) +# the corresponding gnulib module description has 'extensions' among +# its dependencies. This will ensure that the gl_USE_SYSTEM_EXTENSIONS +# invocation occurs in gl_EARLY, not in gl_INIT. + +# AC_USE_SYSTEM_EXTENSIONS +# ------------------------ +# Enable extensions on systems that normally disable them, +# typically due to standards-conformance issues. +# Remember that #undef in AH_VERBATIM gets replaced with #define by +# AC_DEFINE. The goal here is to define all known feature-enabling +# macros, then, if reports of conflicts are made, disable macros that +# cause problems on some platforms (such as __EXTENSIONS__). +AC_DEFUN_ONCE([AC_USE_SYSTEM_EXTENSIONS], +[AC_BEFORE([$0], [AC_COMPILE_IFELSE])dnl +AC_BEFORE([$0], [AC_RUN_IFELSE])dnl + + AC_REQUIRE([AC_CANONICAL_HOST]) + + AC_CHECK_HEADER([minix/config.h], [MINIX=yes], [MINIX=]) + if test "$MINIX" = yes; then + AC_DEFINE([_POSIX_SOURCE], [1], + [Define to 1 if you need to in order for `stat' and other + things to work.]) + AC_DEFINE([_POSIX_1_SOURCE], [2], + [Define to 2 if the system does not provide POSIX.1 features + except with this defined.]) + AC_DEFINE([_MINIX], [1], + [Define to 1 if on MINIX.]) + fi + + dnl HP-UX 11.11 defines mbstate_t only if _XOPEN_SOURCE is defined to 500, + dnl regardless of whether the flags -Ae or _D_HPUX_SOURCE=1 are already + dnl provided. + case "$host_os" in + hpux*) + AC_DEFINE([_XOPEN_SOURCE], [500], + [Define to 500 only on HP-UX.]) + ;; + esac + + AH_VERBATIM([__EXTENSIONS__], +[/* Enable extensions on AIX 3, Interix. */ +#ifndef _ALL_SOURCE +# undef _ALL_SOURCE +#endif +/* Enable GNU extensions on systems that have them. */ +#ifndef _GNU_SOURCE +# undef _GNU_SOURCE +#endif +/* Enable threading extensions on Solaris. */ +#ifndef _POSIX_PTHREAD_SEMANTICS +# undef _POSIX_PTHREAD_SEMANTICS +#endif +/* Enable extensions on HP NonStop. */ +#ifndef _TANDEM_SOURCE +# undef _TANDEM_SOURCE +#endif +/* Enable general extensions on Solaris. */ +#ifndef __EXTENSIONS__ +# undef __EXTENSIONS__ +#endif +]) + AC_CACHE_CHECK([whether it is safe to define __EXTENSIONS__], + [ac_cv_safe_to_define___extensions__], + [AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([[ +# define __EXTENSIONS__ 1 + ]AC_INCLUDES_DEFAULT])], + [ac_cv_safe_to_define___extensions__=yes], + [ac_cv_safe_to_define___extensions__=no])]) + test $ac_cv_safe_to_define___extensions__ = yes && + AC_DEFINE([__EXTENSIONS__]) + AC_DEFINE([_ALL_SOURCE]) + AC_DEFINE([_GNU_SOURCE]) + AC_DEFINE([_POSIX_PTHREAD_SEMANTICS]) + AC_DEFINE([_TANDEM_SOURCE]) +])# AC_USE_SYSTEM_EXTENSIONS + +# gl_USE_SYSTEM_EXTENSIONS +# ------------------------ +# Enable extensions on systems that normally disable them, +# typically due to standards-conformance issues. +AC_DEFUN_ONCE([gl_USE_SYSTEM_EXTENSIONS], +[ + dnl Require this macro before AC_USE_SYSTEM_EXTENSIONS. + dnl gnulib does not need it. But if it gets required by third-party macros + dnl after AC_USE_SYSTEM_EXTENSIONS is required, autoconf 2.62..2.63 emit a + dnl warning: "AC_COMPILE_IFELSE was called before AC_USE_SYSTEM_EXTENSIONS". + dnl Note: We can do this only for one of the macros AC_AIX, AC_GNU_SOURCE, + dnl AC_MINIX. If people still use AC_AIX or AC_MINIX, they are out of luck. + AC_REQUIRE([AC_GNU_SOURCE]) + + AC_REQUIRE([AC_USE_SYSTEM_EXTENSIONS]) +]) diff --git a/m4/gnulib-cache.m4 b/m4/gnulib-cache.m4 new file mode 100644 index 0000000..09970a0 --- /dev/null +++ b/m4/gnulib-cache.m4 @@ -0,0 +1,38 @@ +# Copyright (C) 2002-2011 Free Software Foundation, Inc. +# +# This file is free software, distributed under the terms of the GNU +# General Public License. As a special exception to the GNU General +# Public License, this file may be distributed as part of a program +# that contains a configuration script generated by Autoconf, under +# the same distribution terms as the rest of that program. +# +# Generated by gnulib-tool. +# +# This file represents the specification of how gnulib-tool is used. +# It acts as a cache: It is written and read by gnulib-tool. +# In projects that use version control, this file is meant to be put under +# version control, like the configure.ac and various Makefile.am files. + + +# Specification in the form of a command-line invocation: +# gnulib-tool --import --dir=. --lib=libgnu --source-base=lib --m4-base=m4 --doc-base=doc --tests-base=tests --aux-dir=. --no-conditional-dependencies --no-libtool --macro-prefix=gl --no-vc-files memmem sigaction signal + +# Specification in the form of a few gnulib-tool.m4 macro invocations: +gl_LOCAL_DIR([]) +gl_MODULES([ + memmem + sigaction + signal +]) +gl_AVOID([]) +gl_SOURCE_BASE([lib]) +gl_M4_BASE([m4]) +gl_PO_BASE([]) +gl_DOC_BASE([doc]) +gl_TESTS_BASE([tests]) +gl_LIB([libgnu]) +gl_MAKEFILE_NAME([]) +gl_MACRO_PREFIX([gl]) +gl_PO_DOMAIN([]) +gl_WITNESS_C_DOMAIN([]) +gl_VC_FILES([false]) diff --git a/m4/gnulib-common.m4 b/m4/gnulib-common.m4 new file mode 100644 index 0000000..843efe0 --- /dev/null +++ b/m4/gnulib-common.m4 @@ -0,0 +1,282 @@ +# gnulib-common.m4 serial 26 +dnl Copyright (C) 2007-2011 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +# gl_COMMON +# is expanded unconditionally through gnulib-tool magic. +AC_DEFUN([gl_COMMON], [ + dnl Use AC_REQUIRE here, so that the code is expanded once only. + AC_REQUIRE([gl_00GNULIB]) + AC_REQUIRE([gl_COMMON_BODY]) +]) +AC_DEFUN([gl_COMMON_BODY], [ + AH_VERBATIM([isoc99_inline], +[/* Work around a bug in Apple GCC 4.0.1 build 5465: In C99 mode, it supports + the ISO C 99 semantics of 'extern inline' (unlike the GNU C semantics of + earlier versions), but does not display it by setting __GNUC_STDC_INLINE__. + __APPLE__ && __MACH__ test for MacOS X. + __APPLE_CC__ tests for the Apple compiler and its version. + __STDC_VERSION__ tests for the C99 mode. */ +#if defined __APPLE__ && defined __MACH__ && __APPLE_CC__ >= 5465 && !defined __cplusplus && __STDC_VERSION__ >= 199901L && !defined __GNUC_STDC_INLINE__ +# define __GNUC_STDC_INLINE__ 1 +#endif]) + AH_VERBATIM([unused_parameter], +[/* Define as a marker that can be attached to declarations that might not + be used. This helps to reduce warnings, such as from + GCC -Wunused-parameter. */ +#if __GNUC__ >= 3 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 7) +# define _GL_UNUSED __attribute__ ((__unused__)) +#else +# define _GL_UNUSED +#endif +/* The name _UNUSED_PARAMETER_ is an earlier spelling, although the name + is a misnomer outside of parameter lists. */ +#define _UNUSED_PARAMETER_ _GL_UNUSED + +/* The __pure__ attribute was added in gcc 2.96. */ +#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 96) +# define _GL_ATTRIBUTE_PURE __attribute__ ((__pure__)) +#else +# define _GL_ATTRIBUTE_PURE /* empty */ +#endif + +/* The __const__ attribute was added in gcc 2.95. */ +#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95) +# define _GL_ATTRIBUTE_CONST __attribute__ ((__const__)) +#else +# define _GL_ATTRIBUTE_CONST /* empty */ +#endif +]) + dnl Preparation for running test programs: + dnl Tell glibc to write diagnostics from -D_FORTIFY_SOURCE=2 to stderr, not + dnl to /dev/tty, so they can be redirected to log files. Such diagnostics + dnl arise e.g., in the macros gl_PRINTF_DIRECTIVE_N, gl_SNPRINTF_DIRECTIVE_N. + LIBC_FATAL_STDERR_=1 + export LIBC_FATAL_STDERR_ +]) + +# gl_MODULE_INDICATOR_CONDITION +# expands to a C preprocessor expression that evaluates to 1 or 0, depending +# whether a gnulib module that has been requested shall be considered present +# or not. +m4_define([gl_MODULE_INDICATOR_CONDITION], [1]) + +# gl_MODULE_INDICATOR_SET_VARIABLE([modulename]) +# sets the shell variable that indicates the presence of the given module to +# a C preprocessor expression that will evaluate to 1. +AC_DEFUN([gl_MODULE_INDICATOR_SET_VARIABLE], +[ + gl_MODULE_INDICATOR_SET_VARIABLE_AUX( + [GNULIB_[]m4_translit([[$1]], + [abcdefghijklmnopqrstuvwxyz./-], + [ABCDEFGHIJKLMNOPQRSTUVWXYZ___])], + [gl_MODULE_INDICATOR_CONDITION]) +]) + +# gl_MODULE_INDICATOR_SET_VARIABLE_AUX([variable]) +# modifies the shell variable to include the gl_MODULE_INDICATOR_CONDITION. +# The shell variable's value is a C preprocessor expression that evaluates +# to 0 or 1. +AC_DEFUN([gl_MODULE_INDICATOR_SET_VARIABLE_AUX], +[ + m4_if(m4_defn([gl_MODULE_INDICATOR_CONDITION]), [1], + [ + dnl Simplify the expression VALUE || 1 to 1. + $1=1 + ], + [gl_MODULE_INDICATOR_SET_VARIABLE_AUX_OR([$1], + [gl_MODULE_INDICATOR_CONDITION])]) +]) + +# gl_MODULE_INDICATOR_SET_VARIABLE_AUX_OR([variable], [condition]) +# modifies the shell variable to include the given condition. The shell +# variable's value is a C preprocessor expression that evaluates to 0 or 1. +AC_DEFUN([gl_MODULE_INDICATOR_SET_VARIABLE_AUX_OR], +[ + dnl Simplify the expression 1 || CONDITION to 1. + if test "$[]$1" != 1; then + dnl Simplify the expression 0 || CONDITION to CONDITION. + if test "$[]$1" = 0; then + $1=$2 + else + $1="($[]$1 || $2)" + fi + fi +]) + +# gl_MODULE_INDICATOR([modulename]) +# defines a C macro indicating the presence of the given module +# in a location where it can be used. +# | Value | Value | +# | in lib/ | in tests/ | +# --------------------------------------------+---------+-----------+ +# Module present among main modules: | 1 | 1 | +# --------------------------------------------+---------+-----------+ +# Module present among tests-related modules: | 0 | 1 | +# --------------------------------------------+---------+-----------+ +# Module not present at all: | 0 | 0 | +# --------------------------------------------+---------+-----------+ +AC_DEFUN([gl_MODULE_INDICATOR], +[ + AC_DEFINE_UNQUOTED([GNULIB_]m4_translit([[$1]], + [abcdefghijklmnopqrstuvwxyz./-], + [ABCDEFGHIJKLMNOPQRSTUVWXYZ___]), + [gl_MODULE_INDICATOR_CONDITION], + [Define to a C preprocessor expression that evaluates to 1 or 0, + depending whether the gnulib module $1 shall be considered present.]) +]) + +# gl_MODULE_INDICATOR_FOR_TESTS([modulename]) +# defines a C macro indicating the presence of the given module +# in lib or tests. This is useful to determine whether the module +# should be tested. +# | Value | Value | +# | in lib/ | in tests/ | +# --------------------------------------------+---------+-----------+ +# Module present among main modules: | 1 | 1 | +# --------------------------------------------+---------+-----------+ +# Module present among tests-related modules: | 1 | 1 | +# --------------------------------------------+---------+-----------+ +# Module not present at all: | 0 | 0 | +# --------------------------------------------+---------+-----------+ +AC_DEFUN([gl_MODULE_INDICATOR_FOR_TESTS], +[ + AC_DEFINE([GNULIB_TEST_]m4_translit([[$1]], + [abcdefghijklmnopqrstuvwxyz./-], + [ABCDEFGHIJKLMNOPQRSTUVWXYZ___]), [1], + [Define to 1 when the gnulib module $1 should be tested.]) +]) + +# gl_ASSERT_NO_GNULIB_POSIXCHECK +# asserts that there will never be a need to #define GNULIB_POSIXCHECK. +# and thereby enables an optimization of configure and config.h. +# Used by Emacs. +AC_DEFUN([gl_ASSERT_NO_GNULIB_POSIXCHECK], +[ + dnl Override gl_WARN_ON_USE_PREPARE. + dnl But hide this definition from 'aclocal'. + AC_DEFUN([gl_W][ARN_ON_USE_PREPARE], []) +]) + +# gl_ASSERT_NO_GNULIB_TESTS +# asserts that there will be no gnulib tests in the scope of the configure.ac +# and thereby enables an optimization of config.h. +# Used by Emacs. +AC_DEFUN([gl_ASSERT_NO_GNULIB_TESTS], +[ + dnl Override gl_MODULE_INDICATOR_FOR_TESTS. + AC_DEFUN([gl_MODULE_INDICATOR_FOR_TESTS], []) +]) + +# Test whether exists. +# Set HAVE_FEATURES_H. +AC_DEFUN([gl_FEATURES_H], +[ + AC_CHECK_HEADERS_ONCE([features.h]) + if test $ac_cv_header_features_h = yes; then + HAVE_FEATURES_H=1 + else + HAVE_FEATURES_H=0 + fi + AC_SUBST([HAVE_FEATURES_H]) +]) + +# m4_foreach_w +# is a backport of autoconf-2.59c's m4_foreach_w. +# Remove this macro when we can assume autoconf >= 2.60. +m4_ifndef([m4_foreach_w], + [m4_define([m4_foreach_w], + [m4_foreach([$1], m4_split(m4_normalize([$2]), [ ]), [$3])])]) + +# AS_VAR_IF(VAR, VALUE, [IF-MATCH], [IF-NOT-MATCH]) +# ---------------------------------------------------- +# Backport of autoconf-2.63b's macro. +# Remove this macro when we can assume autoconf >= 2.64. +m4_ifndef([AS_VAR_IF], +[m4_define([AS_VAR_IF], +[AS_IF([test x"AS_VAR_GET([$1])" = x""$2], [$3], [$4])])]) + +# AC_PROG_MKDIR_P +# is a backport of autoconf-2.60's AC_PROG_MKDIR_P, with a fix +# for interoperability with automake-1.9.6 from autoconf-2.62. +# Remove this macro when we can assume autoconf >= 2.62 or +# autoconf >= 2.60 && automake >= 1.10. +m4_ifdef([AC_PROG_MKDIR_P], [ + dnl For automake-1.9.6 && autoconf < 2.62: Ensure MKDIR_P is AC_SUBSTed. + m4_define([AC_PROG_MKDIR_P], + m4_defn([AC_PROG_MKDIR_P])[ + AC_SUBST([MKDIR_P])])], [ + dnl For autoconf < 2.60: Backport of AC_PROG_MKDIR_P. + AC_DEFUN_ONCE([AC_PROG_MKDIR_P], + [AC_REQUIRE([AM_PROG_MKDIR_P])dnl defined by automake + MKDIR_P='$(mkdir_p)' + AC_SUBST([MKDIR_P])])]) + +# AC_C_RESTRICT +# This definition overrides the AC_C_RESTRICT macro from autoconf 2.60..2.61, +# so that mixed use of GNU C and GNU C++ and mixed use of Sun C and Sun C++ +# works. +# This definition can be removed once autoconf >= 2.62 can be assumed. +m4_if(m4_version_compare(m4_defn([m4_PACKAGE_VERSION]),[2.62]),[-1],[ +AC_DEFUN([AC_C_RESTRICT], +[AC_CACHE_CHECK([for C/C++ restrict keyword], [ac_cv_c_restrict], + [ac_cv_c_restrict=no + # The order here caters to the fact that C++ does not require restrict. + for ac_kw in __restrict __restrict__ _Restrict restrict; do + AC_COMPILE_IFELSE([AC_LANG_PROGRAM( + [[typedef int * int_ptr; + int foo (int_ptr $ac_kw ip) { + return ip[0]; + }]], + [[int s[1]; + int * $ac_kw t = s; + t[0] = 0; + return foo(t)]])], + [ac_cv_c_restrict=$ac_kw]) + test "$ac_cv_c_restrict" != no && break + done + ]) + AH_VERBATIM([restrict], +[/* Define to the equivalent of the C99 'restrict' keyword, or to + nothing if this is not supported. Do not define if restrict is + supported directly. */ +#undef restrict +/* Work around a bug in Sun C++: it does not support _Restrict, even + though the corresponding Sun C compiler does, which causes + "#define restrict _Restrict" in the previous line. Perhaps some future + version of Sun C++ will work with _Restrict; if so, it'll probably + define __RESTRICT, just as Sun C does. */ +#if defined __SUNPRO_CC && !defined __RESTRICT +# define _Restrict +#endif]) + case $ac_cv_c_restrict in + restrict) ;; + no) AC_DEFINE([restrict], []) ;; + *) AC_DEFINE_UNQUOTED([restrict], [$ac_cv_c_restrict]) ;; + esac +]) +]) + +# gl_BIGENDIAN +# is like AC_C_BIGENDIAN, except that it can be AC_REQUIREd. +# Note that AC_REQUIRE([AC_C_BIGENDIAN]) does not work reliably because some +# macros invoke AC_C_BIGENDIAN with arguments. +AC_DEFUN([gl_BIGENDIAN], +[ + AC_C_BIGENDIAN +]) + +# gl_CACHE_VAL_SILENT(cache-id, command-to-set-it) +# is like AC_CACHE_VAL(cache-id, command-to-set-it), except that it does not +# output a spurious "(cached)" mark in the midst of other configure output. +# This macro should be used instead of AC_CACHE_VAL when it is not surrounded +# by an AC_MSG_CHECKING/AC_MSG_RESULT pair. +AC_DEFUN([gl_CACHE_VAL_SILENT], +[ + saved_as_echo_n="$as_echo_n" + as_echo_n=':' + AC_CACHE_VAL([$1], [$2]) + as_echo_n="$saved_as_echo_n" +]) diff --git a/m4/gnulib-comp.m4 b/m4/gnulib-comp.m4 new file mode 100644 index 0000000..ba727d7 --- /dev/null +++ b/m4/gnulib-comp.m4 @@ -0,0 +1,268 @@ +# DO NOT EDIT! GENERATED AUTOMATICALLY! +# Copyright (C) 2002-2011 Free Software Foundation, Inc. +# +# This file is free software, distributed under the terms of the GNU +# General Public License. As a special exception to the GNU General +# Public License, this file may be distributed as part of a program +# that contains a configuration script generated by Autoconf, under +# the same distribution terms as the rest of that program. +# +# Generated by gnulib-tool. +# +# This file represents the compiled summary of the specification in +# gnulib-cache.m4. It lists the computed macro invocations that need +# to be invoked from configure.ac. +# In projects that use version control, this file can be treated like +# other built files. + + +# This macro should be invoked from ./configure.ac, in the section +# "Checks for programs", right after AC_PROG_CC, and certainly before +# any checks for libraries, header files, types and library functions. +AC_DEFUN([gl_EARLY], +[ + m4_pattern_forbid([^gl_[A-Z]])dnl the gnulib macro namespace + m4_pattern_allow([^gl_ES$])dnl a valid locale name + m4_pattern_allow([^gl_LIBOBJS$])dnl a variable + m4_pattern_allow([^gl_LTLIBOBJS$])dnl a variable + AC_REQUIRE([AC_PROG_RANLIB]) + # Code from module arg-nonnull: + # Code from module c++defs: + # Code from module extensions: + AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS]) + # Code from module include_next: + # Code from module memchr: + # Code from module memmem: + # Code from module memmem-simple: + # Code from module multiarch: + # Code from module sigaction: + # Code from module signal: + # Code from module sigprocmask: + # Code from module stddef: + # Code from module stdint: + # Code from module string: + # Code from module warn-on-use: +]) + +# This macro should be invoked from ./configure.ac, in the section +# "Check for header files, types and library functions". +AC_DEFUN([gl_INIT], +[ + AM_CONDITIONAL([GL_COND_LIBTOOL], [false]) + gl_cond_libtool=false + gl_libdeps= + gl_ltlibdeps= + gl_m4_base='m4' + m4_pushdef([AC_LIBOBJ], m4_defn([gl_LIBOBJ])) + m4_pushdef([AC_REPLACE_FUNCS], m4_defn([gl_REPLACE_FUNCS])) + m4_pushdef([AC_LIBSOURCES], m4_defn([gl_LIBSOURCES])) + m4_pushdef([gl_LIBSOURCES_LIST], []) + m4_pushdef([gl_LIBSOURCES_DIR], []) + gl_COMMON + gl_source_base='lib' +gl_FUNC_MEMCHR +if test $HAVE_MEMCHR = 0 || test $REPLACE_MEMCHR = 1; then + AC_LIBOBJ([memchr]) + gl_PREREQ_MEMCHR +fi +gl_STRING_MODULE_INDICATOR([memchr]) +gl_FUNC_MEMMEM +if test $HAVE_MEMMEM = 0 || test $REPLACE_MEMMEM = 1; then + AC_LIBOBJ([memmem]) +fi +gl_FUNC_MEMMEM_SIMPLE +if test $HAVE_MEMMEM = 0 || test $REPLACE_MEMMEM = 1; then + AC_LIBOBJ([memmem]) +fi +gl_STRING_MODULE_INDICATOR([memmem]) +gl_MULTIARCH +gl_SIGACTION +if test $HAVE_SIGACTION = 0; then + AC_LIBOBJ([sigaction]) + gl_PREREQ_SIGACTION +fi +gl_SIGNAL_MODULE_INDICATOR([sigaction]) +gl_SIGNAL_H +gl_SIGNALBLOCKING +if test $HAVE_POSIX_SIGNALBLOCKING = 0; then + AC_LIBOBJ([sigprocmask]) + gl_PREREQ_SIGPROCMASK +fi +gl_SIGNAL_MODULE_INDICATOR([sigprocmask]) +gl_STDDEF_H +gl_STDINT_H +gl_HEADER_STRING_H + # End of code from modules + m4_ifval(gl_LIBSOURCES_LIST, [ + m4_syscmd([test ! -d ]m4_defn([gl_LIBSOURCES_DIR])[ || + for gl_file in ]gl_LIBSOURCES_LIST[ ; do + if test ! -r ]m4_defn([gl_LIBSOURCES_DIR])[/$gl_file ; then + echo "missing file ]m4_defn([gl_LIBSOURCES_DIR])[/$gl_file" >&2 + exit 1 + fi + done])dnl + m4_if(m4_sysval, [0], [], + [AC_FATAL([expected source file, required through AC_LIBSOURCES, not found])]) + ]) + m4_popdef([gl_LIBSOURCES_DIR]) + m4_popdef([gl_LIBSOURCES_LIST]) + m4_popdef([AC_LIBSOURCES]) + m4_popdef([AC_REPLACE_FUNCS]) + m4_popdef([AC_LIBOBJ]) + AC_CONFIG_COMMANDS_PRE([ + gl_libobjs= + gl_ltlibobjs= + if test -n "$gl_LIBOBJS"; then + # Remove the extension. + sed_drop_objext='s/\.o$//;s/\.obj$//' + for i in `for i in $gl_LIBOBJS; do echo "$i"; done | sed -e "$sed_drop_objext" | sort | uniq`; do + gl_libobjs="$gl_libobjs $i.$ac_objext" + gl_ltlibobjs="$gl_ltlibobjs $i.lo" + done + fi + AC_SUBST([gl_LIBOBJS], [$gl_libobjs]) + AC_SUBST([gl_LTLIBOBJS], [$gl_ltlibobjs]) + ]) + gltests_libdeps= + gltests_ltlibdeps= + m4_pushdef([AC_LIBOBJ], m4_defn([gltests_LIBOBJ])) + m4_pushdef([AC_REPLACE_FUNCS], m4_defn([gltests_REPLACE_FUNCS])) + m4_pushdef([AC_LIBSOURCES], m4_defn([gltests_LIBSOURCES])) + m4_pushdef([gltests_LIBSOURCES_LIST], []) + m4_pushdef([gltests_LIBSOURCES_DIR], []) + gl_COMMON + gl_source_base='tests' +changequote(,)dnl + gltests_WITNESS=IN_`echo "${PACKAGE-$PACKAGE_TARNAME}" | LC_ALL=C tr abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ | LC_ALL=C sed -e 's/[^A-Z0-9_]/_/g'`_GNULIB_TESTS +changequote([, ])dnl + AC_SUBST([gltests_WITNESS]) + gl_module_indicator_condition=$gltests_WITNESS + m4_pushdef([gl_MODULE_INDICATOR_CONDITION], [$gl_module_indicator_condition]) + m4_popdef([gl_MODULE_INDICATOR_CONDITION]) + m4_ifval(gltests_LIBSOURCES_LIST, [ + m4_syscmd([test ! -d ]m4_defn([gltests_LIBSOURCES_DIR])[ || + for gl_file in ]gltests_LIBSOURCES_LIST[ ; do + if test ! -r ]m4_defn([gltests_LIBSOURCES_DIR])[/$gl_file ; then + echo "missing file ]m4_defn([gltests_LIBSOURCES_DIR])[/$gl_file" >&2 + exit 1 + fi + done])dnl + m4_if(m4_sysval, [0], [], + [AC_FATAL([expected source file, required through AC_LIBSOURCES, not found])]) + ]) + m4_popdef([gltests_LIBSOURCES_DIR]) + m4_popdef([gltests_LIBSOURCES_LIST]) + m4_popdef([AC_LIBSOURCES]) + m4_popdef([AC_REPLACE_FUNCS]) + m4_popdef([AC_LIBOBJ]) + AC_CONFIG_COMMANDS_PRE([ + gltests_libobjs= + gltests_ltlibobjs= + if test -n "$gltests_LIBOBJS"; then + # Remove the extension. + sed_drop_objext='s/\.o$//;s/\.obj$//' + for i in `for i in $gltests_LIBOBJS; do echo "$i"; done | sed -e "$sed_drop_objext" | sort | uniq`; do + gltests_libobjs="$gltests_libobjs $i.$ac_objext" + gltests_ltlibobjs="$gltests_ltlibobjs $i.lo" + done + fi + AC_SUBST([gltests_LIBOBJS], [$gltests_libobjs]) + AC_SUBST([gltests_LTLIBOBJS], [$gltests_ltlibobjs]) + ]) + LIBGNU_LIBDEPS="$gl_libdeps" + AC_SUBST([LIBGNU_LIBDEPS]) + LIBGNU_LTLIBDEPS="$gl_ltlibdeps" + AC_SUBST([LIBGNU_LTLIBDEPS]) +]) + +# Like AC_LIBOBJ, except that the module name goes +# into gl_LIBOBJS instead of into LIBOBJS. +AC_DEFUN([gl_LIBOBJ], [ + AS_LITERAL_IF([$1], [gl_LIBSOURCES([$1.c])])dnl + gl_LIBOBJS="$gl_LIBOBJS $1.$ac_objext" +]) + +# Like AC_REPLACE_FUNCS, except that the module name goes +# into gl_LIBOBJS instead of into LIBOBJS. +AC_DEFUN([gl_REPLACE_FUNCS], [ + m4_foreach_w([gl_NAME], [$1], [AC_LIBSOURCES(gl_NAME[.c])])dnl + AC_CHECK_FUNCS([$1], , [gl_LIBOBJ($ac_func)]) +]) + +# Like AC_LIBSOURCES, except the directory where the source file is +# expected is derived from the gnulib-tool parameterization, +# and alloca is special cased (for the alloca-opt module). +# We could also entirely rely on EXTRA_lib..._SOURCES. +AC_DEFUN([gl_LIBSOURCES], [ + m4_foreach([_gl_NAME], [$1], [ + m4_if(_gl_NAME, [alloca.c], [], [ + m4_define([gl_LIBSOURCES_DIR], [lib]) + m4_append([gl_LIBSOURCES_LIST], _gl_NAME, [ ]) + ]) + ]) +]) + +# Like AC_LIBOBJ, except that the module name goes +# into gltests_LIBOBJS instead of into LIBOBJS. +AC_DEFUN([gltests_LIBOBJ], [ + AS_LITERAL_IF([$1], [gltests_LIBSOURCES([$1.c])])dnl + gltests_LIBOBJS="$gltests_LIBOBJS $1.$ac_objext" +]) + +# Like AC_REPLACE_FUNCS, except that the module name goes +# into gltests_LIBOBJS instead of into LIBOBJS. +AC_DEFUN([gltests_REPLACE_FUNCS], [ + m4_foreach_w([gl_NAME], [$1], [AC_LIBSOURCES(gl_NAME[.c])])dnl + AC_CHECK_FUNCS([$1], , [gltests_LIBOBJ($ac_func)]) +]) + +# Like AC_LIBSOURCES, except the directory where the source file is +# expected is derived from the gnulib-tool parameterization, +# and alloca is special cased (for the alloca-opt module). +# We could also entirely rely on EXTRA_lib..._SOURCES. +AC_DEFUN([gltests_LIBSOURCES], [ + m4_foreach([_gl_NAME], [$1], [ + m4_if(_gl_NAME, [alloca.c], [], [ + m4_define([gltests_LIBSOURCES_DIR], [tests]) + m4_append([gltests_LIBSOURCES_LIST], _gl_NAME, [ ]) + ]) + ]) +]) + +# This macro records the list of files which have been installed by +# gnulib-tool and may be removed by future gnulib-tool invocations. +AC_DEFUN([gl_FILE_LIST], [ + build-aux/arg-nonnull.h + build-aux/c++defs.h + build-aux/warn-on-use.h + lib/dummy.c + lib/memchr.c + lib/memchr.valgrind + lib/memmem.c + lib/sig-handler.h + lib/sigaction.c + lib/signal.in.h + lib/sigprocmask.c + lib/stddef.in.h + lib/stdint.in.h + lib/str-two-way.h + lib/string.in.h + m4/00gnulib.m4 + m4/extensions.m4 + m4/gnulib-common.m4 + m4/include_next.m4 + m4/longlong.m4 + m4/memchr.m4 + m4/memmem.m4 + m4/mmap-anon.m4 + m4/multiarch.m4 + m4/onceonly.m4 + m4/sigaction.m4 + m4/signal_h.m4 + m4/signalblocking.m4 + m4/stddef_h.m4 + m4/stdint.m4 + m4/string_h.m4 + m4/warn-on-use.m4 + m4/wchar_t.m4 +]) diff --git a/m4/gnulib-tool.m4 b/m4/gnulib-tool.m4 new file mode 100644 index 0000000..ed41e9d --- /dev/null +++ b/m4/gnulib-tool.m4 @@ -0,0 +1,57 @@ +# gnulib-tool.m4 serial 2 +dnl Copyright (C) 2004-2005, 2009-2011 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +dnl The following macros need not be invoked explicitly. +dnl Invoking them does nothing except to declare default arguments +dnl for "gnulib-tool --import". + +dnl Usage: gl_LOCAL_DIR([DIR]) +AC_DEFUN([gl_LOCAL_DIR], []) + +dnl Usage: gl_MODULES([module1 module2 ...]) +AC_DEFUN([gl_MODULES], []) + +dnl Usage: gl_AVOID([module1 module2 ...]) +AC_DEFUN([gl_AVOID], []) + +dnl Usage: gl_SOURCE_BASE([DIR]) +AC_DEFUN([gl_SOURCE_BASE], []) + +dnl Usage: gl_M4_BASE([DIR]) +AC_DEFUN([gl_M4_BASE], []) + +dnl Usage: gl_PO_BASE([DIR]) +AC_DEFUN([gl_PO_BASE], []) + +dnl Usage: gl_DOC_BASE([DIR]) +AC_DEFUN([gl_DOC_BASE], []) + +dnl Usage: gl_TESTS_BASE([DIR]) +AC_DEFUN([gl_TESTS_BASE], []) + +dnl Usage: gl_WITH_TESTS +AC_DEFUN([gl_WITH_TESTS], []) + +dnl Usage: gl_LIB([LIBNAME]) +AC_DEFUN([gl_LIB], []) + +dnl Usage: gl_LGPL or gl_LGPL([VERSION]) +AC_DEFUN([gl_LGPL], []) + +dnl Usage: gl_MAKEFILE_NAME([FILENAME]) +AC_DEFUN([gl_MAKEFILE_NAME], []) + +dnl Usage: gl_LIBTOOL +AC_DEFUN([gl_LIBTOOL], []) + +dnl Usage: gl_MACRO_PREFIX([PREFIX]) +AC_DEFUN([gl_MACRO_PREFIX], []) + +dnl Usage: gl_PO_DOMAIN([DOMAIN]) +AC_DEFUN([gl_PO_DOMAIN], []) + +dnl Usage: gl_VC_FILES([BOOLEAN]) +AC_DEFUN([gl_VC_FILES], []) diff --git a/m4/include_next.m4 b/m4/include_next.m4 new file mode 100644 index 0000000..b3c7849 --- /dev/null +++ b/m4/include_next.m4 @@ -0,0 +1,244 @@ +# include_next.m4 serial 18 +dnl Copyright (C) 2006-2011 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +dnl From Paul Eggert and Derek Price. + +dnl Sets INCLUDE_NEXT and PRAGMA_SYSTEM_HEADER. +dnl +dnl INCLUDE_NEXT expands to 'include_next' if the compiler supports it, or to +dnl 'include' otherwise. +dnl +dnl INCLUDE_NEXT_AS_FIRST_DIRECTIVE expands to 'include_next' if the compiler +dnl supports it in the special case that it is the first include directive in +dnl the given file, or to 'include' otherwise. +dnl +dnl PRAGMA_SYSTEM_HEADER can be used in files that contain #include_next, +dnl so as to avoid GCC warnings when the gcc option -pedantic is used. +dnl '#pragma GCC system_header' has the same effect as if the file was found +dnl through the include search path specified with '-isystem' options (as +dnl opposed to the search path specified with '-I' options). Namely, gcc +dnl does not warn about some things, and on some systems (Solaris and Interix) +dnl __STDC__ evaluates to 0 instead of to 1. The latter is an undesired side +dnl effect; we are therefore careful to use 'defined __STDC__' or '1' instead +dnl of plain '__STDC__'. +dnl +dnl PRAGMA_COLUMNS can be used in files that override system header files, so +dnl as to avoid compilation errors on HP NonStop systems when the gnulib file +dnl is included by a system header file that does a "#pragma COLUMNS 80" (which +dnl has the effect of truncating the lines of that file and all files that it +dnl includes to 80 columns) and the gnulib file has lines longer than 80 +dnl columns. + +AC_DEFUN([gl_INCLUDE_NEXT], +[ + AC_LANG_PREPROC_REQUIRE() + AC_CACHE_CHECK([whether the preprocessor supports include_next], + [gl_cv_have_include_next], + [rm -rf conftestd1a conftestd1b conftestd2 + mkdir conftestd1a conftestd1b conftestd2 + dnl IBM C 9.0, 10.1 (original versions, prior to the 2009-01 updates) on + dnl AIX 6.1 support include_next when used as first preprocessor directive + dnl in a file, but not when preceded by another include directive. Check + dnl for this bug by including . + dnl Additionally, with this same compiler, include_next is a no-op when + dnl used in a header file that was included by specifying its absolute + dnl file name. Despite these two bugs, include_next is used in the + dnl compiler's . By virtue of the second bug, we need to use + dnl include_next as well in this case. + cat < conftestd1a/conftest.h +#define DEFINED_IN_CONFTESTD1 +#include_next +#ifdef DEFINED_IN_CONFTESTD2 +int foo; +#else +#error "include_next doesn't work" +#endif +EOF + cat < conftestd1b/conftest.h +#define DEFINED_IN_CONFTESTD1 +#include +#include_next +#ifdef DEFINED_IN_CONFTESTD2 +int foo; +#else +#error "include_next doesn't work" +#endif +EOF + cat < conftestd2/conftest.h +#ifndef DEFINED_IN_CONFTESTD1 +#error "include_next test doesn't work" +#endif +#define DEFINED_IN_CONFTESTD2 +EOF + gl_save_CPPFLAGS="$CPPFLAGS" + CPPFLAGS="$gl_save_CPPFLAGS -Iconftestd1b -Iconftestd2" +dnl We intentionally avoid using AC_LANG_SOURCE here. + AC_COMPILE_IFELSE([AC_LANG_DEFINES_PROVIDED[#include ]], + [gl_cv_have_include_next=yes], + [CPPFLAGS="$gl_save_CPPFLAGS -Iconftestd1a -Iconftestd2" + AC_COMPILE_IFELSE([AC_LANG_DEFINES_PROVIDED[#include ]], + [gl_cv_have_include_next=buggy], + [gl_cv_have_include_next=no]) + ]) + CPPFLAGS="$gl_save_CPPFLAGS" + rm -rf conftestd1a conftestd1b conftestd2 + ]) + PRAGMA_SYSTEM_HEADER= + if test $gl_cv_have_include_next = yes; then + INCLUDE_NEXT=include_next + INCLUDE_NEXT_AS_FIRST_DIRECTIVE=include_next + if test -n "$GCC"; then + PRAGMA_SYSTEM_HEADER='#pragma GCC system_header' + fi + else + if test $gl_cv_have_include_next = buggy; then + INCLUDE_NEXT=include + INCLUDE_NEXT_AS_FIRST_DIRECTIVE=include_next + else + INCLUDE_NEXT=include + INCLUDE_NEXT_AS_FIRST_DIRECTIVE=include + fi + fi + AC_SUBST([INCLUDE_NEXT]) + AC_SUBST([INCLUDE_NEXT_AS_FIRST_DIRECTIVE]) + AC_SUBST([PRAGMA_SYSTEM_HEADER]) + AC_CACHE_CHECK([whether system header files limit the line length], + [gl_cv_pragma_columns], + [dnl HP NonStop systems, which define __TANDEM, have this misfeature. + AC_EGREP_CPP([choke me], + [ +#ifdef __TANDEM +choke me +#endif + ], + [gl_cv_pragma_columns=yes], + [gl_cv_pragma_columns=no]) + ]) + if test $gl_cv_pragma_columns = yes; then + PRAGMA_COLUMNS="#pragma COLUMNS 10000" + else + PRAGMA_COLUMNS= + fi + AC_SUBST([PRAGMA_COLUMNS]) +]) + +# gl_CHECK_NEXT_HEADERS(HEADER1 HEADER2 ...) +# ------------------------------------------ +# For each arg foo.h, if #include_next works, define NEXT_FOO_H to be +# ''; otherwise define it to be +# '"///usr/include/foo.h"', or whatever other absolute file name is suitable. +# Also, if #include_next works as first preprocessing directive in a file, +# define NEXT_AS_FIRST_DIRECTIVE_FOO_H to be ''; otherwise define it to +# be +# '"///usr/include/foo.h"', or whatever other absolute file name is suitable. +# That way, a header file with the following line: +# #@INCLUDE_NEXT@ @NEXT_FOO_H@ +# or +# #@INCLUDE_NEXT_AS_FIRST_DIRECTIVE@ @NEXT_AS_FIRST_DIRECTIVE_FOO_H@ +# behaves (after sed substitution) as if it contained +# #include_next +# even if the compiler does not support include_next. +# The three "///" are to pacify Sun C 5.8, which otherwise would say +# "warning: #include of /usr/include/... may be non-portable". +# Use `""', not `<>', so that the /// cannot be confused with a C99 comment. +# Note: This macro assumes that the header file is not empty after +# preprocessing, i.e. it does not only define preprocessor macros but also +# provides some type/enum definitions or function/variable declarations. +# +# This macro also checks whether each header exists, by invoking +# AC_CHECK_HEADERS_ONCE or AC_CHECK_HEADERS on each argument. +AC_DEFUN([gl_CHECK_NEXT_HEADERS], +[ + gl_NEXT_HEADERS_INTERNAL([$1], [check]) +]) + +# gl_NEXT_HEADERS(HEADER1 HEADER2 ...) +# ------------------------------------ +# Like gl_CHECK_NEXT_HEADERS, except do not check whether the headers exist. +# This is suitable for headers like that are standardized by C89 +# and therefore can be assumed to exist. +AC_DEFUN([gl_NEXT_HEADERS], +[ + gl_NEXT_HEADERS_INTERNAL([$1], [assume]) +]) + +# The guts of gl_CHECK_NEXT_HEADERS and gl_NEXT_HEADERS. +AC_DEFUN([gl_NEXT_HEADERS_INTERNAL], +[ + AC_REQUIRE([gl_INCLUDE_NEXT]) + AC_REQUIRE([AC_CANONICAL_HOST]) + + m4_if([$2], [check], + [AC_CHECK_HEADERS_ONCE([$1]) + ]) + + m4_foreach_w([gl_HEADER_NAME], [$1], + [AS_VAR_PUSHDEF([gl_next_header], + [gl_cv_next_]m4_defn([gl_HEADER_NAME])) + if test $gl_cv_have_include_next = yes; then + AS_VAR_SET([gl_next_header], ['<'gl_HEADER_NAME'>']) + else + AC_CACHE_CHECK( + [absolute name of <]m4_defn([gl_HEADER_NAME])[>], + m4_defn([gl_next_header]), + [m4_if([$2], [check], + [AS_VAR_PUSHDEF([gl_header_exists], + [ac_cv_header_]m4_defn([gl_HEADER_NAME])) + if test AS_VAR_GET(gl_header_exists) = yes; then + AS_VAR_POPDEF([gl_header_exists]) + ]) + AC_LANG_CONFTEST( + [AC_LANG_SOURCE( + [[#include <]]m4_dquote(m4_defn([gl_HEADER_NAME]))[[>]] + )]) + dnl AIX "xlc -E" and "cc -E" omit #line directives for header + dnl files that contain only a #include of other header files and + dnl no non-comment tokens of their own. This leads to a failure + dnl to detect the absolute name of , , + dnl and others. The workaround is to force preservation + dnl of comments through option -C. This ensures all necessary + dnl #line directives are present. GCC supports option -C as well. + case "$host_os" in + aix*) gl_absname_cpp="$ac_cpp -C" ;; + *) gl_absname_cpp="$ac_cpp" ;; + esac + dnl eval is necessary to expand gl_absname_cpp. + dnl Ultrix and Pyramid sh refuse to redirect output of eval, + dnl so use subshell. + AS_VAR_SET([gl_next_header], + ['"'`(eval "$gl_absname_cpp conftest.$ac_ext") 2>&AS_MESSAGE_LOG_FD | + sed -n '\#/]m4_defn([gl_HEADER_NAME])[#{ + s#.*"\(.*/]m4_defn([gl_HEADER_NAME])[\)".*#\1# + s#^/[^/]#//&# + p + q + }'`'"']) + m4_if([$2], [check], + [else + AS_VAR_SET([gl_next_header], ['<'gl_HEADER_NAME'>']) + fi + ]) + ]) + fi + AC_SUBST( + AS_TR_CPP([NEXT_]m4_defn([gl_HEADER_NAME])), + [AS_VAR_GET([gl_next_header])]) + if test $gl_cv_have_include_next = yes || test $gl_cv_have_include_next = buggy; then + # INCLUDE_NEXT_AS_FIRST_DIRECTIVE='include_next' + gl_next_as_first_directive='<'gl_HEADER_NAME'>' + else + # INCLUDE_NEXT_AS_FIRST_DIRECTIVE='include' + gl_next_as_first_directive=AS_VAR_GET([gl_next_header]) + fi + AC_SUBST( + AS_TR_CPP([NEXT_AS_FIRST_DIRECTIVE_]m4_defn([gl_HEADER_NAME])), + [$gl_next_as_first_directive]) + AS_VAR_POPDEF([gl_next_header])]) +]) + +# Autoconf 2.68 added warnings for our use of AC_COMPILE_IFELSE; +# this fallback is safe for all earlier autoconf versions. +m4_define_default([AC_LANG_DEFINES_PROVIDED]) diff --git a/m4/longlong.m4 b/m4/longlong.m4 new file mode 100644 index 0000000..aed816c --- /dev/null +++ b/m4/longlong.m4 @@ -0,0 +1,113 @@ +# longlong.m4 serial 16 +dnl Copyright (C) 1999-2007, 2009-2011 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +dnl From Paul Eggert. + +# Define HAVE_LONG_LONG_INT if 'long long int' works. +# This fixes a bug in Autoconf 2.61, and can be faster +# than what's in Autoconf 2.62 through 2.68. + +# Note: If the type 'long long int' exists but is only 32 bits large +# (as on some very old compilers), HAVE_LONG_LONG_INT will not be +# defined. In this case you can treat 'long long int' like 'long int'. + +AC_DEFUN([AC_TYPE_LONG_LONG_INT], +[ + AC_REQUIRE([AC_TYPE_UNSIGNED_LONG_LONG_INT]) + AC_CACHE_CHECK([for long long int], [ac_cv_type_long_long_int], + [ac_cv_type_long_long_int=yes + if test "x${ac_cv_prog_cc_c99-no}" = xno; then + ac_cv_type_long_long_int=$ac_cv_type_unsigned_long_long_int + if test $ac_cv_type_long_long_int = yes; then + dnl Catch a bug in Tandem NonStop Kernel (OSS) cc -O circa 2004. + dnl If cross compiling, assume the bug is not important, since + dnl nobody cross compiles for this platform as far as we know. + AC_RUN_IFELSE( + [AC_LANG_PROGRAM( + [[@%:@include + @%:@ifndef LLONG_MAX + @%:@ define HALF \ + (1LL << (sizeof (long long int) * CHAR_BIT - 2)) + @%:@ define LLONG_MAX (HALF - 1 + HALF) + @%:@endif]], + [[long long int n = 1; + int i; + for (i = 0; ; i++) + { + long long int m = n << i; + if (m >> i != n) + return 1; + if (LLONG_MAX / 2 < m) + break; + } + return 0;]])], + [], + [ac_cv_type_long_long_int=no], + [:]) + fi + fi]) + if test $ac_cv_type_long_long_int = yes; then + AC_DEFINE([HAVE_LONG_LONG_INT], [1], + [Define to 1 if the system has the type `long long int'.]) + fi +]) + +# Define HAVE_UNSIGNED_LONG_LONG_INT if 'unsigned long long int' works. +# This fixes a bug in Autoconf 2.61, and can be faster +# than what's in Autoconf 2.62 through 2.68. + +# Note: If the type 'unsigned long long int' exists but is only 32 bits +# large (as on some very old compilers), AC_TYPE_UNSIGNED_LONG_LONG_INT +# will not be defined. In this case you can treat 'unsigned long long int' +# like 'unsigned long int'. + +AC_DEFUN([AC_TYPE_UNSIGNED_LONG_LONG_INT], +[ + AC_CACHE_CHECK([for unsigned long long int], + [ac_cv_type_unsigned_long_long_int], + [ac_cv_type_unsigned_long_long_int=yes + if test "x${ac_cv_prog_cc_c99-no}" = xno; then + AC_LINK_IFELSE( + [_AC_TYPE_LONG_LONG_SNIPPET], + [], + [ac_cv_type_unsigned_long_long_int=no]) + fi]) + if test $ac_cv_type_unsigned_long_long_int = yes; then + AC_DEFINE([HAVE_UNSIGNED_LONG_LONG_INT], [1], + [Define to 1 if the system has the type `unsigned long long int'.]) + fi +]) + +# Expands to a C program that can be used to test for simultaneous support +# of 'long long' and 'unsigned long long'. We don't want to say that +# 'long long' is available if 'unsigned long long' is not, or vice versa, +# because too many programs rely on the symmetry between signed and unsigned +# integer types (excluding 'bool'). +AC_DEFUN([_AC_TYPE_LONG_LONG_SNIPPET], +[ + AC_LANG_PROGRAM( + [[/* For now, do not test the preprocessor; as of 2007 there are too many + implementations with broken preprocessors. Perhaps this can + be revisited in 2012. In the meantime, code should not expect + #if to work with literals wider than 32 bits. */ + /* Test literals. */ + long long int ll = 9223372036854775807ll; + long long int nll = -9223372036854775807LL; + unsigned long long int ull = 18446744073709551615ULL; + /* Test constant expressions. */ + typedef int a[((-9223372036854775807LL < 0 && 0 < 9223372036854775807ll) + ? 1 : -1)]; + typedef int b[(18446744073709551615ULL <= (unsigned long long int) -1 + ? 1 : -1)]; + int i = 63;]], + [[/* Test availability of runtime routines for shift and division. */ + long long int llmax = 9223372036854775807ll; + unsigned long long int ullmax = 18446744073709551615ull; + return ((ll << 63) | (ll >> 63) | (ll < i) | (ll > i) + | (llmax / ll) | (llmax % ll) + | (ull << 63) | (ull >> 63) | (ull << i) | (ull >> i) + | (ullmax / ull) | (ullmax % ull));]]) +]) diff --git a/m4/memchr.m4 b/m4/memchr.m4 new file mode 100644 index 0000000..f6dc3e7 --- /dev/null +++ b/m4/memchr.m4 @@ -0,0 +1,88 @@ +# memchr.m4 serial 12 +dnl Copyright (C) 2002-2004, 2009-2011 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +AC_DEFUN_ONCE([gl_FUNC_MEMCHR], +[ + dnl Check for prerequisites for memory fence checks. + gl_FUNC_MMAP_ANON + AC_CHECK_HEADERS_ONCE([sys/mman.h]) + AC_CHECK_FUNCS_ONCE([mprotect]) + + AC_REQUIRE([gl_HEADER_STRING_H_DEFAULTS]) + m4_ifdef([gl_FUNC_MEMCHR_OBSOLETE], [ + dnl These days, we assume memchr is present. But if support for old + dnl platforms is desired: + AC_CHECK_FUNCS_ONCE([memchr]) + if test $ac_cv_func_memchr = no; then + HAVE_MEMCHR=0 + fi + ]) + if test $HAVE_MEMCHR = 1; then + # Detect platform-specific bugs in some versions of glibc: + # memchr should not dereference anything with length 0 + # http://bugzilla.redhat.com/499689 + # memchr should not dereference overestimated length after a match + # http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=521737 + # http://sourceware.org/bugzilla/show_bug.cgi?id=10162 + # Assume that memchr works on platforms that lack mprotect. + AC_CACHE_CHECK([whether memchr works], [gl_cv_func_memchr_works], + [AC_RUN_IFELSE([AC_LANG_PROGRAM([[ +#include +#if HAVE_SYS_MMAN_H +# include +# include +# include +# include +# ifndef MAP_FILE +# define MAP_FILE 0 +# endif +#endif +]], [[ + int result = 0; + char *fence = NULL; +#if HAVE_SYS_MMAN_H && HAVE_MPROTECT +# if HAVE_MAP_ANONYMOUS + const int flags = MAP_ANONYMOUS | MAP_PRIVATE; + const int fd = -1; +# else /* !HAVE_MAP_ANONYMOUS */ + const int flags = MAP_FILE | MAP_PRIVATE; + int fd = open ("/dev/zero", O_RDONLY, 0666); + if (fd >= 0) +# endif + { + int pagesize = getpagesize (); + char *two_pages = + (char *) mmap (NULL, 2 * pagesize, PROT_READ | PROT_WRITE, + flags, fd, 0); + if (two_pages != (char *)(-1) + && mprotect (two_pages + pagesize, pagesize, PROT_NONE) == 0) + fence = two_pages + pagesize; + } +#endif + if (fence) + { + if (memchr (fence, 0, 0)) + result |= 1; + strcpy (fence - 9, "12345678"); + if (memchr (fence - 9, 0, 79) != fence - 1) + result |= 2; + if (memchr (fence - 1, 0, 3) != fence - 1) + result |= 4; + } + return result; +]])], [gl_cv_func_memchr_works=yes], [gl_cv_func_memchr_works=no], + [dnl Be pessimistic for now. + gl_cv_func_memchr_works="guessing no"])]) + if test "$gl_cv_func_memchr_works" != yes; then + REPLACE_MEMCHR=1 + fi + fi +]) + +# Prerequisites of lib/memchr.c. +AC_DEFUN([gl_PREREQ_MEMCHR], [ + AC_CHECK_HEADERS([bp-sym.h]) +]) diff --git a/m4/memmem.m4 b/m4/memmem.m4 new file mode 100644 index 0000000..e912205 --- /dev/null +++ b/m4/memmem.m4 @@ -0,0 +1,145 @@ +# memmem.m4 serial 23 +dnl Copyright (C) 2002-2004, 2007-2011 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +dnl Check that memmem is present and functional. +AC_DEFUN([gl_FUNC_MEMMEM_SIMPLE], +[ + dnl Persuade glibc to declare memmem(). + AC_REQUIRE([AC_USE_SYSTEM_EXTENSIONS]) + + AC_REQUIRE([gl_HEADER_STRING_H_DEFAULTS]) + AC_CHECK_FUNCS([memmem]) + if test $ac_cv_func_memmem = yes; then + HAVE_MEMMEM=1 + else + HAVE_MEMMEM=0 + fi + AC_CHECK_DECLS_ONCE([memmem]) + if test $ac_cv_have_decl_memmem = no; then + HAVE_DECL_MEMMEM=0 + else + dnl Detect http://sourceware.org/bugzilla/show_bug.cgi?id=12092. + dnl Also check that we handle empty needles correctly. + AC_CACHE_CHECK([whether memmem works], + [gl_cv_func_memmem_works_always], + [AC_RUN_IFELSE([AC_LANG_PROGRAM([[ +#include /* for memmem */ +#define P "_EF_BF_BD" +#define HAYSTACK "F_BD_CE_BD" P P P P "_C3_88_20" P P P "_C3_A7_20" P +#define NEEDLE P P P P P +]], [[ + int result = 0; + if (memmem (HAYSTACK, strlen (HAYSTACK), NEEDLE, strlen (NEEDLE))) + result |= 1; + /* Check for empty needle behavior. */ + { + const char *haystack = "AAA"; + if (memmem (haystack, 3, NULL, 0) != haystack) + result |= 2; + } + return result; + ]])], + [gl_cv_func_memmem_works_always=yes], + [gl_cv_func_memmem_works_always=no], + [dnl glibc 2.9..2.12 and cygwin 1.7.7 have issue #12092 above. + dnl Also empty needles work on glibc >= 2.1 and cygwin >= 1.7.0. + dnl uClibc is not affected, since it uses different source code. + dnl Assume that it works on all other platforms (even if not linear). + AC_EGREP_CPP([Lucky user], + [ +#ifdef __GNU_LIBRARY__ + #include + #if ((__GLIBC__ == 2 && ((__GLIBC_MINOR > 0 && __GLIBC_MINOR__ < 9) \ + || __GLIBC_MINOR__ > 12)) \ + || (__GLIBC__ > 2)) \ + || defined __UCLIBC__ + Lucky user + #endif +#elif defined __CYGWIN__ + #include + #if CYGWIN_VERSION_DLL_COMBINED > CYGWIN_VERSION_DLL_MAKE_COMBINED (1007, 7) + Lucky user + #endif +#else + Lucky user +#endif + ], + [gl_cv_func_memmem_works_always=yes], + [gl_cv_func_memmem_works_always="guessing no"]) + ]) + ]) + if test "$gl_cv_func_memmem_works_always" != yes; then + REPLACE_MEMMEM=1 + fi + fi + gl_PREREQ_MEMMEM +]) # gl_FUNC_MEMMEM_SIMPLE + +dnl Additionally, check that memmem has linear performance characteristics +AC_DEFUN([gl_FUNC_MEMMEM], +[ + AC_REQUIRE([gl_FUNC_MEMMEM_SIMPLE]) + if test $HAVE_DECL_MEMMEM = 1 && test $REPLACE_MEMMEM = 0; then + AC_CACHE_CHECK([whether memmem works in linear time], + [gl_cv_func_memmem_works_fast], + [AC_RUN_IFELSE([AC_LANG_PROGRAM([[ +#include /* for signal */ +#include /* for memmem */ +#include /* for malloc */ +#include /* for alarm */ +static void quit (int sig) { exit (sig + 128); } +]], [[ + int result = 0; + size_t m = 1000000; + char *haystack = (char *) malloc (2 * m + 1); + char *needle = (char *) malloc (m + 1); + /* Failure to compile this test due to missing alarm is okay, + since all such platforms (mingw) also lack memmem. */ + signal (SIGALRM, quit); + alarm (5); + /* Check for quadratic performance. */ + if (haystack && needle) + { + memset (haystack, 'A', 2 * m); + haystack[2 * m] = 'B'; + memset (needle, 'A', m); + needle[m] = 'B'; + if (!memmem (haystack, 2 * m + 1, needle, m + 1)) + result |= 1; + } + return result; + ]])], + [gl_cv_func_memmem_works_fast=yes], [gl_cv_func_memmem_works_fast=no], + [dnl Only glibc >= 2.9 and cygwin > 1.7.0 are known to have a + dnl memmem that works in linear time. + AC_EGREP_CPP([Lucky user], + [ +#include +#ifdef __GNU_LIBRARY__ + #if ((__GLIBC__ == 2 && __GLIBC_MINOR__ >= 9) || (__GLIBC__ > 2)) \ + && !defined __UCLIBC__ + Lucky user + #endif +#endif +#ifdef __CYGWIN__ + #include + #if CYGWIN_VERSION_DLL_COMBINED > CYGWIN_VERSION_DLL_MAKE_COMBINED (1007, 0) + Lucky user + #endif +#endif + ], + [gl_cv_func_memmem_works_fast=yes], + [gl_cv_func_memmem_works_fast="guessing no"]) + ]) + ]) + if test "$gl_cv_func_memmem_works_fast" != yes; then + REPLACE_MEMMEM=1 + fi + fi +]) # gl_FUNC_MEMMEM + +# Prerequisites of lib/memmem.c. +AC_DEFUN([gl_PREREQ_MEMMEM], [:]) diff --git a/m4/mmap-anon.m4 b/m4/mmap-anon.m4 new file mode 100644 index 0000000..7ba7fd2 --- /dev/null +++ b/m4/mmap-anon.m4 @@ -0,0 +1,55 @@ +# mmap-anon.m4 serial 9 +dnl Copyright (C) 2005, 2007, 2009-2011 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +# Detect how mmap can be used to create anonymous (not file-backed) memory +# mappings. +# - On Linux, AIX, OSF/1, Solaris, Cygwin, Interix, Haiku, both MAP_ANONYMOUS +# and MAP_ANON exist and have the same value. +# - On HP-UX, only MAP_ANONYMOUS exists. +# - On MacOS X, FreeBSD, NetBSD, OpenBSD, only MAP_ANON exists. +# - On IRIX, neither exists, and a file descriptor opened to /dev/zero must be +# used. + +AC_DEFUN([gl_FUNC_MMAP_ANON], +[ + dnl Persuade glibc to define MAP_ANONYMOUS. + AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS]) + + # Check for mmap(). Don't use AC_FUNC_MMAP, because it checks too much: it + # fails on HP-UX 11, because MAP_FIXED mappings do not work. But this is + # irrelevant for anonymous mappings. + AC_CHECK_FUNC([mmap], [gl_have_mmap=yes], [gl_have_mmap=no]) + + # Try to allow MAP_ANONYMOUS. + gl_have_mmap_anonymous=no + if test $gl_have_mmap = yes; then + AC_MSG_CHECKING([for MAP_ANONYMOUS]) + AC_EGREP_CPP([I cant identify this map.], [ +#include +#ifdef MAP_ANONYMOUS + I cant identify this map. +#endif +], + [gl_have_mmap_anonymous=yes]) + if test $gl_have_mmap_anonymous != yes; then + AC_EGREP_CPP([I cant identify this map.], [ +#include +#ifdef MAP_ANON + I cant identify this map. +#endif +], + [AC_DEFINE([MAP_ANONYMOUS], [MAP_ANON], + [Define to a substitute value for mmap()'s MAP_ANONYMOUS flag.]) + gl_have_mmap_anonymous=yes]) + fi + AC_MSG_RESULT([$gl_have_mmap_anonymous]) + if test $gl_have_mmap_anonymous = yes; then + AC_DEFINE([HAVE_MAP_ANONYMOUS], [1], + [Define to 1 if mmap()'s MAP_ANONYMOUS flag is available after including + config.h and .]) + fi + fi +]) diff --git a/m4/multiarch.m4 b/m4/multiarch.m4 new file mode 100644 index 0000000..691d892 --- /dev/null +++ b/m4/multiarch.m4 @@ -0,0 +1,62 @@ +# multiarch.m4 serial 6 +dnl Copyright (C) 2008-2011 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +# Determine whether the compiler is or may be producing universal binaries. +# +# On MacOS X 10.5 and later systems, the user can create libraries and +# executables that work on multiple system types--known as "fat" or +# "universal" binaries--by specifying multiple '-arch' options to the +# compiler but only a single '-arch' option to the preprocessor. Like +# this: +# +# ./configure CC="gcc -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ +# CXX="g++ -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ +# CPP="gcc -E" CXXCPP="g++ -E" +# +# Detect this situation and set APPLE_UNIVERSAL_BUILD accordingly. + +AC_DEFUN_ONCE([gl_MULTIARCH], +[ + dnl Code similar to autoconf-2.63 AC_C_BIGENDIAN. + gl_cv_c_multiarch=no + AC_COMPILE_IFELSE( + [AC_LANG_SOURCE( + [[#ifndef __APPLE_CC__ + not a universal capable compiler + #endif + typedef int dummy; + ]])], + [ + dnl Check for potential -arch flags. It is not universal unless + dnl there are at least two -arch flags with different values. + arch= + prev= + for word in ${CC} ${CFLAGS} ${CPPFLAGS} ${LDFLAGS}; do + if test -n "$prev"; then + case $word in + i?86 | x86_64 | ppc | ppc64) + if test -z "$arch" || test "$arch" = "$word"; then + arch="$word" + else + gl_cv_c_multiarch=yes + fi + ;; + esac + prev= + else + if test "x$word" = "x-arch"; then + prev=arch + fi + fi + done + ]) + if test $gl_cv_c_multiarch = yes; then + APPLE_UNIVERSAL_BUILD=1 + else + APPLE_UNIVERSAL_BUILD=0 + fi + AC_SUBST([APPLE_UNIVERSAL_BUILD]) +]) diff --git a/m4/onceonly.m4 b/m4/onceonly.m4 new file mode 100644 index 0000000..223071a --- /dev/null +++ b/m4/onceonly.m4 @@ -0,0 +1,91 @@ +# onceonly.m4 serial 7 +dnl Copyright (C) 2002-2003, 2005-2006, 2008-2011 Free Software Foundation, +dnl Inc. +dnl This file is free software, distributed under the terms of the GNU +dnl General Public License. As a special exception to the GNU General +dnl Public License, this file may be distributed as part of a program +dnl that contains a configuration script generated by Autoconf, under +dnl the same distribution terms as the rest of that program. + +dnl This file defines some "once only" variants of standard autoconf macros. +dnl AC_CHECK_HEADERS_ONCE like AC_CHECK_HEADERS +dnl AC_CHECK_FUNCS_ONCE like AC_CHECK_FUNCS +dnl AC_CHECK_DECLS_ONCE like AC_CHECK_DECLS +dnl AC_REQUIRE([AC_FUNC_STRCOLL]) like AC_FUNC_STRCOLL +dnl The advantage is that the check for each of the headers/functions/decls +dnl will be put only once into the 'configure' file. It keeps the size of +dnl the 'configure' file down, and avoids redundant output when 'configure' +dnl is run. +dnl The drawback is that the checks cannot be conditionalized. If you write +dnl if some_condition; then gl_CHECK_HEADERS(stdlib.h); fi +dnl inside an AC_DEFUNed function, the gl_CHECK_HEADERS macro call expands to +dnl empty, and the check will be inserted before the body of the AC_DEFUNed +dnl function. + +dnl The original code implemented AC_CHECK_HEADERS_ONCE and AC_CHECK_FUNCS_ONCE +dnl in terms of AC_DEFUN and AC_REQUIRE. This implementation uses diversions to +dnl named sections DEFAULTS and INIT_PREPARE in order to check all requested +dnl headers at once, thus reducing the size of 'configure'. It is known to work +dnl with autoconf 2.57..2.62 at least . The size reduction is ca. 9%. + +dnl Autoconf version 2.59 plus gnulib is required; this file is not needed +dnl with Autoconf 2.60 or greater. But note that autoconf's implementation of +dnl AC_CHECK_DECLS_ONCE expects a comma-separated list of symbols as first +dnl argument! +AC_PREREQ([2.59]) + +# AC_CHECK_HEADERS_ONCE(HEADER1 HEADER2 ...) is a once-only variant of +# AC_CHECK_HEADERS(HEADER1 HEADER2 ...). +AC_DEFUN([AC_CHECK_HEADERS_ONCE], [ + : + m4_foreach_w([gl_HEADER_NAME], [$1], [ + AC_DEFUN([gl_CHECK_HEADER_]m4_quote(m4_translit(gl_HEADER_NAME, + [./-], [___])), [ + m4_divert_text([INIT_PREPARE], + [gl_header_list="$gl_header_list gl_HEADER_NAME"]) + gl_HEADERS_EXPANSION + AH_TEMPLATE(AS_TR_CPP([HAVE_]m4_defn([gl_HEADER_NAME])), + [Define to 1 if you have the <]m4_defn([gl_HEADER_NAME])[> header file.]) + ]) + AC_REQUIRE([gl_CHECK_HEADER_]m4_quote(m4_translit(gl_HEADER_NAME, + [./-], [___]))) + ]) +]) +m4_define([gl_HEADERS_EXPANSION], [ + m4_divert_text([DEFAULTS], [gl_header_list=]) + AC_CHECK_HEADERS([$gl_header_list]) + m4_define([gl_HEADERS_EXPANSION], []) +]) + +# AC_CHECK_FUNCS_ONCE(FUNC1 FUNC2 ...) is a once-only variant of +# AC_CHECK_FUNCS(FUNC1 FUNC2 ...). +AC_DEFUN([AC_CHECK_FUNCS_ONCE], [ + : + m4_foreach_w([gl_FUNC_NAME], [$1], [ + AC_DEFUN([gl_CHECK_FUNC_]m4_defn([gl_FUNC_NAME]), [ + m4_divert_text([INIT_PREPARE], + [gl_func_list="$gl_func_list gl_FUNC_NAME"]) + gl_FUNCS_EXPANSION + AH_TEMPLATE(AS_TR_CPP([HAVE_]m4_defn([gl_FUNC_NAME])), + [Define to 1 if you have the `]m4_defn([gl_FUNC_NAME])[' function.]) + ]) + AC_REQUIRE([gl_CHECK_FUNC_]m4_defn([gl_FUNC_NAME])) + ]) +]) +m4_define([gl_FUNCS_EXPANSION], [ + m4_divert_text([DEFAULTS], [gl_func_list=]) + AC_CHECK_FUNCS([$gl_func_list]) + m4_define([gl_FUNCS_EXPANSION], []) +]) + +# AC_CHECK_DECLS_ONCE(DECL1 DECL2 ...) is a once-only variant of +# AC_CHECK_DECLS(DECL1, DECL2, ...). +AC_DEFUN([AC_CHECK_DECLS_ONCE], [ + : + m4_foreach_w([gl_DECL_NAME], [$1], [ + AC_DEFUN([gl_CHECK_DECL_]m4_defn([gl_DECL_NAME]), [ + AC_CHECK_DECLS(m4_defn([gl_DECL_NAME])) + ]) + AC_REQUIRE([gl_CHECK_DECL_]m4_defn([gl_DECL_NAME])) + ]) +]) diff --git a/m4/sigaction.m4 b/m4/sigaction.m4 new file mode 100644 index 0000000..b365e26 --- /dev/null +++ b/m4/sigaction.m4 @@ -0,0 +1,43 @@ +# sigaction.m4 serial 6 +dnl Copyright (C) 2008-2011 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +# Determine if sigaction interface is present. +AC_DEFUN([gl_SIGACTION], +[ + AC_REQUIRE([gl_SIGNAL_H_DEFAULTS]) + AC_CHECK_FUNCS_ONCE([sigaction]) + if test $ac_cv_func_sigaction = yes; then + AC_CHECK_MEMBERS([struct sigaction.sa_sigaction], , , + [[#include ]]) + if test $ac_cv_member_struct_sigaction_sa_sigaction = no; then + HAVE_STRUCT_SIGACTION_SA_SIGACTION=0 + fi + else + HAVE_SIGACTION=0 + fi +]) + +# Prerequisites of the part of lib/signal.in.h and of lib/sigaction.c. +AC_DEFUN([gl_PREREQ_SIGACTION], +[ + AC_REQUIRE([gl_SIGNAL_H_DEFAULTS]) + AC_REQUIRE([AC_C_RESTRICT]) + AC_REQUIRE([AC_TYPE_UID_T]) + AC_REQUIRE([gl_PREREQ_SIG_HANDLER_H]) + AC_CHECK_FUNCS_ONCE([sigaltstack siginterrupt]) + AC_CHECK_TYPES([siginfo_t], [], [], [[ +#include + ]]) + if test $ac_cv_type_siginfo_t = no; then + HAVE_SIGINFO_T=0 + fi +]) + +# Prerequisites of lib/sig-handler.h. +AC_DEFUN([gl_PREREQ_SIG_HANDLER_H], +[ + AC_REQUIRE([AC_C_INLINE]) +]) diff --git a/m4/signal_h.m4 b/m4/signal_h.m4 new file mode 100644 index 0000000..459ec00 --- /dev/null +++ b/m4/signal_h.m4 @@ -0,0 +1,58 @@ +# signal_h.m4 serial 12 +dnl Copyright (C) 2007-2011 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +AC_DEFUN([gl_SIGNAL_H], +[ + AC_REQUIRE([gl_SIGNAL_H_DEFAULTS]) + gl_NEXT_HEADERS([signal.h]) + +# AIX declares sig_atomic_t to already include volatile, and C89 compilers +# then choke on 'volatile sig_atomic_t'. C99 requires that it compile. + AC_CHECK_TYPE([volatile sig_atomic_t], [], + [HAVE_TYPE_VOLATILE_SIG_ATOMIC_T=0], [[ +#include + ]]) + + AC_REQUIRE([AC_TYPE_UID_T]) + + dnl Persuade glibc to define sighandler_t. + AC_REQUIRE([AC_USE_SYSTEM_EXTENSIONS]) + AC_CHECK_TYPE([sighandler_t], [], [HAVE_SIGHANDLER_T=0], [[ +#include + ]]) + + dnl Check for declarations of anything we want to poison if the + dnl corresponding gnulib module is not in use. + gl_WARN_ON_USE_PREPARE([[#include + ]], [sigaction sigaddset sigdelset sigemptyset sigfillset sigismember + sigpending sigprocmask]) +]) + +AC_DEFUN([gl_SIGNAL_MODULE_INDICATOR], +[ + dnl Use AC_REQUIRE here, so that the default settings are expanded once only. + AC_REQUIRE([gl_SIGNAL_H_DEFAULTS]) + gl_MODULE_INDICATOR_SET_VARIABLE([$1]) + dnl Define it also as a C macro, for the benefit of the unit tests. + gl_MODULE_INDICATOR_FOR_TESTS([$1]) +]) + +AC_DEFUN([gl_SIGNAL_H_DEFAULTS], +[ + GNULIB_SIGNAL_H_SIGPIPE=0; AC_SUBST([GNULIB_SIGNAL_H_SIGPIPE]) + GNULIB_SIGPROCMASK=0; AC_SUBST([GNULIB_SIGPROCMASK]) + GNULIB_SIGACTION=0; AC_SUBST([GNULIB_SIGACTION]) + dnl Assume proper GNU behavior unless another module says otherwise. + HAVE_POSIX_SIGNALBLOCKING=1; AC_SUBST([HAVE_POSIX_SIGNALBLOCKING]) + HAVE_SIGSET_T=1; AC_SUBST([HAVE_SIGSET_T]) + HAVE_SIGINFO_T=1; AC_SUBST([HAVE_SIGINFO_T]) + HAVE_SIGACTION=1; AC_SUBST([HAVE_SIGACTION]) + HAVE_STRUCT_SIGACTION_SA_SIGACTION=1; + AC_SUBST([HAVE_STRUCT_SIGACTION_SA_SIGACTION]) + HAVE_TYPE_VOLATILE_SIG_ATOMIC_T=1; + AC_SUBST([HAVE_TYPE_VOLATILE_SIG_ATOMIC_T]) + HAVE_SIGHANDLER_T=1; AC_SUBST([HAVE_SIGHANDLER_T]) +]) diff --git a/m4/signalblocking.m4 b/m4/signalblocking.m4 new file mode 100644 index 0000000..6e83f1b --- /dev/null +++ b/m4/signalblocking.m4 @@ -0,0 +1,40 @@ +# signalblocking.m4 serial 11 +dnl Copyright (C) 2001-2002, 2006-2011 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +# Determine available signal blocking primitives. Three different APIs exist: +# 1) POSIX: sigemptyset, sigaddset, sigprocmask +# 2) SYSV: sighold, sigrelse +# 3) BSD: sigblock, sigsetmask +# For simplicity, here we check only for the POSIX signal blocking. +AC_DEFUN([gl_SIGNALBLOCKING], +[ + AC_REQUIRE([gl_SIGNAL_H_DEFAULTS]) + signals_not_posix= + AC_EGREP_HEADER([sigset_t], [signal.h], , [signals_not_posix=1]) + if test -z "$signals_not_posix"; then + AC_CHECK_FUNC([sigprocmask], [gl_cv_func_sigprocmask=1]) + fi + if test -z "$gl_cv_func_sigprocmask"; then + HAVE_POSIX_SIGNALBLOCKING=0 + fi +]) + +# Prerequisites of the part of lib/signal.in.h and of lib/sigprocmask.c. +AC_DEFUN([gl_PREREQ_SIGPROCMASK], +[ + AC_REQUIRE([gl_SIGNAL_H_DEFAULTS]) + AC_CHECK_TYPES([sigset_t], + [gl_cv_type_sigset_t=yes], [gl_cv_type_sigset_t=no], + [#include +/* Mingw defines sigset_t not in , but in . */ +#include ]) + if test $gl_cv_type_sigset_t != yes; then + HAVE_SIGSET_T=0 + fi + dnl HAVE_SIGSET_T is 1 if the system lacks the sigprocmask function but has + dnl the sigset_t type. + AC_SUBST([HAVE_SIGSET_T]) +]) diff --git a/m4/stddef_h.m4 b/m4/stddef_h.m4 new file mode 100644 index 0000000..1ae2344 --- /dev/null +++ b/m4/stddef_h.m4 @@ -0,0 +1,47 @@ +dnl A placeholder for POSIX 2008 , for platforms that have issues. +# stddef_h.m4 serial 4 +dnl Copyright (C) 2009-2011 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +AC_DEFUN([gl_STDDEF_H], +[ + AC_REQUIRE([gl_STDDEF_H_DEFAULTS]) + AC_REQUIRE([gt_TYPE_WCHAR_T]) + STDDEF_H= + if test $gt_cv_c_wchar_t = no; then + HAVE_WCHAR_T=0 + STDDEF_H=stddef.h + fi + AC_CACHE_CHECK([whether NULL can be used in arbitrary expressions], + [gl_cv_decl_null_works], + [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include + int test[2 * (sizeof NULL == sizeof (void *)) -1]; +]])], + [gl_cv_decl_null_works=yes], + [gl_cv_decl_null_works=no])]) + if test $gl_cv_decl_null_works = no; then + REPLACE_NULL=1 + STDDEF_H=stddef.h + fi + AC_SUBST([STDDEF_H]) + AM_CONDITIONAL([GL_GENERATE_STDDEF_H], [test -n "$STDDEF_H"]) + if test -n "$STDDEF_H"; then + gl_NEXT_HEADERS([stddef.h]) + fi +]) + +AC_DEFUN([gl_STDDEF_MODULE_INDICATOR], +[ + dnl Use AC_REQUIRE here, so that the default settings are expanded once only. + AC_REQUIRE([gl_STDDEF_H_DEFAULTS]) + gl_MODULE_INDICATOR_SET_VARIABLE([$1]) +]) + +AC_DEFUN([gl_STDDEF_H_DEFAULTS], +[ + dnl Assume proper GNU behavior unless another module says otherwise. + REPLACE_NULL=0; AC_SUBST([REPLACE_NULL]) + HAVE_WCHAR_T=1; AC_SUBST([HAVE_WCHAR_T]) +]) diff --git a/m4/stdint.m4 b/m4/stdint.m4 new file mode 100644 index 0000000..c75e957 --- /dev/null +++ b/m4/stdint.m4 @@ -0,0 +1,480 @@ +# stdint.m4 serial 41 +dnl Copyright (C) 2001-2011 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +dnl From Paul Eggert and Bruno Haible. +dnl Test whether is supported or must be substituted. + +AC_DEFUN_ONCE([gl_STDINT_H], +[ + AC_PREREQ([2.59])dnl + + dnl Check for long long int and unsigned long long int. + AC_REQUIRE([AC_TYPE_LONG_LONG_INT]) + if test $ac_cv_type_long_long_int = yes; then + HAVE_LONG_LONG_INT=1 + else + HAVE_LONG_LONG_INT=0 + fi + AC_SUBST([HAVE_LONG_LONG_INT]) + AC_REQUIRE([AC_TYPE_UNSIGNED_LONG_LONG_INT]) + if test $ac_cv_type_unsigned_long_long_int = yes; then + HAVE_UNSIGNED_LONG_LONG_INT=1 + else + HAVE_UNSIGNED_LONG_LONG_INT=0 + fi + AC_SUBST([HAVE_UNSIGNED_LONG_LONG_INT]) + + dnl Check for , in the same way as gl_WCHAR_H does. + AC_CHECK_HEADERS_ONCE([wchar.h]) + if test $ac_cv_header_wchar_h = yes; then + HAVE_WCHAR_H=1 + else + HAVE_WCHAR_H=0 + fi + AC_SUBST([HAVE_WCHAR_H]) + + dnl Check for . + dnl AC_INCLUDES_DEFAULT defines $ac_cv_header_inttypes_h. + if test $ac_cv_header_inttypes_h = yes; then + HAVE_INTTYPES_H=1 + else + HAVE_INTTYPES_H=0 + fi + AC_SUBST([HAVE_INTTYPES_H]) + + dnl Check for . + dnl AC_INCLUDES_DEFAULT defines $ac_cv_header_sys_types_h. + if test $ac_cv_header_sys_types_h = yes; then + HAVE_SYS_TYPES_H=1 + else + HAVE_SYS_TYPES_H=0 + fi + AC_SUBST([HAVE_SYS_TYPES_H]) + + gl_CHECK_NEXT_HEADERS([stdint.h]) + if test $ac_cv_header_stdint_h = yes; then + HAVE_STDINT_H=1 + else + HAVE_STDINT_H=0 + fi + AC_SUBST([HAVE_STDINT_H]) + + dnl Now see whether we need a substitute . + if test $ac_cv_header_stdint_h = yes; then + AC_CACHE_CHECK([whether stdint.h conforms to C99], + [gl_cv_header_working_stdint_h], + [gl_cv_header_working_stdint_h=no + AC_COMPILE_IFELSE([ + AC_LANG_PROGRAM([[ +#define __STDC_LIMIT_MACROS 1 /* to make it work also in C++ mode */ +#define __STDC_CONSTANT_MACROS 1 /* to make it work also in C++ mode */ +#define _GL_JUST_INCLUDE_SYSTEM_STDINT_H 1 /* work if build isn't clean */ +#include +/* Dragonfly defines WCHAR_MIN, WCHAR_MAX only in . */ +#if !(defined WCHAR_MIN && defined WCHAR_MAX) +#error "WCHAR_MIN, WCHAR_MAX not defined in " +#endif +] +gl_STDINT_INCLUDES +[ +#ifdef INT8_MAX +int8_t a1 = INT8_MAX; +int8_t a1min = INT8_MIN; +#endif +#ifdef INT16_MAX +int16_t a2 = INT16_MAX; +int16_t a2min = INT16_MIN; +#endif +#ifdef INT32_MAX +int32_t a3 = INT32_MAX; +int32_t a3min = INT32_MIN; +#endif +#ifdef INT64_MAX +int64_t a4 = INT64_MAX; +int64_t a4min = INT64_MIN; +#endif +#ifdef UINT8_MAX +uint8_t b1 = UINT8_MAX; +#else +typedef int b1[(unsigned char) -1 != 255 ? 1 : -1]; +#endif +#ifdef UINT16_MAX +uint16_t b2 = UINT16_MAX; +#endif +#ifdef UINT32_MAX +uint32_t b3 = UINT32_MAX; +#endif +#ifdef UINT64_MAX +uint64_t b4 = UINT64_MAX; +#endif +int_least8_t c1 = INT8_C (0x7f); +int_least8_t c1max = INT_LEAST8_MAX; +int_least8_t c1min = INT_LEAST8_MIN; +int_least16_t c2 = INT16_C (0x7fff); +int_least16_t c2max = INT_LEAST16_MAX; +int_least16_t c2min = INT_LEAST16_MIN; +int_least32_t c3 = INT32_C (0x7fffffff); +int_least32_t c3max = INT_LEAST32_MAX; +int_least32_t c3min = INT_LEAST32_MIN; +int_least64_t c4 = INT64_C (0x7fffffffffffffff); +int_least64_t c4max = INT_LEAST64_MAX; +int_least64_t c4min = INT_LEAST64_MIN; +uint_least8_t d1 = UINT8_C (0xff); +uint_least8_t d1max = UINT_LEAST8_MAX; +uint_least16_t d2 = UINT16_C (0xffff); +uint_least16_t d2max = UINT_LEAST16_MAX; +uint_least32_t d3 = UINT32_C (0xffffffff); +uint_least32_t d3max = UINT_LEAST32_MAX; +uint_least64_t d4 = UINT64_C (0xffffffffffffffff); +uint_least64_t d4max = UINT_LEAST64_MAX; +int_fast8_t e1 = INT_FAST8_MAX; +int_fast8_t e1min = INT_FAST8_MIN; +int_fast16_t e2 = INT_FAST16_MAX; +int_fast16_t e2min = INT_FAST16_MIN; +int_fast32_t e3 = INT_FAST32_MAX; +int_fast32_t e3min = INT_FAST32_MIN; +int_fast64_t e4 = INT_FAST64_MAX; +int_fast64_t e4min = INT_FAST64_MIN; +uint_fast8_t f1 = UINT_FAST8_MAX; +uint_fast16_t f2 = UINT_FAST16_MAX; +uint_fast32_t f3 = UINT_FAST32_MAX; +uint_fast64_t f4 = UINT_FAST64_MAX; +#ifdef INTPTR_MAX +intptr_t g = INTPTR_MAX; +intptr_t gmin = INTPTR_MIN; +#endif +#ifdef UINTPTR_MAX +uintptr_t h = UINTPTR_MAX; +#endif +intmax_t i = INTMAX_MAX; +uintmax_t j = UINTMAX_MAX; + +#include /* for CHAR_BIT */ +#define TYPE_MINIMUM(t) \ + ((t) ((t) 0 < (t) -1 ? (t) 0 : ~ TYPE_MAXIMUM (t))) +#define TYPE_MAXIMUM(t) \ + ((t) ((t) 0 < (t) -1 \ + ? (t) -1 \ + : ((((t) 1 << (sizeof (t) * CHAR_BIT - 2)) - 1) * 2 + 1))) +struct s { + int check_PTRDIFF: + PTRDIFF_MIN == TYPE_MINIMUM (ptrdiff_t) + && PTRDIFF_MAX == TYPE_MAXIMUM (ptrdiff_t) + ? 1 : -1; + /* Detect bug in FreeBSD 6.0 / ia64. */ + int check_SIG_ATOMIC: + SIG_ATOMIC_MIN == TYPE_MINIMUM (sig_atomic_t) + && SIG_ATOMIC_MAX == TYPE_MAXIMUM (sig_atomic_t) + ? 1 : -1; + int check_SIZE: SIZE_MAX == TYPE_MAXIMUM (size_t) ? 1 : -1; + int check_WCHAR: + WCHAR_MIN == TYPE_MINIMUM (wchar_t) + && WCHAR_MAX == TYPE_MAXIMUM (wchar_t) + ? 1 : -1; + /* Detect bug in mingw. */ + int check_WINT: + WINT_MIN == TYPE_MINIMUM (wint_t) + && WINT_MAX == TYPE_MAXIMUM (wint_t) + ? 1 : -1; + + /* Detect bugs in glibc 2.4 and Solaris 10 stdint.h, among others. */ + int check_UINT8_C: + (-1 < UINT8_C (0)) == (-1 < (uint_least8_t) 0) ? 1 : -1; + int check_UINT16_C: + (-1 < UINT16_C (0)) == (-1 < (uint_least16_t) 0) ? 1 : -1; + + /* Detect bugs in OpenBSD 3.9 stdint.h. */ +#ifdef UINT8_MAX + int check_uint8: (uint8_t) -1 == UINT8_MAX ? 1 : -1; +#endif +#ifdef UINT16_MAX + int check_uint16: (uint16_t) -1 == UINT16_MAX ? 1 : -1; +#endif +#ifdef UINT32_MAX + int check_uint32: (uint32_t) -1 == UINT32_MAX ? 1 : -1; +#endif +#ifdef UINT64_MAX + int check_uint64: (uint64_t) -1 == UINT64_MAX ? 1 : -1; +#endif + int check_uint_least8: (uint_least8_t) -1 == UINT_LEAST8_MAX ? 1 : -1; + int check_uint_least16: (uint_least16_t) -1 == UINT_LEAST16_MAX ? 1 : -1; + int check_uint_least32: (uint_least32_t) -1 == UINT_LEAST32_MAX ? 1 : -1; + int check_uint_least64: (uint_least64_t) -1 == UINT_LEAST64_MAX ? 1 : -1; + int check_uint_fast8: (uint_fast8_t) -1 == UINT_FAST8_MAX ? 1 : -1; + int check_uint_fast16: (uint_fast16_t) -1 == UINT_FAST16_MAX ? 1 : -1; + int check_uint_fast32: (uint_fast32_t) -1 == UINT_FAST32_MAX ? 1 : -1; + int check_uint_fast64: (uint_fast64_t) -1 == UINT_FAST64_MAX ? 1 : -1; + int check_uintptr: (uintptr_t) -1 == UINTPTR_MAX ? 1 : -1; + int check_uintmax: (uintmax_t) -1 == UINTMAX_MAX ? 1 : -1; + int check_size: (size_t) -1 == SIZE_MAX ? 1 : -1; +}; + ]])], + [dnl Determine whether the various *_MIN, *_MAX macros are usable + dnl in preprocessor expression. We could do it by compiling a test + dnl program for each of these macros. It is faster to run a program + dnl that inspects the macro expansion. + dnl This detects a bug on HP-UX 11.23/ia64. + AC_RUN_IFELSE([ + AC_LANG_PROGRAM([[ +#define __STDC_LIMIT_MACROS 1 /* to make it work also in C++ mode */ +#define __STDC_CONSTANT_MACROS 1 /* to make it work also in C++ mode */ +#define _GL_JUST_INCLUDE_SYSTEM_STDINT_H 1 /* work if build isn't clean */ +#include +] +gl_STDINT_INCLUDES +[ +#include +#include +#define MVAL(macro) MVAL1(macro) +#define MVAL1(expression) #expression +static const char *macro_values[] = + { +#ifdef INT8_MAX + MVAL (INT8_MAX), +#endif +#ifdef INT16_MAX + MVAL (INT16_MAX), +#endif +#ifdef INT32_MAX + MVAL (INT32_MAX), +#endif +#ifdef INT64_MAX + MVAL (INT64_MAX), +#endif +#ifdef UINT8_MAX + MVAL (UINT8_MAX), +#endif +#ifdef UINT16_MAX + MVAL (UINT16_MAX), +#endif +#ifdef UINT32_MAX + MVAL (UINT32_MAX), +#endif +#ifdef UINT64_MAX + MVAL (UINT64_MAX), +#endif + NULL + }; +]], [[ + const char **mv; + for (mv = macro_values; *mv != NULL; mv++) + { + const char *value = *mv; + /* Test whether it looks like a cast expression. */ + if (strncmp (value, "((unsigned int)"/*)*/, 15) == 0 + || strncmp (value, "((unsigned short)"/*)*/, 17) == 0 + || strncmp (value, "((unsigned char)"/*)*/, 16) == 0 + || strncmp (value, "((int)"/*)*/, 6) == 0 + || strncmp (value, "((signed short)"/*)*/, 15) == 0 + || strncmp (value, "((signed char)"/*)*/, 14) == 0) + return mv - macro_values + 1; + } + return 0; +]])], + [gl_cv_header_working_stdint_h=yes], + [], + [dnl When cross-compiling, assume it works. + gl_cv_header_working_stdint_h=yes + ]) + ]) + ]) + fi + if test "$gl_cv_header_working_stdint_h" = yes; then + STDINT_H= + else + dnl Check for , and for + dnl (used in Linux libc4 >= 4.6.7 and libc5). + AC_CHECK_HEADERS([sys/inttypes.h sys/bitypes.h]) + if test $ac_cv_header_sys_inttypes_h = yes; then + HAVE_SYS_INTTYPES_H=1 + else + HAVE_SYS_INTTYPES_H=0 + fi + AC_SUBST([HAVE_SYS_INTTYPES_H]) + if test $ac_cv_header_sys_bitypes_h = yes; then + HAVE_SYS_BITYPES_H=1 + else + HAVE_SYS_BITYPES_H=0 + fi + AC_SUBST([HAVE_SYS_BITYPES_H]) + + gl_STDINT_TYPE_PROPERTIES + STDINT_H=stdint.h + fi + AC_SUBST([STDINT_H]) + AM_CONDITIONAL([GL_GENERATE_STDINT_H], [test -n "$STDINT_H"]) +]) + +dnl gl_STDINT_BITSIZEOF(TYPES, INCLUDES) +dnl Determine the size of each of the given types in bits. +AC_DEFUN([gl_STDINT_BITSIZEOF], +[ + dnl Use a shell loop, to avoid bloating configure, and + dnl - extra AH_TEMPLATE calls, so that autoheader knows what to put into + dnl config.h.in, + dnl - extra AC_SUBST calls, so that the right substitutions are made. + m4_foreach_w([gltype], [$1], + [AH_TEMPLATE([BITSIZEOF_]m4_translit(gltype,[abcdefghijklmnopqrstuvwxyz ],[ABCDEFGHIJKLMNOPQRSTUVWXYZ_]), + [Define to the number of bits in type ']gltype['.])]) + for gltype in $1 ; do + AC_CACHE_CHECK([for bit size of $gltype], [gl_cv_bitsizeof_${gltype}], + [AC_COMPUTE_INT([result], [sizeof ($gltype) * CHAR_BIT], + [$2 +#include ], [result=unknown]) + eval gl_cv_bitsizeof_${gltype}=\$result + ]) + eval result=\$gl_cv_bitsizeof_${gltype} + if test $result = unknown; then + dnl Use a nonempty default, because some compilers, such as IRIX 5 cc, + dnl do a syntax check even on unused #if conditions and give an error + dnl on valid C code like this: + dnl #if 0 + dnl # if > 32 + dnl # endif + dnl #endif + result=0 + fi + GLTYPE=`echo "$gltype" | tr 'abcdefghijklmnopqrstuvwxyz ' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ_'` + AC_DEFINE_UNQUOTED([BITSIZEOF_${GLTYPE}], [$result]) + eval BITSIZEOF_${GLTYPE}=\$result + done + m4_foreach_w([gltype], [$1], + [AC_SUBST([BITSIZEOF_]m4_translit(gltype,[abcdefghijklmnopqrstuvwxyz ],[ABCDEFGHIJKLMNOPQRSTUVWXYZ_]))]) +]) + +dnl gl_CHECK_TYPES_SIGNED(TYPES, INCLUDES) +dnl Determine the signedness of each of the given types. +dnl Define HAVE_SIGNED_TYPE if type is signed. +AC_DEFUN([gl_CHECK_TYPES_SIGNED], +[ + dnl Use a shell loop, to avoid bloating configure, and + dnl - extra AH_TEMPLATE calls, so that autoheader knows what to put into + dnl config.h.in, + dnl - extra AC_SUBST calls, so that the right substitutions are made. + m4_foreach_w([gltype], [$1], + [AH_TEMPLATE([HAVE_SIGNED_]m4_translit(gltype,[abcdefghijklmnopqrstuvwxyz ],[ABCDEFGHIJKLMNOPQRSTUVWXYZ_]), + [Define to 1 if ']gltype[' is a signed integer type.])]) + for gltype in $1 ; do + AC_CACHE_CHECK([whether $gltype is signed], [gl_cv_type_${gltype}_signed], + [AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([$2[ + int verify[2 * (($gltype) -1 < ($gltype) 0) - 1];]])], + result=yes, result=no) + eval gl_cv_type_${gltype}_signed=\$result + ]) + eval result=\$gl_cv_type_${gltype}_signed + GLTYPE=`echo $gltype | tr 'abcdefghijklmnopqrstuvwxyz ' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ_'` + if test "$result" = yes; then + AC_DEFINE_UNQUOTED([HAVE_SIGNED_${GLTYPE}], [1]) + eval HAVE_SIGNED_${GLTYPE}=1 + else + eval HAVE_SIGNED_${GLTYPE}=0 + fi + done + m4_foreach_w([gltype], [$1], + [AC_SUBST([HAVE_SIGNED_]m4_translit(gltype,[abcdefghijklmnopqrstuvwxyz ],[ABCDEFGHIJKLMNOPQRSTUVWXYZ_]))]) +]) + +dnl gl_INTEGER_TYPE_SUFFIX(TYPES, INCLUDES) +dnl Determine the suffix to use for integer constants of the given types. +dnl Define t_SUFFIX for each such type. +AC_DEFUN([gl_INTEGER_TYPE_SUFFIX], +[ + dnl Use a shell loop, to avoid bloating configure, and + dnl - extra AH_TEMPLATE calls, so that autoheader knows what to put into + dnl config.h.in, + dnl - extra AC_SUBST calls, so that the right substitutions are made. + m4_foreach_w([gltype], [$1], + [AH_TEMPLATE(m4_translit(gltype,[abcdefghijklmnopqrstuvwxyz ],[ABCDEFGHIJKLMNOPQRSTUVWXYZ_])[_SUFFIX], + [Define to l, ll, u, ul, ull, etc., as suitable for + constants of type ']gltype['.])]) + for gltype in $1 ; do + AC_CACHE_CHECK([for $gltype integer literal suffix], + [gl_cv_type_${gltype}_suffix], + [eval gl_cv_type_${gltype}_suffix=no + eval result=\$gl_cv_type_${gltype}_signed + if test "$result" = yes; then + glsufu= + else + glsufu=u + fi + for glsuf in "$glsufu" ${glsufu}l ${glsufu}ll ${glsufu}i64; do + case $glsuf in + '') gltype1='int';; + l) gltype1='long int';; + ll) gltype1='long long int';; + i64) gltype1='__int64';; + u) gltype1='unsigned int';; + ul) gltype1='unsigned long int';; + ull) gltype1='unsigned long long int';; + ui64)gltype1='unsigned __int64';; + esac + AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([$2[ + extern $gltype foo; + extern $gltype1 foo;]])], + [eval gl_cv_type_${gltype}_suffix=\$glsuf]) + eval result=\$gl_cv_type_${gltype}_suffix + test "$result" != no && break + done]) + GLTYPE=`echo $gltype | tr 'abcdefghijklmnopqrstuvwxyz ' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ_'` + eval result=\$gl_cv_type_${gltype}_suffix + test "$result" = no && result= + eval ${GLTYPE}_SUFFIX=\$result + AC_DEFINE_UNQUOTED([${GLTYPE}_SUFFIX], [$result]) + done + m4_foreach_w([gltype], [$1], + [AC_SUBST(m4_translit(gltype,[abcdefghijklmnopqrstuvwxyz ],[ABCDEFGHIJKLMNOPQRSTUVWXYZ_])[_SUFFIX])]) +]) + +dnl gl_STDINT_INCLUDES +AC_DEFUN([gl_STDINT_INCLUDES], +[[ + /* BSD/OS 4.0.1 has a bug: , and must be + included before . */ + #include + #include + #if HAVE_WCHAR_H + # include + # include + # include + #endif +]]) + +dnl gl_STDINT_TYPE_PROPERTIES +dnl Compute HAVE_SIGNED_t, BITSIZEOF_t and t_SUFFIX, for all the types t +dnl of interest to stdint.in.h. +AC_DEFUN([gl_STDINT_TYPE_PROPERTIES], +[ + AC_REQUIRE([gl_MULTIARCH]) + if test $APPLE_UNIVERSAL_BUILD = 0; then + gl_STDINT_BITSIZEOF([ptrdiff_t size_t], + [gl_STDINT_INCLUDES]) + fi + gl_STDINT_BITSIZEOF([sig_atomic_t wchar_t wint_t], + [gl_STDINT_INCLUDES]) + gl_CHECK_TYPES_SIGNED([sig_atomic_t wchar_t wint_t], + [gl_STDINT_INCLUDES]) + gl_cv_type_ptrdiff_t_signed=yes + gl_cv_type_size_t_signed=no + if test $APPLE_UNIVERSAL_BUILD = 0; then + gl_INTEGER_TYPE_SUFFIX([ptrdiff_t size_t], + [gl_STDINT_INCLUDES]) + fi + gl_INTEGER_TYPE_SUFFIX([sig_atomic_t wchar_t wint_t], + [gl_STDINT_INCLUDES]) +]) + +dnl Autoconf >= 2.61 has AC_COMPUTE_INT built-in. +dnl Remove this when we can assume autoconf >= 2.61. +m4_ifdef([AC_COMPUTE_INT], [], [ + AC_DEFUN([AC_COMPUTE_INT], [_AC_COMPUTE_INT([$2],[$1],[$3],[$4])]) +]) + +# Hey Emacs! +# Local Variables: +# indent-tabs-mode: nil +# End: diff --git a/m4/string_h.m4 b/m4/string_h.m4 new file mode 100644 index 0000000..df8c403 --- /dev/null +++ b/m4/string_h.m4 @@ -0,0 +1,116 @@ +# Configure a GNU-like replacement for . + +# Copyright (C) 2007-2011 Free Software Foundation, Inc. +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 20 + +# Written by Paul Eggert. + +AC_DEFUN([gl_HEADER_STRING_H], +[ + dnl Use AC_REQUIRE here, so that the default behavior below is expanded + dnl once only, before all statements that occur in other macros. + AC_REQUIRE([gl_HEADER_STRING_H_BODY]) +]) + +AC_DEFUN([gl_HEADER_STRING_H_BODY], +[ + AC_REQUIRE([AC_C_RESTRICT]) + AC_REQUIRE([gl_HEADER_STRING_H_DEFAULTS]) + gl_NEXT_HEADERS([string.h]) + + dnl Check for declarations of anything we want to poison if the + dnl corresponding gnulib module is not in use, and which is not + dnl guaranteed by C89. + gl_WARN_ON_USE_PREPARE([[#include + ]], + [memmem mempcpy memrchr rawmemchr stpcpy stpncpy strchrnul strdup + strncat strndup strnlen strpbrk strsep strcasestr strtok_r strerror_r + strsignal strverscmp]) +]) + +AC_DEFUN([gl_STRING_MODULE_INDICATOR], +[ + dnl Use AC_REQUIRE here, so that the default settings are expanded once only. + AC_REQUIRE([gl_HEADER_STRING_H_DEFAULTS]) + gl_MODULE_INDICATOR_SET_VARIABLE([$1]) + dnl Define it also as a C macro, for the benefit of the unit tests. + gl_MODULE_INDICATOR_FOR_TESTS([$1]) +]) + +AC_DEFUN([gl_HEADER_STRING_H_DEFAULTS], +[ + GNULIB_MEMCHR=0; AC_SUBST([GNULIB_MEMCHR]) + GNULIB_MEMMEM=0; AC_SUBST([GNULIB_MEMMEM]) + GNULIB_MEMPCPY=0; AC_SUBST([GNULIB_MEMPCPY]) + GNULIB_MEMRCHR=0; AC_SUBST([GNULIB_MEMRCHR]) + GNULIB_RAWMEMCHR=0; AC_SUBST([GNULIB_RAWMEMCHR]) + GNULIB_STPCPY=0; AC_SUBST([GNULIB_STPCPY]) + GNULIB_STPNCPY=0; AC_SUBST([GNULIB_STPNCPY]) + GNULIB_STRCHRNUL=0; AC_SUBST([GNULIB_STRCHRNUL]) + GNULIB_STRDUP=0; AC_SUBST([GNULIB_STRDUP]) + GNULIB_STRNCAT=0; AC_SUBST([GNULIB_STRNCAT]) + GNULIB_STRNDUP=0; AC_SUBST([GNULIB_STRNDUP]) + GNULIB_STRNLEN=0; AC_SUBST([GNULIB_STRNLEN]) + GNULIB_STRPBRK=0; AC_SUBST([GNULIB_STRPBRK]) + GNULIB_STRSEP=0; AC_SUBST([GNULIB_STRSEP]) + GNULIB_STRSTR=0; AC_SUBST([GNULIB_STRSTR]) + GNULIB_STRCASESTR=0; AC_SUBST([GNULIB_STRCASESTR]) + GNULIB_STRTOK_R=0; AC_SUBST([GNULIB_STRTOK_R]) + GNULIB_MBSLEN=0; AC_SUBST([GNULIB_MBSLEN]) + GNULIB_MBSNLEN=0; AC_SUBST([GNULIB_MBSNLEN]) + GNULIB_MBSCHR=0; AC_SUBST([GNULIB_MBSCHR]) + GNULIB_MBSRCHR=0; AC_SUBST([GNULIB_MBSRCHR]) + GNULIB_MBSSTR=0; AC_SUBST([GNULIB_MBSSTR]) + GNULIB_MBSCASECMP=0; AC_SUBST([GNULIB_MBSCASECMP]) + GNULIB_MBSNCASECMP=0; AC_SUBST([GNULIB_MBSNCASECMP]) + GNULIB_MBSPCASECMP=0; AC_SUBST([GNULIB_MBSPCASECMP]) + GNULIB_MBSCASESTR=0; AC_SUBST([GNULIB_MBSCASESTR]) + GNULIB_MBSCSPN=0; AC_SUBST([GNULIB_MBSCSPN]) + GNULIB_MBSPBRK=0; AC_SUBST([GNULIB_MBSPBRK]) + GNULIB_MBSSPN=0; AC_SUBST([GNULIB_MBSSPN]) + GNULIB_MBSSEP=0; AC_SUBST([GNULIB_MBSSEP]) + GNULIB_MBSTOK_R=0; AC_SUBST([GNULIB_MBSTOK_R]) + GNULIB_STRERROR=0; AC_SUBST([GNULIB_STRERROR]) + GNULIB_STRERROR_R=0; AC_SUBST([GNULIB_STRERROR_R]) + GNULIB_STRSIGNAL=0; AC_SUBST([GNULIB_STRSIGNAL]) + GNULIB_STRVERSCMP=0; AC_SUBST([GNULIB_STRVERSCMP]) + HAVE_MBSLEN=0; AC_SUBST([HAVE_MBSLEN]) + dnl Assume proper GNU behavior unless another module says otherwise. + HAVE_MEMCHR=1; AC_SUBST([HAVE_MEMCHR]) + HAVE_DECL_MEMMEM=1; AC_SUBST([HAVE_DECL_MEMMEM]) + HAVE_MEMPCPY=1; AC_SUBST([HAVE_MEMPCPY]) + HAVE_DECL_MEMRCHR=1; AC_SUBST([HAVE_DECL_MEMRCHR]) + HAVE_RAWMEMCHR=1; AC_SUBST([HAVE_RAWMEMCHR]) + HAVE_STPCPY=1; AC_SUBST([HAVE_STPCPY]) + HAVE_STPNCPY=1; AC_SUBST([HAVE_STPNCPY]) + HAVE_STRCHRNUL=1; AC_SUBST([HAVE_STRCHRNUL]) + HAVE_DECL_STRDUP=1; AC_SUBST([HAVE_DECL_STRDUP]) + HAVE_DECL_STRNDUP=1; AC_SUBST([HAVE_DECL_STRNDUP]) + HAVE_DECL_STRNLEN=1; AC_SUBST([HAVE_DECL_STRNLEN]) + HAVE_STRPBRK=1; AC_SUBST([HAVE_STRPBRK]) + HAVE_STRSEP=1; AC_SUBST([HAVE_STRSEP]) + HAVE_STRCASESTR=1; AC_SUBST([HAVE_STRCASESTR]) + HAVE_DECL_STRTOK_R=1; AC_SUBST([HAVE_DECL_STRTOK_R]) + HAVE_DECL_STRERROR_R=1; AC_SUBST([HAVE_DECL_STRERROR_R]) + HAVE_DECL_STRSIGNAL=1; AC_SUBST([HAVE_DECL_STRSIGNAL]) + HAVE_STRVERSCMP=1; AC_SUBST([HAVE_STRVERSCMP]) + REPLACE_MEMCHR=0; AC_SUBST([REPLACE_MEMCHR]) + REPLACE_MEMMEM=0; AC_SUBST([REPLACE_MEMMEM]) + REPLACE_STPNCPY=0; AC_SUBST([REPLACE_STPNCPY]) + REPLACE_STRDUP=0; AC_SUBST([REPLACE_STRDUP]) + REPLACE_STRSTR=0; AC_SUBST([REPLACE_STRSTR]) + REPLACE_STRCASESTR=0; AC_SUBST([REPLACE_STRCASESTR]) + REPLACE_STRCHRNUL=0; AC_SUBST([REPLACE_STRCHRNUL]) + REPLACE_STRERROR=0; AC_SUBST([REPLACE_STRERROR]) + REPLACE_STRERROR_R=0; AC_SUBST([REPLACE_STRERROR_R]) + REPLACE_STRNCAT=0; AC_SUBST([REPLACE_STRNCAT]) + REPLACE_STRNDUP=0; AC_SUBST([REPLACE_STRNDUP]) + REPLACE_STRNLEN=0; AC_SUBST([REPLACE_STRNLEN]) + REPLACE_STRSIGNAL=0; AC_SUBST([REPLACE_STRSIGNAL]) + REPLACE_STRTOK_R=0; AC_SUBST([REPLACE_STRTOK_R]) + UNDEFINE_STRTOK_R=0; AC_SUBST([UNDEFINE_STRTOK_R]) +]) diff --git a/m4/warn-on-use.m4 b/m4/warn-on-use.m4 new file mode 100644 index 0000000..e0d0f27 --- /dev/null +++ b/m4/warn-on-use.m4 @@ -0,0 +1,45 @@ +# warn-on-use.m4 serial 2 +dnl Copyright (C) 2010-2011 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +# gl_WARN_ON_USE_PREPARE(INCLUDES, NAMES) +# --------------------------------------- +# For each whitespace-separated element in the list of NAMES, define +# HAVE_RAW_DECL_name if the function has a declaration among INCLUDES +# even after being undefined as a macro. +# +# See warn-on-use.h for some hints on how to poison function names, as +# well as ideas on poisoning global variables and macros. NAMES may +# include global variables, but remember that only functions work with +# _GL_WARN_ON_USE. Typically, INCLUDES only needs to list a single +# header, but if the replacement header pulls in other headers because +# some systems declare functions in the wrong header, then INCLUDES +# should do likewise. +# +# If you assume C89, then it is generally safe to assume declarations +# for functions declared in that standard (such as gets) without +# needing gl_WARN_ON_USE_PREPARE. +AC_DEFUN([gl_WARN_ON_USE_PREPARE], +[ + m4_foreach_w([gl_decl], [$2], + [AH_TEMPLATE([HAVE_RAW_DECL_]AS_TR_CPP(m4_defn([gl_decl])), + [Define to 1 if ]m4_defn([gl_decl])[ is declared even after + undefining macros.])])dnl + for gl_func in m4_flatten([$2]); do + AS_VAR_PUSHDEF([gl_Symbol], [gl_cv_have_raw_decl_$gl_func])dnl + AC_CACHE_CHECK([whether $gl_func is declared without a macro], + gl_Symbol, + [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([$1], +[@%:@undef $gl_func + (void) $gl_func;])], + [AS_VAR_SET(gl_Symbol, [yes])], [AS_VAR_SET(gl_Symbol, [no])])]) + AS_VAR_IF(gl_Symbol, [yes], + [AC_DEFINE_UNQUOTED(AS_TR_CPP([HAVE_RAW_DECL_$gl_func]), [1]) + dnl shortcut - if the raw declaration exists, then set a cache + dnl variable to allow skipping any later AC_CHECK_DECL efforts + eval ac_cv_have_decl_$gl_func=yes]) + AS_VAR_POPDEF([gl_Symbol])dnl + done +]) diff --git a/m4/wchar_t.m4 b/m4/wchar_t.m4 new file mode 100644 index 0000000..d2c03c4 --- /dev/null +++ b/m4/wchar_t.m4 @@ -0,0 +1,24 @@ +# wchar_t.m4 serial 4 (gettext-0.18.2) +dnl Copyright (C) 2002-2003, 2008-2011 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +dnl From Bruno Haible. +dnl Test whether has the 'wchar_t' type. +dnl Prerequisite: AC_PROG_CC + +AC_DEFUN([gt_TYPE_WCHAR_T], +[ + AC_CACHE_CHECK([for wchar_t], [gt_cv_c_wchar_t], + [AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM( + [[#include + wchar_t foo = (wchar_t)'\0';]], + [[]])], + [gt_cv_c_wchar_t=yes], + [gt_cv_c_wchar_t=no])]) + if test $gt_cv_c_wchar_t = yes; then + AC_DEFINE([HAVE_WCHAR_T], [1], [Define if you have the 'wchar_t' type.]) + fi +]) diff --git a/mcp2210.c b/mcp2210.c new file mode 100644 index 0000000..83ff95f --- /dev/null +++ b/mcp2210.c @@ -0,0 +1,366 @@ +/* + * Copyright 2014 Con Kolivas + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ +#include "miner.h" +#include "usbutils.h" +#include "mcp2210.h" + +static bool mcp2210_send(struct cgpu_info *cgpu, char *buf, enum usb_cmds cmd) +{ + int amount, err; + + if (unlikely(cgpu->usbinfo.nodev)) + return false; + + err = usb_write(cgpu, buf, MCP2210_BUFFER_LENGTH, &amount, cmd); + if (err || amount != MCP2210_BUFFER_LENGTH) { + applog(LOG_WARNING, "%s %d: Error %d sending %s sent %d of %d", + cgpu->drv->name, cgpu->device_id, err, usb_cmdname(cmd), + amount, MCP2210_BUFFER_LENGTH); + return false; + } + return true; +} + +static bool mcp2210_recv(struct cgpu_info *cgpu, char *buf, enum usb_cmds cmd) +{ + int amount, err; + + if (unlikely(cgpu->usbinfo.nodev)) + return false; + + err = usb_read(cgpu, buf, MCP2210_BUFFER_LENGTH, &amount, cmd); + if (err || amount != MCP2210_BUFFER_LENGTH) { + applog(LOG_WARNING, "%s %d: Error %d receiving %s received %d of %d", + cgpu->drv->name, cgpu->device_id, err, usb_cmdname(cmd), + amount, MCP2210_BUFFER_LENGTH); + return false; + } + return true; +} + +bool mcp2210_send_recv(struct cgpu_info *cgpu, char *buf, enum usb_cmds cmd) +{ + uint8_t mcp_cmd = buf[0]; + + if (!mcp2210_send(cgpu, buf, cmd)) + return false; + + if (!mcp2210_recv(cgpu, buf, cmd)) + return false; + + /* Return code should always echo original command */ + if (buf[0] != mcp_cmd) { + applog(LOG_WARNING, "%s %d: Response code mismatch, asked for %u got %u", + cgpu->drv->name, cgpu->device_id, mcp_cmd, buf[0]); + return false; + } + return true; +} + +bool mcp2210_get_gpio_settings(struct cgpu_info *cgpu, struct mcp_settings *mcp) +{ + char buf[MCP2210_BUFFER_LENGTH]; + int i; + + memset(buf, 0, MCP2210_BUFFER_LENGTH); + buf[0] = MCP2210_GET_GPIO_SETTING; + if (!mcp2210_send_recv(cgpu, buf, C_MCP_GETGPIOSETTING)) + return false; + + for (i = 0; i < 8; i++) { + mcp->designation.pin[i] = buf[4 + i]; + mcp->value.pin[i] = !!(buf[13] & (0x01u << i)); + mcp->direction.pin[i] = !!(buf[15] & (0x01u << i)); + } + mcp->designation.pin[8] = buf[12]; + mcp->value.pin[8] = buf[14] & 0x01u; + mcp->direction.pin[8] = buf[16] & 0x01u; + + return true; +} + +bool mcp2210_set_gpio_settings(struct cgpu_info *cgpu, struct mcp_settings *mcp) +{ + char buf[MCP2210_BUFFER_LENGTH]; + uint8_t buf17; + int i; + + memset(buf, 0, MCP2210_BUFFER_LENGTH); + buf[0] = MCP2210_GET_GPIO_SETTING; + if (!mcp2210_send_recv(cgpu, buf, C_MCP_GETGPIOSETTING)) + return false; + buf17 = buf[17]; + + memset(buf, 0, MCP2210_BUFFER_LENGTH); + buf[0] = MCP2210_SET_GPIO_SETTING; + buf[17] = buf17; + for (i = 0; i < 8; i++) { + buf[4 + i] = mcp->designation.pin[i]; + buf[13] |= mcp->value.pin[i] << i; + buf[15] |= mcp->direction.pin[i] << i; + } + buf[12] = mcp->designation.pin[8]; + buf[14] = mcp->value.pin[8]; + buf[16] = mcp->direction.pin[8]; + return mcp2210_send_recv(cgpu, buf, C_MCP_SETGPIOSETTING); +} + +/* Get all the pin designations and store them in a gpio_pin struct */ +bool mcp2210_get_gpio_pindes(struct cgpu_info *cgpu, struct gpio_pin *gp) +{ + char buf[MCP2210_BUFFER_LENGTH]; + int i; + + memset(buf, 0, MCP2210_BUFFER_LENGTH); + buf[0] = MCP2210_GET_GPIO_SETTING; + if (!mcp2210_send_recv(cgpu, buf, C_MCP_GETGPIOSETTING)) + return false; + + for (i = 0; i < 9; i++) + gp->pin[i] = buf[4 + i]; + return true; +} + + +/* Get all the pin vals and store them in a gpio_pin struct */ +bool mcp2210_get_gpio_pinvals(struct cgpu_info *cgpu, struct gpio_pin *gp) +{ + char buf[MCP2210_BUFFER_LENGTH]; + int i; + + memset(buf, 0, MCP2210_BUFFER_LENGTH); + buf[0] = MCP2210_GET_GPIO_PIN_VAL; + if (!mcp2210_send_recv(cgpu, buf, C_MCP_GETGPIOPINVAL)) + return false; + + for (i = 0; i < 8; i++) + gp->pin[i] = !!(buf[4] & (0x01u << i)); + gp->pin[8] = buf[5] & 0x01u; + + return true; +} + +/* Get all the pindirs */ +bool mcp2210_get_gpio_pindirs(struct cgpu_info *cgpu, struct gpio_pin *gp) +{ + char buf[MCP2210_BUFFER_LENGTH]; + int i; + + memset(buf, 0, MCP2210_BUFFER_LENGTH); + buf[0] = MCP2210_GET_GPIO_PIN_DIR; + if (!mcp2210_send_recv(cgpu, buf, C_MCP_GETGPIOPINDIR)) + return false; + + for (i = 0; i < 8; i++) + gp->pin[i] = !!(buf[4] & (0x01u << i)); + gp->pin[8] = buf[5] & 0x01u; + + return true; +} + +/* Get the designation of one pin */ +bool mcp2210_get_gpio_pin(struct cgpu_info *cgpu, int pin, int *des) +{ + struct gpio_pin gp; + + if (!mcp2210_get_gpio_pindes(cgpu, &gp)) + return false; + + *des = gp.pin[pin]; + return true; +} + +/* Get one pinval */ +bool mcp2210_get_gpio_pinval(struct cgpu_info *cgpu, int pin, int *val) +{ + char buf[MCP2210_BUFFER_LENGTH]; + + memset(buf, 0, MCP2210_BUFFER_LENGTH); + buf[0] = MCP2210_GET_GPIO_PIN_VAL; + if (!mcp2210_send_recv(cgpu, buf, C_MCP_GETGPIOPINVAL)) + return false; + + buf[0] = MCP2210_GET_GPIO_PIN_VAL; + + if (pin < 8) + *val = !!(buf[4] & (0x01u << pin)); + else + *val = !!(buf[5] & 0x01u); + + return true; +} + +/* Get one pindir */ +bool mcp2210_get_gpio_pindir(struct cgpu_info *cgpu, int pin, int *dir) +{ + char buf[MCP2210_BUFFER_LENGTH]; + + memset(buf, 0, MCP2210_BUFFER_LENGTH); + buf[0] = MCP2210_GET_GPIO_PIN_DIR; + if (!mcp2210_send_recv(cgpu, buf, C_MCP_GETGPIOPINDIR)) + return false; + + buf[0] = MCP2210_GET_GPIO_PIN_DIR; + + if (pin < 8) + *dir = !!(buf[4] & (0x01u << pin)); + else + *dir = !!(buf[5] & 0x01u); + + return true; +} + +bool mcp2210_spi_cancel(struct cgpu_info *cgpu) +{ + char buf[MCP2210_BUFFER_LENGTH]; + + memset(buf, 0, MCP2210_BUFFER_LENGTH); + buf[0] = MCP2210_SPI_CANCEL; + return mcp2210_send_recv(cgpu, buf, C_MCP_SPICANCEL); +} + +/* Abbreviations correspond to: + * IdleChipSelectValue, ActiveChipSelectValue, CSToDataDelay, LastDataByteToCSDelay, + * SubsequentDataByteDelay, BytesPerSPITransfer + */ +bool +mcp2210_get_spi_transfer_settings(struct cgpu_info *cgpu, unsigned int *bitrate, unsigned int *icsv, + unsigned int *acsv, unsigned int *cstdd, unsigned int *ldbtcsd, + unsigned int *sdbd, unsigned int *bpst, unsigned int *spimode) +{ + char buf[MCP2210_BUFFER_LENGTH]; + + memset(buf, 0, MCP2210_BUFFER_LENGTH); + buf[0] = MCP2210_GET_SPI_SETTING; + + if (!mcp2210_send_recv(cgpu, buf, C_MCP_GETSPISETTING)) + return false; + *bitrate = buf[7] << 24 | buf[6] << 16 | buf[5] << 8 | buf[4]; + *icsv = (buf[9] & 0x1) << 8 | buf[8]; + *acsv = (buf[11] & 0x1) << 8 | buf[10]; + *cstdd = buf[13] << 8 | buf[12]; + *ldbtcsd = buf[15] << 8 | buf[14]; + *sdbd = buf[17] << 8 | buf[16]; + *bpst = buf[19] << 8 | buf[18]; + *spimode = buf[20]; + return true; +} + +bool +mcp2210_set_spi_transfer_settings(struct cgpu_info *cgpu, unsigned int bitrate, unsigned int icsv, + unsigned int acsv, unsigned int cstdd, unsigned int ldbtcsd, + unsigned int sdbd, unsigned int bpst, unsigned int spimode) +{ + char buf[MCP2210_BUFFER_LENGTH]; + bool ret; + + memset(buf, 0, MCP2210_BUFFER_LENGTH); + buf[0] = MCP2210_SET_SPI_SETTING; + + buf[4] = bitrate & 0xfful; + buf[5] = (bitrate & 0xff00ul) >> 8; + buf[6] = (bitrate & 0xff0000ul) >> 16; + buf[7] = (bitrate & 0xff000000ul) >> 24; + + buf[8] = icsv & 0xff; + buf[9] = (icsv & 0x100) >> 8; + + buf[10] = acsv & 0xff; + buf[11] = (acsv & 0x100) >> 8; + + buf[12] = cstdd & 0xff; + buf[13] = (cstdd & 0xff00) >> 8; + + buf[14] = ldbtcsd & 0xff; + buf[15] = (ldbtcsd & 0xff00) >> 8; + + buf[16] = sdbd & 0xff; + buf[17] = (sdbd & 0xff00) >> 8; + + buf[18] = bpst & 0xff; + buf[19] = (bpst & 0xff00) >> 8; + + buf[20] = spimode; + ret = mcp2210_send_recv(cgpu, buf, C_MCP_SETSPISETTING); + if (!ret) + return ret; + if (buf[1] != 0) { + applog(LOG_DEBUG, "Failed to set spi settings"); + return false; + } + return true; +} + +/* Perform an spi transfer of *length bytes and return the amount of data + * returned in the same buffer in *length */ +bool mcp2210_spi_transfer(struct cgpu_info *cgpu, struct mcp_settings *mcp, + char *data, unsigned int *length) +{ + uint8_t res, status, orig_len, offset = 0; + char buf[MCP2210_BUFFER_LENGTH]; + + if (unlikely(*length > MCP2210_TRANSFER_MAX || !*length)) { + applog(LOG_ERR, "%s %d: Unable to spi transfer %u bytes", cgpu->drv->name, + cgpu->device_id, *length); + return false; + } + if (mcp->bpst != *length) { + /* Set the transfer setting only when it changes. */ + mcp->bpst = *length; + if (!mcp2210_set_spi_transfer_settings(cgpu, mcp->bitrate, mcp->icsv, + mcp->acsv, mcp->cstdd, mcp->ldbtcsd, mcp->sdbd, mcp->bpst, mcp->spimode)) + return false; + } + orig_len = *length; +retry: + applog(LOG_DEBUG, "%s %d: SPI sending %u bytes", cgpu->drv->name, cgpu->device_id, + *length); + memset(buf, 0, MCP2210_BUFFER_LENGTH); + buf[0] = MCP2210_SPI_TRANSFER; + buf[1] = *length; + + if (*length) + memcpy(buf + 4, data + offset, *length); + if (!mcp2210_send_recv(cgpu, buf, C_MCP_SPITRANSFER)) + return false; + + res = (uint8_t)buf[1]; + switch(res) { + case MCP2210_SPI_TRANSFER_SUCCESS: + *length = buf[2]; + status = buf[3]; + applog(LOG_DEBUG, "%s %d: SPI transfer success, received %u bytes status 0x%x", + cgpu->drv->name, cgpu->device_id, *length, status); + if (*length) { + memcpy(data + offset, buf + 4, *length); + offset += *length; + } + if (status == 0x30) { + /* This shouldn't happen */ + applog(LOG_DEBUG, "%s %d: SPI expecting more data inappropriately", + cgpu->drv->name, cgpu->device_id); + return false; + } + if (offset < orig_len) { + *length = 0; + goto retry; + } + *length = orig_len; + return true; + case MCP2210_SPI_TRANSFER_ERROR_IP: + applog(LOG_DEBUG, "%s %d: SPI transfer error in progress", + cgpu->drv->name, cgpu->device_id); + goto retry; + case MCP2210_SPI_TRANSFER_ERROR_NA: + applog(LOG_WARNING, "%s %d: External owner error on mcp2210 spi transfer", + cgpu->drv->name, cgpu->device_id); + default: + return false; + } +} diff --git a/mcp2210.h b/mcp2210.h new file mode 100644 index 0000000..cb75443 --- /dev/null +++ b/mcp2210.h @@ -0,0 +1,73 @@ +/* + * Copyright 2014 Con Kolivas + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#ifndef MCP2210_H +#define MCP2210_H + +#define MCP2210_BUFFER_LENGTH 64 +#define MCP2210_TRANSFER_MAX 60 + +#define MCP2210_PIN_GPIO 0x0 +#define MCP2210_PIN_CS 0x1 +#define MCP2210_PIN_DEDICATED 0x2 + +#define MCP2210_GPIO_PIN_LOW 0 +#define MCP2210_GPIO_PIN_HIGH 1 + +#define MCP2210_GPIO_OUTPUT 0 +#define MCP2210_GPIO_INPUT 1 + +#define MCP2210_SPI_CANCEL 0x11 +#define MCP2210_GET_GPIO_SETTING 0x20 +#define MCP2210_SET_GPIO_SETTING 0x21 +#define MCP2210_SET_GPIO_PIN_VAL 0x30 +#define MCP2210_GET_GPIO_PIN_VAL 0x31 +#define MCP2210_SET_GPIO_PIN_DIR 0x32 +#define MCP2210_GET_GPIO_PIN_DIR 0x33 +#define MCP2210_SET_SPI_SETTING 0X40 +#define MCP2210_GET_SPI_SETTING 0X41 +#define MCP2210_SPI_TRANSFER 0x42 + +#define MCP2210_SPI_TRANSFER_SUCCESS 0x00 +#define MCP2210_SPI_TRANSFER_ERROR_NA 0xF7 // SPI not available due to external owner +#define MCP2210_SPI_TRANSFER_ERROR_IP 0xF8 // SPI not available due to transfer in progress + +struct gpio_pin { + uint8_t pin[9]; +}; + +struct mcp_settings { + struct gpio_pin designation; + struct gpio_pin value; + struct gpio_pin direction; + unsigned int bitrate, icsv, acsv, cstdd, ldbtcsd, sdbd, bpst, spimode; +}; + +bool mcp2210_send_recv(struct cgpu_info *cgpu, char *buf, enum usb_cmds cmd); +bool mcp2210_get_gpio_settings(struct cgpu_info *cgpu, struct mcp_settings *mcp); +bool mcp2210_set_gpio_settings(struct cgpu_info *cgpu, struct mcp_settings *mcp); +bool mcp2210_get_gpio_pindes(struct cgpu_info *cgpu, struct gpio_pin *gp); +bool mcp2210_get_gpio_pinvals(struct cgpu_info *cgpu, struct gpio_pin *gp); +bool mcp2210_get_gpio_pindirs(struct cgpu_info *cgpu, struct gpio_pin *gp); +bool mcp2210_get_gpio_pin(struct cgpu_info *cgpu, int pin, int *des); +bool mcp2210_get_gpio_pinval(struct cgpu_info *cgpu, int pin, int *val); +bool mcp2210_get_gpio_pindir(struct cgpu_info *cgpu, int pin, int *dir); +bool mcp2210_spi_cancel(struct cgpu_info *cgpu); +bool +mcp2210_get_spi_transfer_settings(struct cgpu_info *cgpu, unsigned int *bitrate, unsigned int *icsv, + unsigned int *acsv, unsigned int *cstdd, unsigned int *ldbtcsd, + unsigned int *sdbd, unsigned int *bpst, unsigned int *spimode); +bool +mcp2210_set_spi_transfer_settings(struct cgpu_info *cgpu, unsigned int bitrate, unsigned int icsv, + unsigned int acsv, unsigned int cstdd, unsigned int ldbtcsd, + unsigned int sdbd, unsigned int bpst, unsigned int spimode); +bool mcp2210_spi_transfer(struct cgpu_info *cgpu, struct mcp_settings *mcp, + char *data, unsigned int *length); + +#endif /* MCP2210_H */ diff --git a/miner.h b/miner.h new file mode 100644 index 0000000..04e1afe --- /dev/null +++ b/miner.h @@ -0,0 +1,1670 @@ +#ifndef __MINER_H__ +#define __MINER_H__ + +#include "config.h" + +#include +#include +#include +#include +#include +#ifdef HAVE_LIBCURL +#include +#else +typedef char CURL; +extern char *curly; +#define curl_easy_init(curl) (curly) +#define curl_easy_cleanup(curl) {} +#define curl_global_cleanup() {} +#define CURL_GLOBAL_ALL 0 +#define curl_global_init(X) (0) +#endif +#include + +#include "elist.h" +#include "uthash.h" +#include "logging.h" +#include "util.h" +#include +#ifndef WIN32 +# include +# include +#endif + +#ifdef USE_USBUTILS +#include +#endif + +#ifdef STDC_HEADERS +# include +# include +#else +# ifdef HAVE_STDLIB_H +# include +# endif +#endif +#ifdef HAVE_ALLOCA_H +# include +#elif defined __GNUC__ +# ifndef WIN32 +# define alloca __builtin_alloca +# else +# include +# endif +#elif defined _AIX +# define alloca __alloca +#elif defined _MSC_VER +# include +# define alloca _alloca +#else +# ifndef HAVE_ALLOCA +# ifdef __cplusplus +extern "C" +# endif +void *alloca (size_t); +# endif +#endif + +#ifdef __MINGW32__ +#include +#include +static inline int fsync (int fd) +{ + return (FlushFileBuffers ((HANDLE) _get_osfhandle (fd))) ? 0 : -1; +} + +#ifndef EWOULDBLOCK +# define EWOULDBLOCK EAGAIN +#endif + +#ifndef MSG_DONTWAIT +# define MSG_DONTWAIT 0x1000000 +#endif +#endif /* __MINGW32__ */ + +#if defined (__linux) + #ifndef LINUX + #define LINUX + #endif +#endif + +#ifdef WIN32 + #ifndef timersub + #define timersub(a, b, result) \ + do { \ + (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \ + (result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \ + if ((result)->tv_usec < 0) { \ + --(result)->tv_sec; \ + (result)->tv_usec += 1000000; \ + } \ + } while (0) + #endif + #ifndef timeradd + # define timeradd(a, b, result) \ + do { \ + (result)->tv_sec = (a)->tv_sec + (b)->tv_sec; \ + (result)->tv_usec = (a)->tv_usec + (b)->tv_usec; \ + if ((result)->tv_usec >= 1000000) \ + { \ + ++(result)->tv_sec; \ + (result)->tv_usec -= 1000000; \ + } \ + } while (0) + #endif +#endif + + +#ifdef USE_USBUTILS + #include +#endif + +#ifdef USE_USBUTILS + #include "usbutils.h" +#endif + +#if (!defined(WIN32) && ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3))) \ + || (defined(WIN32) && ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7))) +#ifndef bswap_16 + #define bswap_16(value) \ + ((((value) & 0xff) << 8) | ((value) >> 8)) + #define bswap_32 __builtin_bswap32 + #define bswap_64 __builtin_bswap64 +#endif +#else +#if HAVE_BYTESWAP_H +#include +#elif defined(USE_SYS_ENDIAN_H) +#include +#elif defined(__APPLE__) +#include +#define bswap_16 OSSwapInt16 +#define bswap_32 OSSwapInt32 +#define bswap_64 OSSwapInt64 +#else +#define bswap_16(value) \ + ((((value) & 0xff) << 8) | ((value) >> 8)) + +#define bswap_32(value) \ + (((uint32_t)bswap_16((uint16_t)((value) & 0xffff)) << 16) | \ + (uint32_t)bswap_16((uint16_t)((value) >> 16))) + +#define bswap_64(value) \ + (((uint64_t)bswap_32((uint32_t)((value) & 0xffffffff)) \ + << 32) | \ + (uint64_t)bswap_32((uint32_t)((value) >> 32))) +#endif +#endif /* !defined(__GLXBYTEORDER_H__) */ + +#ifndef bswap_8 +extern unsigned char bit_swap_table[256]; +#define bswap_8(x) (bit_swap_table[x]) +#endif + +/* This assumes htobe32 is a macro in endian.h, and if it doesn't exist, then + * htobe64 also won't exist */ +#ifndef htobe32 +# if __BYTE_ORDER == __LITTLE_ENDIAN +# define htole8(x) (x) +# define htole16(x) (x) +# define le16toh(x) (x) +# define htole32(x) (x) +# define htole64(x) (x) +# define le32toh(x) (x) +# define le64toh(x) (x) +# define be32toh(x) bswap_32(x) +# define be64toh(x) bswap_64(x) +# define htobe16(x) bswap_16(x) +# define htobe32(x) bswap_32(x) +# define htobe64(x) bswap_64(x) +# elif __BYTE_ORDER == __BIG_ENDIAN +# define htole8(x) bswap_8(x) +# define htole16(x) bswap_16(x) +# define le16toh(x) bswap_16(x) +# define htole32(x) bswap_32(x) +# define le32toh(x) bswap_32(x) +# define le64toh(x) bswap_64(x) +# define htole64(x) bswap_64(x) +# define be32toh(x) (x) +# define be64toh(x) (x) +# define htobe16(x) (x) +# define htobe32(x) (x) +# define htobe64(x) (x) +#else +#error UNKNOWN BYTE ORDER +#endif + +#else + +# if __BYTE_ORDER == __LITTLE_ENDIAN +# define htole8(x) (x) +# elif __BYTE_ORDER == __BIG_ENDIAN +# define htole8(x) bswap_8(x) +#else +#error UNKNOWN BYTE ORDER +#endif + +#endif + +#undef unlikely +#undef likely +#if defined(__GNUC__) && (__GNUC__ > 2) && defined(__OPTIMIZE__) +#define unlikely(expr) (__builtin_expect(!!(expr), 0)) +#define likely(expr) (__builtin_expect(!!(expr), 1)) +#else +#define unlikely(expr) (expr) +#define likely(expr) (expr) +#endif +#define __maybe_unused __attribute__((unused)) + +#define uninitialised_var(x) x = x + +#if defined(__i386__) +#define WANT_CRYPTOPP_ASM32 +#endif + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) +#endif + +/* No semtimedop on apple so ignore timeout till we implement one */ +#ifdef __APPLE__ +#define semtimedop(SEM, SOPS, VAL, TIMEOUT) semop(SEM, SOPS, VAL) +#endif + +#ifndef MIN +#define MIN(x, y) ((x) > (y) ? (y) : (x)) +#endif +#ifndef MAX +#define MAX(x, y) ((x) > (y) ? (x) : (y)) +#endif + +/* Put avalon last to make it the last device it tries to detect to prevent it + * trying to claim same chip but different devices. Adding a device here will + * update all macros in the code that use the *_PARSE_COMMANDS macros for each + * listed driver. */ +#define FPGA_PARSE_COMMANDS(DRIVER_ADD_COMMAND) \ + DRIVER_ADD_COMMAND(bitforce) \ + DRIVER_ADD_COMMAND(modminer) + +#define ASIC_PARSE_COMMANDS(DRIVER_ADD_COMMAND) \ + DRIVER_ADD_COMMAND(bitmain) \ + DRIVER_ADD_COMMAND(bitmain_c5) \ + DRIVER_ADD_COMMAND(bmsc) \ + DRIVER_ADD_COMMAND(avalon) \ + DRIVER_ADD_COMMAND(avalon2) \ + DRIVER_ADD_COMMAND(avalon4) \ + DRIVER_ADD_COMMAND(bflsc) \ + DRIVER_ADD_COMMAND(bitfury) \ + DRIVER_ADD_COMMAND(blockerupter) \ + DRIVER_ADD_COMMAND(cointerra) \ + DRIVER_ADD_COMMAND(hashfast) \ + DRIVER_ADD_COMMAND(hashratio) \ + DRIVER_ADD_COMMAND(icarus) \ + DRIVER_ADD_COMMAND(klondike) \ + DRIVER_ADD_COMMAND(knc) \ + DRIVER_ADD_COMMAND(bitmineA1) \ + DRIVER_ADD_COMMAND(drillbit) \ + DRIVER_ADD_COMMAND(bab) \ + DRIVER_ADD_COMMAND(minion) \ + DRIVER_ADD_COMMAND(sp10) \ + DRIVER_ADD_COMMAND(sp30) + +#define DRIVER_PARSE_COMMANDS(DRIVER_ADD_COMMAND) \ + FPGA_PARSE_COMMANDS(DRIVER_ADD_COMMAND) \ + ASIC_PARSE_COMMANDS(DRIVER_ADD_COMMAND) + +#define DRIVER_ENUM(X) DRIVER_##X, +#define DRIVER_PROTOTYPE(X) struct device_drv X##_drv; + +/* Create drv_driver enum from DRIVER_PARSE_COMMANDS macro */ +enum drv_driver { + DRIVER_PARSE_COMMANDS(DRIVER_ENUM) + DRIVER_MAX +}; + +/* Use DRIVER_PARSE_COMMANDS to generate extern device_drv prototypes */ +DRIVER_PARSE_COMMANDS(DRIVER_PROTOTYPE) + +enum alive { + LIFE_WELL, + LIFE_SICK, + LIFE_DEAD, + LIFE_NOSTART, + LIFE_INIT, +}; + + +enum pool_strategy { + POOL_FAILOVER, + POOL_ROUNDROBIN, + POOL_ROTATE, + POOL_LOADBALANCE, + POOL_BALANCE, +}; + +#define TOP_STRATEGY (POOL_BALANCE) + +struct strategies { + const char *s; +}; + +struct cgpu_info; + +extern void blank_get_statline_before(char *buf, size_t bufsiz, struct cgpu_info __maybe_unused *cgpu); + +struct api_data; +struct thr_info; +struct work; + +struct device_drv { + enum drv_driver drv_id; + + char *dname; + char *name; + + // DRV-global functions + void (*drv_detect)(bool); + + // Device-specific functions + void (*reinit_device)(struct cgpu_info *); + void (*get_statline_before)(char *, size_t, struct cgpu_info *); + void (*get_statline)(char *, size_t, struct cgpu_info *); + struct api_data *(*get_api_stats)(struct cgpu_info *); + bool (*get_stats)(struct cgpu_info *); + void (*identify_device)(struct cgpu_info *); // e.g. to flash a led + char *(*set_device)(struct cgpu_info *, char *option, char *setting, char *replybuf); + + // Thread-specific functions + bool (*thread_prepare)(struct thr_info *); + uint64_t (*can_limit_work)(struct thr_info *); + bool (*thread_init)(struct thr_info *); + bool (*prepare_work)(struct thr_info *, struct work *); + + /* Which hash work loop this driver uses. */ + void (*hash_work)(struct thr_info *); + /* Two variants depending on whether the device divides work up into + * small pieces or works with whole work items and may or may not have + * a queue of its own. */ + int64_t (*scanhash)(struct thr_info *, struct work *, int64_t); + int64_t (*scanwork)(struct thr_info *); + + /* Used to extract work from the hash table of queued work and tell + * the main loop that it should not add any further work to the table. + */ + bool (*queue_full)(struct cgpu_info *); + /* Tell the driver of a block change */ + void (*flush_work)(struct cgpu_info *); + /* Tell the driver of an updated work template for eg. stratum */ + void (*update_work)(struct cgpu_info *); + + void (*hw_error)(struct thr_info *); + void (*thread_shutdown)(struct thr_info *); + void (*thread_enable)(struct thr_info *); + + /* What should be zeroed in this device when global zero stats is sent */ + void (*zero_stats)(struct cgpu_info *); + + // Does it need to be free()d? + bool copy; + + /* Highest target diff the device supports */ + double max_diff; + + /* Lowest diff the controller can safely run at */ + double min_diff; +}; + +extern struct device_drv *copy_drv(struct device_drv*); + +enum dev_enable { + DEV_ENABLED, + DEV_DISABLED, + DEV_RECOVER, +}; + +enum dev_reason { + REASON_THREAD_FAIL_INIT, + REASON_THREAD_ZERO_HASH, + REASON_THREAD_FAIL_QUEUE, + REASON_DEV_SICK_IDLE_60, + REASON_DEV_DEAD_IDLE_600, + REASON_DEV_NOSTART, + REASON_DEV_OVER_HEAT, + REASON_DEV_THERMAL_CUTOFF, + REASON_DEV_COMMS_ERROR, + REASON_DEV_THROTTLE, +}; + +#define REASON_NONE "None" +#define REASON_THREAD_FAIL_INIT_STR "Thread failed to init" +#define REASON_THREAD_ZERO_HASH_STR "Thread got zero hashes" +#define REASON_THREAD_FAIL_QUEUE_STR "Thread failed to queue work" +#define REASON_DEV_SICK_IDLE_60_STR "Device idle for 60s" +#define REASON_DEV_DEAD_IDLE_600_STR "Device dead - idle for 600s" +#define REASON_DEV_NOSTART_STR "Device failed to start" +#define REASON_DEV_OVER_HEAT_STR "Device over heated" +#define REASON_DEV_THERMAL_CUTOFF_STR "Device reached thermal cutoff" +#define REASON_DEV_COMMS_ERROR_STR "Device comms error" +#define REASON_DEV_THROTTLE_STR "Device throttle" +#define REASON_UNKNOWN_STR "Unknown reason - code bug" + +#define MIN_SEC_UNSET 99999999 + +struct cgminer_stats { + uint32_t getwork_calls; + struct timeval getwork_wait; + struct timeval getwork_wait_max; + struct timeval getwork_wait_min; +}; + +// Just the actual network getworks to the pool +struct cgminer_pool_stats { + uint32_t getwork_calls; + uint32_t getwork_attempts; + struct timeval getwork_wait; + struct timeval getwork_wait_max; + struct timeval getwork_wait_min; + double getwork_wait_rolling; + bool hadrolltime; + bool canroll; + bool hadexpire; + uint32_t rolltime; + double min_diff; + double max_diff; + double last_diff; + uint32_t min_diff_count; + uint32_t max_diff_count; + uint64_t times_sent; + uint64_t bytes_sent; + uint64_t net_bytes_sent; + uint64_t times_received; + uint64_t bytes_received; + uint64_t net_bytes_received; +}; + +struct cgpu_info { + int cgminer_id; + struct device_drv *drv; + int device_id; + char *name; + char *device_path; + void *device_data; + void *dup_data; + char *unique_id; +#ifdef USE_USBUTILS + struct cg_usb_device *usbdev; + struct cg_usb_info usbinfo; + bool blacklisted; + bool nozlp; // Device prefers no zero length packet +#endif +#if defined(USE_AVALON) || defined(USE_AVALON2) + struct work **works; + int work_array; + int queued; + int results; +#endif +#ifdef USE_BITMAIN + int device_fd; + struct work **works; + int work_array; + int queued; + int results; +#endif +#ifdef USE_MODMINER + char fpgaid; + unsigned char clock; + pthread_mutex_t *modminer_mutex; +#endif +#ifdef USE_BITFORCE + struct timeval work_start_tv; + unsigned int wait_ms; + unsigned int sleep_ms; + double avg_wait_f; + unsigned int avg_wait_d; + uint32_t nonces; + bool nonce_range; + bool polling; + bool flash_led; +#endif /* USE_BITFORCE */ +#if defined(USE_BITFORCE) || defined(USE_BFLSC) + pthread_mutex_t device_mutex; +#endif /* USE_BITFORCE || USE_BFLSC */ + enum dev_enable deven; + int accepted; + int rejected; + int hw_errors; + double rolling; + double rolling1; + double rolling5; + double rolling15; + double total_mhashes; + double utility; + enum alive status; + char init[40]; + struct timeval last_message_tv; + + int threads; + struct thr_info **thr; + + int64_t max_hashes; + + const char *kname; + + bool new_work; + + double temp; + int cutofftemp; + + int64_t diff1; + double diff_accepted; + double diff_rejected; + int last_share_pool; + time_t last_share_pool_time; + double last_share_diff; + time_t last_device_valid_work; + uint32_t last_nonce; + + time_t device_last_well; + time_t device_last_not_well; + enum dev_reason device_not_well_reason; + int thread_fail_init_count; + int thread_zero_hash_count; + int thread_fail_queue_count; + int dev_sick_idle_60_count; + int dev_dead_idle_600_count; + int dev_nostart_count; + int dev_over_heat_count; // It's a warning but worth knowing + int dev_thermal_cutoff_count; + int dev_comms_error_count; + int dev_throttle_count; + + struct cgminer_stats cgminer_stats; + + pthread_rwlock_t qlock; + struct work *queued_work; + struct work *unqueued_work; + unsigned int queued_count; + + bool shutdown; + + struct timeval dev_start_tv; + + /* For benchmarking only */ + int hidiff; + int lodiff; + int direction; +}; + +extern bool add_cgpu(struct cgpu_info*); + +struct thread_q { + struct list_head q; + + bool frozen; + + pthread_mutex_t mutex; + pthread_cond_t cond; +}; + +struct thr_info { + int id; + int device_thread; + bool primary_thread; + + pthread_t pth; + cgsem_t sem; + struct thread_q *q; + struct cgpu_info *cgpu; + void *cgpu_data; + struct timeval last; + struct timeval sick; + + bool pause; + bool getwork; + + bool work_restart; + bool work_update; +}; + +struct string_elist { + char *string; + bool free_me; + + struct list_head list; +}; + +static inline void string_elist_add(const char *s, struct list_head *head) +{ + struct string_elist *n; + + n = calloc(1, sizeof(*n)); + n->string = strdup(s); + n->free_me = true; + list_add_tail(&n->list, head); +} + +static inline void string_elist_del(struct string_elist *item) +{ + if (item->free_me) + free(item->string); + list_del(&item->list); +} + + +static inline uint32_t swab32(uint32_t v) +{ + return bswap_32(v); +} + +static inline void swap256(void *dest_p, const void *src_p) +{ + uint32_t *dest = dest_p; + const uint32_t *src = src_p; + + dest[0] = src[7]; + dest[1] = src[6]; + dest[2] = src[5]; + dest[3] = src[4]; + dest[4] = src[3]; + dest[5] = src[2]; + dest[6] = src[1]; + dest[7] = src[0]; +} + +static inline void swab256(void *dest_p, const void *src_p) +{ + uint32_t *dest = dest_p; + const uint32_t *src = src_p; + + dest[0] = swab32(src[7]); + dest[1] = swab32(src[6]); + dest[2] = swab32(src[5]); + dest[3] = swab32(src[4]); + dest[4] = swab32(src[3]); + dest[5] = swab32(src[2]); + dest[6] = swab32(src[1]); + dest[7] = swab32(src[0]); +} + +static inline void flip12(void *dest_p, const void *src_p) +{ + uint32_t *dest = dest_p; + const uint32_t *src = src_p; + int i; + + for (i = 0; i < 3; i++) + dest[i] = swab32(src[i]); +} + +static inline void flip32(void *dest_p, const void *src_p) +{ + uint32_t *dest = dest_p; + const uint32_t *src = src_p; + int i; + + for (i = 0; i < 8; i++) + dest[i] = swab32(src[i]); +} + +static inline void flip64(void *dest_p, const void *src_p) +{ + uint32_t *dest = dest_p; + const uint32_t *src = src_p; + int i; + + for (i = 0; i < 16; i++) + dest[i] = swab32(src[i]); +} + +static inline void flip80(void *dest_p, const void *src_p) +{ + uint32_t *dest = dest_p; + const uint32_t *src = src_p; + int i; + + for (i = 0; i < 20; i++) + dest[i] = swab32(src[i]); +} + +static inline void flip128(void *dest_p, const void *src_p) +{ + uint32_t *dest = dest_p; + const uint32_t *src = src_p; + int i; + + for (i = 0; i < 32; i++) + dest[i] = swab32(src[i]); +} + +/* For flipping to the correct endianness if necessary */ +#if defined(__BIG_ENDIAN__) || defined(MIPSEB) +static inline void endian_flip32(void *dest_p, const void *src_p) +{ + flip32(dest_p, src_p); +} + +static inline void endian_flip128(void *dest_p, const void *src_p) +{ + flip128(dest_p, src_p); +} +#else +static inline void +endian_flip32(void __maybe_unused *dest_p, const void __maybe_unused *src_p) +{ +} + +static inline void +endian_flip128(void __maybe_unused *dest_p, const void __maybe_unused *src_p) +{ +} +#endif + +extern double cgpu_runtime(struct cgpu_info *cgpu); +extern double tsince_restart(void); +extern double tsince_update(void); +extern void __quit(int status, bool clean); +extern void _quit(int status); + +/* + * Set this to non-zero to enable lock tracking + * Use the API lockstats command to see the locking status on stderr + * i.e. in your log file if you 2> log.log - but not on the screen + * API lockstats is privilidged but will always exist and will return + * success if LOCK_TRACKING is enabled and warning if disabled + * In production code, this should never be enabled since it will slow down all locking + * So, e.g. use it to track down a deadlock - after a reproducable deadlock occurs + * ... Of course if the API code itself deadlocks, it wont help :) + */ +#define LOCK_TRACKING 0 + +#if LOCK_TRACKING +enum cglock_typ { + CGLOCK_MUTEX, + CGLOCK_RW, + CGLOCK_UNKNOWN +}; + +extern uint64_t api_getlock(void *lock, const char *file, const char *func, const int line); +extern void api_gotlock(uint64_t id, void *lock, const char *file, const char *func, const int line); +extern uint64_t api_trylock(void *lock, const char *file, const char *func, const int line); +extern void api_didlock(uint64_t id, int ret, void *lock, const char *file, const char *func, const int line); +extern void api_gunlock(void *lock, const char *file, const char *func, const int line); +extern void api_initlock(void *lock, enum cglock_typ typ, const char *file, const char *func, const int line); + +#define GETLOCK(_lock, _file, _func, _line) uint64_t _id1 = api_getlock((void *)(_lock), _file, _func, _line) +#define GOTLOCK(_lock, _file, _func, _line) api_gotlock(_id1, (void *)(_lock), _file, _func, _line) +#define TRYLOCK(_lock, _file, _func, _line) uint64_t _id2 = api_trylock((void *)(_lock), _file, _func, _line) +#define DIDLOCK(_ret, _lock, _file, _func, _line) api_didlock(_id2, _ret, (void *)(_lock), _file, _func, _line) +#define GUNLOCK(_lock, _file, _func, _line) api_gunlock((void *)(_lock), _file, _func, _line) +#define INITLOCK(_lock, _typ, _file, _func, _line) api_initlock((void *)(_lock), _typ, _file, _func, _line) +#else +#define GETLOCK(_lock, _file, _func, _line) +#define GOTLOCK(_lock, _file, _func, _line) +#define TRYLOCK(_lock, _file, _func, _line) +#define DIDLOCK(_ret, _lock, _file, _func, _line) +#define GUNLOCK(_lock, _file, _func, _line) +#define INITLOCK(_typ, _lock, _file, _func, _line) +#endif + +#define mutex_lock(_lock) _mutex_lock(_lock, __FILE__, __func__, __LINE__) +#define mutex_unlock_noyield(_lock) _mutex_unlock_noyield(_lock, __FILE__, __func__, __LINE__) +#define mutex_unlock(_lock) _mutex_unlock(_lock, __FILE__, __func__, __LINE__) +#define mutex_trylock(_lock) _mutex_trylock(_lock, __FILE__, __func__, __LINE__) +#define wr_lock(_lock) _wr_lock(_lock, __FILE__, __func__, __LINE__) +#define wr_trylock(_lock) _wr_trylock(_lock, __FILE__, __func__, __LINE__) +#define rd_lock(_lock) _rd_lock(_lock, __FILE__, __func__, __LINE__) +#define rw_unlock(_lock) _rw_unlock(_lock, __FILE__, __func__, __LINE__) +#define rd_unlock_noyield(_lock) _rd_unlock_noyield(_lock, __FILE__, __func__, __LINE__) +#define wr_unlock_noyield(_lock) _wr_unlock_noyield(_lock, __FILE__, __func__, __LINE__) +#define rd_unlock(_lock) _rd_unlock(_lock, __FILE__, __func__, __LINE__) +#define wr_unlock(_lock) _wr_unlock(_lock, __FILE__, __func__, __LINE__) +#define mutex_init(_lock) _mutex_init(_lock, __FILE__, __func__, __LINE__) +#define rwlock_init(_lock) _rwlock_init(_lock, __FILE__, __func__, __LINE__) +#define cglock_init(_lock) _cglock_init(_lock, __FILE__, __func__, __LINE__) +#define cg_rlock(_lock) _cg_rlock(_lock, __FILE__, __func__, __LINE__) +#define cg_ilock(_lock) _cg_ilock(_lock, __FILE__, __func__, __LINE__) +#define cg_uilock(_lock) _cg_uilock(_lock, __FILE__, __func__, __LINE__) +#define cg_ulock(_lock) _cg_ulock(_lock, __FILE__, __func__, __LINE__) +#define cg_wlock(_lock) _cg_wlock(_lock, __FILE__, __func__, __LINE__) +#define cg_dwlock(_lock) _cg_dwlock(_lock, __FILE__, __func__, __LINE__) +#define cg_dwilock(_lock) _cg_dwilock(_lock, __FILE__, __func__, __LINE__) +#define cg_dlock(_lock) _cg_dlock(_lock, __FILE__, __func__, __LINE__) +#define cg_runlock(_lock) _cg_runlock(_lock, __FILE__, __func__, __LINE__) +#define cg_ruwlock(_lock) _cg_ruwlock(_lock, __FILE__, __func__, __LINE__) +#define cg_wunlock(_lock) _cg_wunlock(_lock, __FILE__, __func__, __LINE__) + +static inline void _mutex_lock(pthread_mutex_t *lock, const char *file, const char *func, const int line) +{ + GETLOCK(lock, file, func, line); + if (unlikely(pthread_mutex_lock(lock))) + quitfrom(1, file, func, line, "WTF MUTEX ERROR ON LOCK! errno=%d", errno); + GOTLOCK(lock, file, func, line); +} + +static inline void _mutex_unlock_noyield(pthread_mutex_t *lock, const char *file, const char *func, const int line) +{ + if (unlikely(pthread_mutex_unlock(lock))) + quitfrom(1, file, func, line, "WTF MUTEX ERROR ON UNLOCK! errno=%d", errno); + GUNLOCK(lock, file, func, line); +} + +static inline void _mutex_unlock(pthread_mutex_t *lock, const char *file, const char *func, const int line) +{ + _mutex_unlock_noyield(lock, file, func, line); + selective_yield(); +} + +static inline int _mutex_trylock(pthread_mutex_t *lock, __maybe_unused const char *file, __maybe_unused const char *func, __maybe_unused const int line) +{ + TRYLOCK(lock, file, func, line); + int ret = pthread_mutex_trylock(lock); + DIDLOCK(ret, lock, file, func, line); + return ret; +} + +static inline void _wr_lock(pthread_rwlock_t *lock, const char *file, const char *func, const int line) +{ + GETLOCK(lock, file, func, line); + if (unlikely(pthread_rwlock_wrlock(lock))) + quitfrom(1, file, func, line, "WTF WRLOCK ERROR ON LOCK! errno=%d", errno); + GOTLOCK(lock, file, func, line); +} + +static inline int _wr_trylock(pthread_rwlock_t *lock, __maybe_unused const char *file, __maybe_unused const char *func, __maybe_unused const int line) +{ + TRYLOCK(lock, file, func, line); + int ret = pthread_rwlock_trywrlock(lock); + DIDLOCK(ret, lock, file, func, line); + return ret; +} + +static inline void _rd_lock(pthread_rwlock_t *lock, const char *file, const char *func, const int line) +{ + GETLOCK(lock, file, func, line); + if (unlikely(pthread_rwlock_rdlock(lock))) + quitfrom(1, file, func, line, "WTF RDLOCK ERROR ON LOCK! errno=%d", errno); + GOTLOCK(lock, file, func, line); +} + +static inline void _rw_unlock(pthread_rwlock_t *lock, const char *file, const char *func, const int line) +{ + if (unlikely(pthread_rwlock_unlock(lock))) + quitfrom(1, file, func, line, "WTF RWLOCK ERROR ON UNLOCK! errno=%d", errno); + GUNLOCK(lock, file, func, line); +} + +static inline void _rd_unlock_noyield(pthread_rwlock_t *lock, const char *file, const char *func, const int line) +{ + _rw_unlock(lock, file, func, line); +} + +static inline void _wr_unlock_noyield(pthread_rwlock_t *lock, const char *file, const char *func, const int line) +{ + _rw_unlock(lock, file, func, line); +} + +static inline void _rd_unlock(pthread_rwlock_t *lock, const char *file, const char *func, const int line) +{ + _rw_unlock(lock, file, func, line); + selective_yield(); +} + +static inline void _wr_unlock(pthread_rwlock_t *lock, const char *file, const char *func, const int line) +{ + _rw_unlock(lock, file, func, line); + selective_yield(); +} + +static inline void _mutex_init(pthread_mutex_t *lock, const char *file, const char *func, const int line) +{ + if (unlikely(pthread_mutex_init(lock, NULL))) + quitfrom(1, file, func, line, "Failed to pthread_mutex_init errno=%d", errno); + INITLOCK(lock, CGLOCK_MUTEX, file, func, line); +} + +static inline void mutex_destroy(pthread_mutex_t *lock) +{ + /* Ignore return code. This only invalidates the mutex on linux but + * releases resources on windows. */ + pthread_mutex_destroy(lock); +} + +static inline void _rwlock_init(pthread_rwlock_t *lock, const char *file, const char *func, const int line) +{ + if (unlikely(pthread_rwlock_init(lock, NULL))) + quitfrom(1, file, func, line, "Failed to pthread_rwlock_init errno=%d", errno); + INITLOCK(lock, CGLOCK_RW, file, func, line); +} + +static inline void rwlock_destroy(pthread_rwlock_t *lock) +{ + pthread_rwlock_destroy(lock); +} + +static inline void _cglock_init(cglock_t *lock, const char *file, const char *func, const int line) +{ + _mutex_init(&lock->mutex, file, func, line); + _rwlock_init(&lock->rwlock, file, func, line); +} + +static inline void cglock_destroy(cglock_t *lock) +{ + rwlock_destroy(&lock->rwlock); + mutex_destroy(&lock->mutex); +} + +/* Read lock variant of cglock. Cannot be promoted. */ +static inline void _cg_rlock(cglock_t *lock, const char *file, const char *func, const int line) +{ + _mutex_lock(&lock->mutex, file, func, line); + _rd_lock(&lock->rwlock, file, func, line); + _mutex_unlock_noyield(&lock->mutex, file, func, line); +} + +/* Intermediate variant of cglock - behaves as a read lock but can be promoted + * to a write lock or demoted to read lock. */ +static inline void _cg_ilock(cglock_t *lock, const char *file, const char *func, const int line) +{ + _mutex_lock(&lock->mutex, file, func, line); +} + +/* Unlock intermediate variant without changing to read or write version */ +static inline void _cg_uilock(cglock_t *lock, const char *file, const char *func, const int line) +{ + _mutex_unlock(&lock->mutex, file, func, line); +} + +/* Upgrade intermediate variant to a write lock */ +static inline void _cg_ulock(cglock_t *lock, const char *file, const char *func, const int line) +{ + _wr_lock(&lock->rwlock, file, func, line); +} + +/* Write lock variant of cglock */ +static inline void _cg_wlock(cglock_t *lock, const char *file, const char *func, const int line) +{ + _mutex_lock(&lock->mutex, file, func, line); + _wr_lock(&lock->rwlock, file, func, line); +} + +/* Downgrade write variant to a read lock */ +static inline void _cg_dwlock(cglock_t *lock, const char *file, const char *func, const int line) +{ + _wr_unlock_noyield(&lock->rwlock, file, func, line); + _rd_lock(&lock->rwlock, file, func, line); + _mutex_unlock_noyield(&lock->mutex, file, func, line); +} + +/* Demote a write variant to an intermediate variant */ +static inline void _cg_dwilock(cglock_t *lock, const char *file, const char *func, const int line) +{ + _wr_unlock(&lock->rwlock, file, func, line); +} + +/* Downgrade intermediate variant to a read lock */ +static inline void _cg_dlock(cglock_t *lock, const char *file, const char *func, const int line) +{ + _rd_lock(&lock->rwlock, file, func, line); + _mutex_unlock_noyield(&lock->mutex, file, func, line); +} + +static inline void _cg_runlock(cglock_t *lock, const char *file, const char *func, const int line) +{ + _rd_unlock(&lock->rwlock, file, func, line); +} + +/* This drops the read lock and grabs a write lock. It does NOT protect data + * between the two locks! */ +static inline void _cg_ruwlock(cglock_t *lock, const char *file, const char *func, const int line) +{ + _rd_unlock_noyield(&lock->rwlock, file, func, line); + _cg_wlock(lock, file, func, line); +} + +static inline void _cg_wunlock(cglock_t *lock, const char *file, const char *func, const int line) +{ + _wr_unlock_noyield(&lock->rwlock, file, func, line); + _mutex_unlock(&lock->mutex, file, func, line); +} + +struct pool; + +#define API_LISTEN_ADDR "0.0.0.0" +#define API_MCAST_CODE "FTW" +#define API_MCAST_ADDR "224.0.0.75" + +extern bool g_logfile_enable; +extern char g_logfile_path[256]; +extern char g_logfile_openflag[32]; +extern FILE * g_logwork_file; +extern FILE * g_logwork_files[65]; +extern FILE * g_logwork_diffs[65]; +extern int g_logwork_asicnum; + +extern bool opt_work_update; +extern bool opt_protocol; +extern bool have_longpoll; +extern char *opt_kernel_path; +extern char *opt_socks_proxy; +extern int opt_suggest_diff; +extern int opt_multi_version; +extern char *cgminer_path; +extern bool opt_fail_only; +extern bool opt_lowmem; +extern bool opt_autofan; +extern bool opt_autoengine; +extern bool use_curses; +extern char *opt_logwork_path; +extern char *opt_logwork_asicnum; +extern bool opt_logwork_diff; +extern char *opt_api_allow; +extern bool opt_api_mcast; +extern char *opt_api_mcast_addr; +extern char *opt_api_mcast_code; +extern char *opt_api_mcast_des; +extern int opt_api_mcast_port; +extern char *opt_api_groups; +extern char *opt_api_description; +extern int opt_api_port; +extern char *opt_api_host; +extern bool opt_api_listen; +extern bool opt_api_network; +extern bool opt_delaynet; +extern time_t last_getwork; +extern bool opt_restart; +#ifdef USE_ICARUS +extern char *opt_icarus_options; +extern char *opt_icarus_timing; +extern float opt_anu_freq; +extern float opt_au3_freq; +extern int opt_au3_volt; +extern float opt_rock_freq; +#endif +extern bool opt_worktime; +#ifdef USE_AVALON +extern char *opt_avalon_options; +extern char *opt_bitburner_fury_options; +#endif +#ifdef USE_KLONDIKE +extern char *opt_klondike_options; +#endif +#ifdef USE_DRILLBIT +extern char *opt_drillbit_options; +extern char *opt_drillbit_auto; +#endif +#ifdef USE_BAB +extern char *opt_bab_options; +#endif +#ifdef USE_BITMINE_A1 +extern char *opt_bitmine_a1_options; +#endif +#ifdef USE_BITMAIN +extern char *opt_bitmain_options; +extern bool opt_bitmain_hwerror; +extern bool opt_bitmain_checkall; +extern char *opt_bitmain_freq; +extern char *opt_bitmain_voltage; +extern bool opt_bitmain_checkn2diff; +extern bool opt_bitmain_nobeeper; +extern bool opt_bitmain_notempoverctrl; +extern bool opt_bitmain_homemode; +#endif +#ifdef USE_BMSC +extern char *opt_bmsc_options; +extern char *opt_bmsc_timing; +extern bool opt_bmsc_gray; +extern char *opt_bmsc_bandops; +extern char *opt_bmsc_voltage; +extern bool opt_bmsc_bootstart; +extern char *opt_bmsc_freq; +extern char *opt_bmsc_rdreg; +extern bool opt_bmsc_rdworktest; +#endif +#ifdef USE_MINION +extern int opt_minion_chipreport; +extern char *opt_minion_cores; +extern bool opt_minion_extra; +extern char *opt_minion_freq; +extern int opt_minion_freqchange; +extern int opt_minion_freqpercent; +extern bool opt_minion_idlecount; +extern int opt_minion_ledcount; +extern int opt_minion_ledlimit; +extern bool opt_minion_noautofreq; +extern bool opt_minion_overheat; +extern int opt_minion_spidelay; +extern char *opt_minion_spireset; +extern int opt_minion_spisleep; +extern int opt_minion_spiusec; +extern char *opt_minion_temp; +#endif +#ifdef USE_USBUTILS +extern char *opt_usb_select; +extern int opt_usbdump; +extern bool opt_usb_list_all; +extern cgsem_t usb_resource_sem; +#endif +#ifdef USE_BITFORCE +extern bool opt_bfl_noncerange; +#endif +extern int swork_id; + +#if LOCK_TRACKING +extern pthread_mutex_t lockstat_lock; +#endif + +extern pthread_rwlock_t netacc_lock; + +extern const uint32_t sha256_init_state[]; +#ifdef HAVE_LIBCURL +extern json_t *json_web_config(const char *url); +extern json_t *json_rpc_call(CURL *curl, const char *url, const char *userpass, + const char *rpc_req, bool, bool, int *, + struct pool *pool, bool); +#endif +extern const char *proxytype(proxytypes_t proxytype); +extern char *get_proxy(char *url, struct pool *pool); +extern void __bin2hex(char *s, const unsigned char *p, size_t len); +extern char *bin2hex(const unsigned char *p, size_t len); +extern bool hex2bin(unsigned char *p, const char *hexstr, size_t len); + +typedef bool (*sha256_func)(struct thr_info*, const unsigned char *pmidstate, + unsigned char *pdata, + unsigned char *phash1, unsigned char *phash, + const unsigned char *ptarget, + uint32_t max_nonce, + uint32_t *last_nonce, + uint32_t nonce); + +extern bool fulltest(const unsigned char *hash, const unsigned char *target); + +extern int opt_queue; +extern int opt_scantime; +extern int opt_expiry; + +extern cglock_t control_lock; +extern pthread_mutex_t hash_lock; +extern pthread_mutex_t console_lock; +extern cglock_t ch_lock; +extern pthread_rwlock_t mining_thr_lock; +extern pthread_rwlock_t devices_lock; + +extern pthread_mutex_t restart_lock; +extern pthread_cond_t restart_cond; + +extern void clear_stratum_shares(struct pool *pool); +extern void clear_pool_work(struct pool *pool); +extern void set_target(unsigned char *dest_target, double diff); +#if defined (USE_AVALON2) || defined (USE_AVALON4) || defined (USE_HASHRATIO) +bool submit_nonce2_nonce(struct thr_info *thr, struct pool *pool, struct pool *real_pool, + uint32_t nonce2, uint32_t nonce, uint32_t ntime); +#endif +extern int restart_wait(struct thr_info *thr, unsigned int mstime); + +extern void kill_work(void); + +extern void reinit_device(struct cgpu_info *cgpu); + +extern void api(int thr_id); + +extern struct pool *current_pool(void); +extern int enabled_pools; +extern void get_intrange(char *arg, int *val1, int *val2); +extern bool detect_stratum(struct pool *pool, char *url); +extern void print_summary(void); +extern void adjust_quota_gcd(void); +extern struct pool *add_pool(void); +extern bool add_pool_details(struct pool *pool, bool live, char *url, char *user, char *pass); + +#define MAX_DEVICES 4096 + +extern char g_miner_version[256]; +extern char g_miner_compiletime[256]; +extern char g_miner_type[256]; +extern bool hotplug_mode; +extern int hotplug_time; +extern struct list_head scan_devices; +extern int nDevs; +extern int num_processors; +extern int hw_errors; +extern bool use_syslog; +extern bool opt_quiet; +extern struct thr_info *control_thr; +extern struct thr_info **mining_thr; +extern double total_secs; +extern int mining_threads; +extern int total_devices; +extern int zombie_devs; +extern struct cgpu_info **devices; +extern int total_pools; +extern struct pool **pools; +extern struct strategies strategies[]; +extern enum pool_strategy pool_strategy; +extern int opt_rotate_period; +extern double rolling1, rolling5, rolling15; +extern double total_rolling; +extern double total_mhashes_done; +extern double g_displayed_rolling; +extern char displayed_hash_rate[16]; +extern unsigned int new_blocks; +extern unsigned int found_blocks; +extern int g_max_fan, g_max_temp; +extern int64_t total_accepted, total_rejected, total_diff1; +extern int64_t total_getworks, total_stale, total_discarded; +extern double total_diff_accepted, total_diff_rejected, total_diff_stale; +extern unsigned int local_work; +extern unsigned int total_go, total_ro; +extern const int opt_cutofftemp; +extern int opt_log_interval; +extern unsigned long long global_hashrate; +extern char current_hash[68]; +extern double current_diff; +extern uint64_t best_diff; +extern struct timeval block_timeval; +extern char *workpadding; + +struct curl_ent { + CURL *curl; + struct list_head node; + struct timeval tv; +}; + +/* Disabled needs to be the lowest enum as a freshly calloced value will then + * equal disabled */ +enum pool_enable { + POOL_DISABLED, + POOL_ENABLED, + POOL_REJECTING, +}; + +struct stratum_work { + char *job_id; + unsigned char **merkle_bin; + bool clean; + + double diff; +}; + +#define RBUFSIZE 8192 +#define RECVSIZE (RBUFSIZE - 4) + +struct pool { + int pool_no; + int prio; + int64_t accepted, rejected; + int seq_rejects; + int seq_getfails; + int solved; + int64_t diff1; + char diff[8]; + int quota; + int quota_gcd; + int quota_used; + int works; + + double diff_accepted; + double diff_rejected; + double diff_stale; + + bool submit_fail; + bool idle; + bool lagging; + bool probed; + enum pool_enable enabled; + bool submit_old; + bool removed; + bool lp_started; + bool blocking; + + char *hdr_path; + char *lp_url; + + unsigned int getwork_requested; + unsigned int stale_shares; + unsigned int discarded_work; + unsigned int getfail_occasions; + unsigned int remotefail_occasions; + struct timeval tv_idle; + + double utility; + int last_shares, shares; + + char *rpc_req; + char *rpc_url; + char *rpc_userpass; + char *rpc_user, *rpc_pass; + proxytypes_t rpc_proxytype; + char *rpc_proxy; + + pthread_mutex_t pool_lock; + cglock_t data_lock; + + struct thread_q *submit_q; + struct thread_q *getwork_q; + + pthread_t longpoll_thread; + pthread_t test_thread; + bool testing; + + int curls; + pthread_cond_t cr_cond; + struct list_head curlring; + + time_t last_share_time; + double last_share_diff; + uint64_t best_diff; + + struct cgminer_stats cgminer_stats; + struct cgminer_pool_stats cgminer_pool_stats; + + /* The last block this particular pool knows about */ + char prev_block[32]; + + /* Stratum variables */ + char *stratum_url; + bool extranonce_subscribe; + char *stratum_port; + SOCKETTYPE sock; + char *sockbuf; + size_t sockbuf_size; + char *sockaddr_url; /* stripped url used for sockaddr */ + char *sockaddr_proxy_url; + char *sockaddr_proxy_port; + + char *nonce1; + unsigned char *nonce1bin; + uint64_t nonce2; + int n2size; + char *sessionid; + bool has_stratum; + bool stratum_active; + bool stratum_init; + bool stratum_notify; +#ifdef USE_BITMAIN_C5 + bool support_vil; + int version_num; + int version[4]; +#endif + struct stratum_work swork; + pthread_t stratum_sthread; + pthread_t stratum_rthread; + pthread_mutex_t stratum_lock; + struct thread_q *stratum_q; + int sshares; /* stratum shares submitted waiting on response */ + + /* GBT variables */ + bool has_gbt; + cglock_t gbt_lock; + unsigned char previousblockhash[32]; + unsigned char gbt_target[32]; + char *coinbasetxn; + char *longpollid; + char *gbt_workid; + int gbt_expires; + uint32_t gbt_version; + uint32_t curtime; + uint32_t gbt_bits; + unsigned char *txn_hashes; + int gbt_txns; + int height; + + bool gbt_solo; + unsigned char merklebin[16 * 32]; + int transactions; + char *txn_data; + unsigned char scriptsig_base[100]; + unsigned char script_pubkey[25 + 3]; + int nValue; + CURL *gbt_curl; + bool gbt_curl_inuse; + + /* Shared by both stratum & GBT */ + size_t n1_len; + unsigned char *coinbase; + int coinbase_len; + int nonce2_offset; + unsigned char header_bin[128]; + int merkles; + char prev_hash[68]; + char bbversion[12]; + char nbit[12]; + char ntime[12]; + double sdiff; + + struct timeval tv_lastwork; +}; + +#define GETWORK_MODE_TESTPOOL 'T' +#define GETWORK_MODE_POOL 'P' +#define GETWORK_MODE_LP 'L' +#define GETWORK_MODE_BENCHMARK 'B' +#define GETWORK_MODE_STRATUM 'S' +#define GETWORK_MODE_GBT 'G' +#define GETWORK_MODE_SOLO 'C' + +struct work { + unsigned char data[128]; + unsigned char midstate[32]; + unsigned char target[32]; + unsigned char hash[32]; + + /* This is the diff the device is currently aiming for and must be + * the minimum of work_difficulty & drv->max_diff */ + double device_diff; + uint64_t share_diff; + + int rolls; + int drv_rolllimit; /* How much the driver can roll ntime */ + uint32_t nonce; /* For devices that hash sole work */ + + struct thr_info *thr; + int thr_id; + struct pool *pool; + struct timeval tv_staged; + + bool mined; + bool clone; + bool cloned; + int rolltime; + bool longpoll; + bool stale; + bool mandatory; + bool block; + + bool stratum; + char *job_id; + uint64_t nonce2; + size_t nonce2_len; + char *ntime; + double sdiff; + char *nonce1; + + bool gbt; + char *coinbase; + int gbt_txns; + + unsigned int work_block; + uint32_t id; + UT_hash_handle hh; + + /* This is the diff work we're aiming to submit and should match the + * work->target binary */ + double work_difficulty; + + // Allow devices to identify work if multiple sub-devices + int subid; + // Allow devices to flag work for their own purposes + bool devflag; + // Allow devices to timestamp work for their own purposes + struct timeval tv_stamp; + + struct timeval tv_getwork; + struct timeval tv_getwork_reply; + struct timeval tv_cloned; + struct timeval tv_work_start; + struct timeval tv_work_found; + char getwork_mode; +#ifdef USE_BITMAIN_C5 + int version; +#endif + + +}; + +#ifdef USE_MODMINER +struct modminer_fpga_state { + bool work_running; + struct work running_work; + struct timeval tv_workstart; + uint32_t hashes; + + char next_work_cmd[46]; + char fpgaid; + + bool overheated; + bool new_work; + + uint32_t shares; + uint32_t shares_last_hw; + uint32_t hw_errors; + uint32_t shares_to_good; + uint32_t timeout_fail; + uint32_t success_more; + struct timeval last_changed; + struct timeval last_nonce; + struct timeval first_work; + bool death_stage_one; + bool tried_two_byte_temp; + bool one_byte_temp; +}; +#endif + +#define TAILBUFSIZ 64 + +#define tailsprintf(buf, bufsiz, fmt, ...) do { \ + char tmp13[TAILBUFSIZ]; \ + size_t len13, buflen = strlen(buf); \ + snprintf(tmp13, sizeof(tmp13), fmt, ##__VA_ARGS__); \ + len13 = strlen(tmp13); \ + if ((buflen + len13) >= bufsiz) \ + quit(1, "tailsprintf buffer overflow in %s %s line %d", __FILE__, __func__, __LINE__); \ + strcat(buf, tmp13); \ +} while (0) + +extern uint64_t share_ndiff(const struct work *work); +extern void get_datestamp(char *, size_t, struct timeval *); +extern void inc_hw_errors(struct thr_info *thr); +extern void inc_dev_status(int max_fan, int max_temp); +extern void inc_work_stats(struct thr_info *thr, struct pool *pool, int diff1); +extern bool test_nonce(struct work *work, uint32_t nonce); +extern bool test_nonce_diff(struct work *work, uint32_t nonce, double diff); +extern bool submit_tested_work(struct thr_info *thr, struct work *work); +extern bool submit_nonce(struct thr_info *thr, struct work *work, uint32_t nonce); +extern bool submit_noffset_nonce(struct thr_info *thr, struct work *work, uint32_t nonce, + int noffset); +extern int share_work_tdiff(struct cgpu_info *cgpu); +extern bool submit_nonce_1(struct thr_info *thr, struct work *work, uint32_t nonce, int * nofull); +extern void submit_nonce_2(struct work *work); +extern bool submit_nonce_direct(struct thr_info *thr, struct work *work, uint32_t nonce); +extern bool submit_noffset_nonce(struct thr_info *thr, struct work *work, uint32_t nonce, int noffset); +extern struct work *get_work(struct thr_info *thr, const int thr_id); +extern void __add_queued(struct cgpu_info *cgpu, struct work *work); +extern struct work *get_queued(struct cgpu_info *cgpu); +extern struct work *__get_queued(struct cgpu_info *cgpu); +extern void add_queued(struct cgpu_info *cgpu, struct work *work); +extern struct work *get_queue_work(struct thr_info *thr, struct cgpu_info *cgpu, int thr_id); +extern struct work *__find_work_bymidstate(struct work *que, char *midstate, size_t midstatelen, char *data, int offset, size_t datalen); +extern struct work *find_queued_work_bymidstate(struct cgpu_info *cgpu, char *midstate, size_t midstatelen, char *data, int offset, size_t datalen); +extern struct work *clone_queued_work_bymidstate(struct cgpu_info *cgpu, char *midstate, size_t midstatelen, char *data, int offset, size_t datalen); +extern struct work *__find_work_byid(struct work *que, uint32_t id); +extern struct work *find_queued_work_byid(struct cgpu_info *cgpu, uint32_t id); +extern struct work *clone_queued_work_byid(struct cgpu_info *cgpu, uint32_t id); +extern void __work_completed(struct cgpu_info *cgpu, struct work *work); +extern int age_queued_work(struct cgpu_info *cgpu, double secs); +extern void work_completed(struct cgpu_info *cgpu, struct work *work); +extern struct work *take_queued_work_bymidstate(struct cgpu_info *cgpu, char *midstate, size_t midstatelen, char *data, int offset, size_t datalen); +extern void flush_queue(struct cgpu_info *cgpu); +extern void hash_driver_work(struct thr_info *mythr); +extern void hash_queued_work(struct thr_info *mythr); +extern void _wlog(const char *str); +extern void _wlogprint(const char *str); +extern int curses_int(const char *query); +extern char *curses_input(const char *query); +extern void kill_work(void); +extern void switch_pools(struct pool *selected); +extern void _discard_work(struct work *work); +#define discard_work(WORK) do { \ + _discard_work(WORK); \ + WORK = NULL; \ +} while (0) +extern void remove_pool(struct pool *pool); +extern void write_config(FILE *fcfg); +extern void zero_bestshare(void); +extern void zero_stats(void); +extern void default_save_file(char *filename); +extern bool log_curses_only(int prio, const char *datetime, const char *str); +extern void clear_logwin(void); +extern void logwin_update(void); +extern bool pool_tclear(struct pool *pool, bool *var); +extern void stratum_resumed(struct pool *pool); +extern void pool_died(struct pool *pool); +extern struct thread_q *tq_new(void); +extern void tq_free(struct thread_q *tq); +extern bool tq_push(struct thread_q *tq, void *data); +extern void *tq_pop(struct thread_q *tq, const struct timespec *abstime); +extern void tq_freeze(struct thread_q *tq); +extern void tq_thaw(struct thread_q *tq); +extern bool successful_connect; +extern void adl(void); +extern void app_restart(void); +extern void roll_work(struct work *work); +extern struct work *make_clone(struct work *work); +extern void clean_work(struct work *work); +extern void _free_work(struct work *work); +#define free_work(WORK) do { \ + _free_work(WORK); \ + WORK = NULL; \ +} while (0) +extern void set_work_ntime(struct work *work, int ntime); +extern struct work *copy_work_noffset(struct work *base_work, int noffset); +#define copy_work(work_in) copy_work_noffset(work_in, 0) +extern uint64_t share_diff(const struct work *work); +extern struct thr_info *get_thread(int thr_id); +extern struct cgpu_info *get_devices(int id); + +enum api_data_type { + API_ESCAPE, + API_STRING, + API_CONST, + API_UINT8, + API_INT16, + API_UINT16, + API_INT, + API_UINT, + API_UINT32, + API_HEX32, + API_UINT64, + API_INT64, + API_DOUBLE, + API_ELAPSED, + API_BOOL, + API_TIMEVAL, + API_TIME, + API_MHS, + API_MHTOTAL, + API_TEMP, + API_UTILITY, + API_FREQ, + API_VOLTS, + API_HS, + API_DIFF, + API_PERCENT, + API_AVG +}; + +struct api_data { + enum api_data_type type; + char *name; + void *data; + bool data_was_malloc; + struct api_data *prev; + struct api_data *next; +}; + +extern struct api_data *api_add_escape(struct api_data *root, char *name, char *data, bool copy_data); +extern struct api_data *api_add_string(struct api_data *root, char *name, char *data, bool copy_data); +extern struct api_data *api_add_const(struct api_data *root, char *name, const char *data, bool copy_data); +extern struct api_data *api_add_uint8(struct api_data *root, char *name, uint8_t *data, bool copy_data); +extern struct api_data *api_add_int16(struct api_data *root, char *name, uint16_t *data, bool copy_data); +extern struct api_data *api_add_uint16(struct api_data *root, char *name, uint16_t *data, bool copy_data); +extern struct api_data *api_add_int(struct api_data *root, char *name, int *data, bool copy_data); +extern struct api_data *api_add_uint(struct api_data *root, char *name, unsigned int *data, bool copy_data); +extern struct api_data *api_add_uint32(struct api_data *root, char *name, uint32_t *data, bool copy_data); +extern struct api_data *api_add_hex32(struct api_data *root, char *name, uint32_t *data, bool copy_data); +extern struct api_data *api_add_uint64(struct api_data *root, char *name, uint64_t *data, bool copy_data); +extern struct api_data *api_add_double(struct api_data *root, char *name, double *data, bool copy_data); +extern struct api_data *api_add_elapsed(struct api_data *root, char *name, double *data, bool copy_data); +extern struct api_data *api_add_bool(struct api_data *root, char *name, bool *data, bool copy_data); +extern struct api_data *api_add_timeval(struct api_data *root, char *name, struct timeval *data, bool copy_data); +extern struct api_data *api_add_time(struct api_data *root, char *name, time_t *data, bool copy_data); +extern struct api_data *api_add_mhs(struct api_data *root, char *name, double *data, bool copy_data); +extern struct api_data *api_add_mhstotal(struct api_data *root, char *name, double *data, bool copy_data); +extern struct api_data *api_add_temp(struct api_data *root, char *name, float *data, bool copy_data); +extern struct api_data *api_add_utility(struct api_data *root, char *name, double *data, bool copy_data); +extern struct api_data *api_add_freq(struct api_data *root, char *name, double *data, bool copy_data); +extern struct api_data *api_add_volts(struct api_data *root, char *name, float *data, bool copy_data); +extern struct api_data *api_add_hs(struct api_data *root, char *name, double *data, bool copy_data); +extern struct api_data *api_add_diff(struct api_data *root, char *name, double *data, bool copy_data); +extern struct api_data *api_add_percent(struct api_data *root, char *name, double *data, bool copy_data); +extern struct api_data *api_add_avg(struct api_data *root, char *name, float *data, bool copy_data); + +extern void dupalloc(struct cgpu_info *cgpu, int timelimit); +extern void dupcounters(struct cgpu_info *cgpu, uint64_t *checked, uint64_t *dups); +extern bool isdupnonce(struct cgpu_info *cgpu, struct work *work, uint32_t nonce); + +#if defined(USE_BITMAIN) || defined(USE_BMSC) +extern void rev(unsigned char *s, size_t l); +extern int check_asicnum(int asic_num, unsigned char nonce); +#endif +#endif /* __MINER_H__ */ diff --git a/miner.php b/miner.php new file mode 100644 index 0000000..6d4d210 --- /dev/null +++ b/miner.php @@ -0,0 +1,3306 @@ +\n"; +# +# See API-README for more details of these variables and how +# to configure miner.php +# +# Web page title +$title = 'Mine'; +# +# Set $readonly to true to force miner.php to be readonly +# Set $readonly to false then it will check cgminer 'privileged' +$readonly = false; +# +# Set $userlist to null to allow anyone access or read API-README +$userlist = null; +# +# Set $notify to false to NOT attempt to display the notify command +# Set $notify to true to attempt to display the notify command +$notify = true; +# +# Set $checklastshare to true to do the following checks: +# If a device's last share is 12x expected ago then display as an error +# If a device's last share is 8x expected ago then display as a warning +# If either of the above is true, also display the whole line highlighted +# This assumes shares are 1 difficulty shares +$checklastshare = true; +# +# Set $poolinputs to true to show the input fields for adding a pool +# and changing the pool priorities +# N.B. also if $readonly is true, it will not display the fields +$poolinputs = false; +# +# Default port to use if any $rigs entries don't specify the port number +$rigport = 4028; +# +# Set $rigs to an array of your cgminer rigs that are running +# format: 'IP' or 'Host' or 'IP:Port' or 'Host:Port' or 'Host:Port:Name' +$rigs = array('127.0.0.1:4028'); +# +# Set $rignames to false, or one of 'ip' or 'ipx' +# this says what to use if $rigs doesn't have a 'name' +$rignames = false; +# +# Set $rigbuttons to false to display a link rather than a button +$rigbuttons = true; +# +# Set $mcast to true to look for your rigs and ignore $rigs +$mcast = false; +# +# Set $mcastexpect to at least how many rigs you expect it to find +$mcastexpect = 0; +# +# API Multicast address all cgminers are listening on +$mcastaddr = '224.0.0.75'; +# +# API Multicast UDP port all cgminers are listening on +$mcastport = 4028; +# +# The code all cgminers expect in the Multicast message sent +$mcastcode = 'FTW'; +# +# UDP port cgminers are to reply on (by request) +$mcastlistport = 4027; +# +# Set $mcasttimeout to the number of seconds (floating point) +# to wait for replies to the Multicast message +$mcasttimeout = 1.5; +# +# Set $mcastretries to the number of times to retry the multicast +$mcastretries = 0; +# +# Set $allowgen to true to allow customsummarypages to use 'gen' +# false means ignore any 'gen' options +$allowgen = false; +# +# Set $rigipsecurity to false to show the IP/Port of the rig +# in the socket error messages and also show the full socket message +$rigipsecurity = true; +# +# Set $rigtotals to true to display totals on the single rig page +# 'false' means no totals (and ignores $forcerigtotals) +# You can force it to always show rig totals when there is only +# one line by setting $forcerigtotals = true; +$rigtotals = true; +$forcerigtotals = false; +# +# These should be OK for most cases +$socksndtimeoutsec = 10; +$sockrcvtimeoutsec = 40; +# +# List of fields NOT to be displayed +# This example would hide the slightly more sensitive pool information +#$hidefields = array('POOL.URL' => 1, 'POOL.User' => 1); +$hidefields = array(); +# +# Auto-refresh of the page (in seconds) - integers only +# $ignorerefresh = true/false always ignore refresh parameters +# $changerefresh = true/false show buttons to change the value +# $autorefresh = default value, 0 means dont auto-refresh +$ignorerefresh = false; +$changerefresh = true; +$autorefresh = 0; +# +# Should we allow custom pages? +# (or just completely ignore them and don't display the buttons) +$allowcustompages = true; +# +# OK this is a bit more complex item: Custom Summary Pages +# As mentioned above, see API-README +# see the example below (if there is no matching data, no total will show) +$mobilepage = array( + 'DATE' => null, + 'RIGS' => null, + 'SUMMARY' => array('Elapsed', 'MHS av', 'MHS 5m', 'Found Blocks=Blks', + 'Difficulty Accepted=DiffA', + 'Difficulty Rejected=DiffR', + 'Hardware Errors=HW', + 'Work Utility=WU'), + 'DEVS+NOTIFY' => array('DEVS.Name=Name', 'DEVS.ID=ID', 'DEVS.Status=Status', + 'DEVS.Temperature=Temp', 'DEVS.MHS av=MHS av', + 'DEVS.MHS 5m=MHS 5m', 'DEVS.Difficulty Accepted=DiffA', + 'DEVS.Difficulty Rejected=DiffR', + 'DEVS.Work Utility=WU', + 'NOTIFY.Last Not Well=Not Well'), + 'POOL' => array('POOL', 'Status', 'Difficulty Accepted=DiffA', + 'Difficulty Rejected=DiffR', 'Last Share Time=LST')); +$mobilesum = array( + 'SUMMARY' => array('MHS av', 'MHS 5m', 'Found Blocks', 'Difficulty Accepted', + 'Difficulty Rejected', 'Hardware Errors', + 'Work Utility'), + 'DEVS+NOTIFY' => array('DEVS.MHS av', 'DEVS.Difficulty Accepted', + 'DEVS.Difficulty Rejected'), + 'POOL' => array('Difficulty Accepted', 'Difficulty Rejected')); +# +$statspage = array( + 'DATE' => null, + 'RIGS' => null, + 'SUMMARY' => array('Elapsed', 'MHS av', 'MHS 5m', 'Found Blocks=Blks', + 'Difficulty Accepted=DiffA', + 'Difficulty Rejected=DiffR', + 'Work Utility=WU', 'Hardware Errors=HW Errs', + 'Network Blocks=Net Blks'), + 'COIN' => array('*'), + 'STATS' => array('*')); +# +$statssum = array( + 'SUMMARY' => array('MHS av', 'MHS 5m', 'Found Blocks', + 'Difficulty Accepted', 'Difficulty Rejected', + 'Work Utility', 'Hardware Errors')); +# +$poolspage = array( + 'DATE' => null, + 'RIGS' => null, + 'SUMMARY' => array('Elapsed', 'MHS av', 'MHS 5m', 'Found Blocks=Blks', + 'Difficulty Accepted=DiffA', + 'Difficulty Rejected=DiffR', + 'Work Utility', 'Hardware Errors=HW', + 'Network Blocks=Net Blks', 'Best Share'), + 'POOL+STATS' => array('STATS.ID=ID', 'POOL.URL=URL', + 'POOL.Difficulty Accepted=DiffA', + 'POOL.Difficulty Rejected=DiffR', + 'POOL.Has Stratum=Stratum', + 'POOL.Stratum Active=StrAct', + 'POOL.Has GBT=GBT', 'STATS.Times Sent=TSent', + 'STATS.Bytes Sent=BSent', 'STATS.Net Bytes Sent=NSent', + 'STATS.Times Recv=TRecv', 'STATS.Bytes Recv=BRecv', + 'STATS.Net Bytes Recv=NRecv', 'GEN.AvShr=AvShr')); +# +$poolssum = array( + 'SUMMARY' => array('MHS av', 'MHS 5m', 'Found Blocks', + 'Difficulty Accepted', 'Difficulty Rejected', + 'Work Utility', 'Hardware Errors'), + 'POOL+STATS' => array('POOL.Difficulty Accepted', 'POOL.Difficulty Rejected', + 'STATS.Times Sent', 'STATS.Bytes Sent', + 'STATS.Net Bytes Sent', 'STATS.Times Recv', + 'STATS.Bytes Recv', 'STATS.Net Bytes Recv')); +# +$poolsext = array( + 'POOL+STATS' => array( + 'where' => null, + 'group' => array('POOL.URL', 'POOL.Has Stratum', + 'POOL.Stratum Active', 'POOL.Has GBT'), + 'calc' => array('POOL.Difficulty Accepted' => 'sum', + 'POOL.Difficulty Rejected' => 'sum', + 'STATS.Times Sent' => 'sum', + 'STATS.Bytes Sent' => 'sum', + 'STATS.Net Bytes Sent' => 'sum', + 'STATS.Times Recv' => 'sum', + 'STATS.Bytes Recv' => 'sum', + 'STATS.Net Bytes Recv' => 'sum', + 'POOL.Accepted' => 'sum'), + 'gen' => array('AvShr' => + 'round(POOL.Difficulty Accepted/'. + 'max(POOL.Accepted,1)*100)/100'), + 'having' => array(array('STATS.Bytes Recv', '>', 0))) +); +# +$devnotpage = array( + 'DATE' => null, + 'RIGS' => null, + 'DEVS+NOTIFY' => array('DEVS.Name=Name', 'DEVS.ID=ID', + 'DEVS.Temperature=Temp', 'DEVS.MHS av=MHS av', + 'DEVS.Difficulty Accepted=DiffA', + 'DEVS.Difficulty Rejected=DiffR', + 'NOTIFY.Last Not Well=Last Not Well')); +$devnotsum = array( + 'DEVS+NOTIFY' => array('DEVS.MHS av', 'DEVS.Difficulty Accepted', + 'DEVS.Difficulty Rejected')); +# +$devdetpage = array( + 'DATE' => null, + 'RIGS' => null, + 'DEVS+DEVDETAILS' => array('DEVS.Name=Name', 'DEVS.ID=ID', + 'DEVS.Temperature=Temp', + 'DEVS.MHS av=MHS av', + 'DEVS.Difficulty Accepted=DiffA', + 'DEVS.Difficulty Rejected=DiffR', + 'DEVDETAILS.Device Path=Device')); +$devdetsum = array( + 'DEVS+DEVDETAILS' => array('DEVS.MHS av', 'DEVS.Difficulty Accepted', + 'DEVS.Difficulty Rejected')); +# +$protopage = array( + 'DATE' => null, + 'RIGS' => null, + 'CONFIG' => array('ASC Count=ASCs', 'PGA Count=PGAs', 'Pool Count=Pools', + 'Strategy', 'Device Code', 'OS', 'Failover-Only'), + 'SUMMARY' => array('Elapsed', 'MHS av', 'Found Blocks=Blks', + 'Difficulty Accepted=Diff Acc', + 'Difficulty Rejected=Diff Rej', + 'Hardware Errors=HW Errs', + 'Network Blocks=Net Blks', 'Utility', 'Work Utility'), + 'POOL+STATS' => array('STATS.ID=ID', 'POOL.URL=URL', 'POOL.Accepted=Acc', + 'POOL.Difficulty Accepted=DiffA', + 'POOL.Difficulty Rejected=DiffR', 'POOL.Has GBT=GBT', + 'STATS.Max Diff=Max Work Diff', + 'STATS.Times Sent=#Sent', 'STATS.Bytes Sent=Byte Sent', + 'STATS.Net Bytes Sent=Net Sent', + 'STATS.Times Recv=#Recv', + 'STATS.Bytes Recv=Byte Recv', + 'STATS.Net Bytes Recv=Net Recv')); +$protosum = array( + 'SUMMARY' => array('MHS av', 'Found Blocks', 'Difficulty Accepted', + 'Difficulty Rejected', 'Hardware Errors', + 'Utility', 'Work Utility'), + 'POOL+STATS' => array('POOL.Accepted', 'POOL.Difficulty Accepted', + 'POOL.Difficulty Rejected', + 'STATS.Times Sent', 'STATS.Bytes Sent', + 'STATS.Net Bytes Sent', 'STATS.Times Recv', + 'STATS.Bytes Recv', 'STATS.Net Bytes Recv')); +$protoext = array( + 'POOL+STATS' => array( + 'where' => null, + 'group' => array('POOL.URL', 'POOL.Has GBT'), + 'calc' => array('POOL.Accepted' => 'sum', + 'POOL.Difficulty Accepted' => 'sum', + 'POOL.Difficulty Rejected' => 'sum', + 'STATS.Max Diff' => 'max', + 'STATS.Times Sent' => 'sum', + 'STATS.Bytes Sent' => 'sum', + 'STATS.Net Bytes Sent' => 'sum', + 'STATS.Times Recv' => 'sum', + 'STATS.Bytes Recv' => 'sum', + 'STATS.Net Bytes Recv' => 'sum'), + 'having' => array(array('STATS.Bytes Recv', '>', 0))) +); +# +# If 'gen' isn't enabled, the 'GEN' fields won't show but +# where present, will be replaced with the ||SUMMARY fields +$kanogenpage = array( + 'DATE' => null, + 'RIGS' => null, + 'SUMMARY+COIN' => array('SUMMARY.Elapsed=Elapsed', + 'GEN.Mined=Block%', 'GEN.GHS Acc=GH/s Acc', + 'GEN.GHS av=GH/s av||SUMMARY.MHS av=MHS av', + 'GEN.GHS 5m=GH/s 5m||SUMMARY.MHS 5m=MHS 5m', + 'GEN.GHS WU=GH/s WU||SUMMARY.Work Utility=WU', + 'SUMMARY.Found Blocks=Blks', + 'SUMMARY.Difficulty Accepted=DiffA', + 'SUMMARY.Difficulty Rejected=DiffR', + 'SUMMARY.Hardware Errors=HW', + 'SUMMARY.Difficulty Stale=DiffS', + 'SUMMARY.Best Share=Best Share', + 'SUMMARY.Device Hardware%=Dev HW%', + 'SUMMARY.Device Rejected%=Dev Rej%', + 'SUMMARY.Pool Rejected%=Pool Rej%', + 'SUMMARY.Pool Stale%=Pool Stale%'), + 'POOL' => array('URL', 'Diff1 Shares=Diff Work', + 'Difficulty Accepted=DiffA', + 'Difficulty Rejected=DiffR', + 'Difficulty Stale=DiffS', + 'Best Share', 'GEN.Acc=Pool Acc%', 'GEN.Rej=Pool Rej%') +); +# sum should list all fields seperately including GEN/BGEN || replacements +$kanogensum = array( + 'SUMMARY+COIN' => array('GEN.Mined', 'GEN.GHS Acc', 'GEN.GHS av', + 'GEN.GHS 5m', 'GEN.GHS WU', + 'SUMMARY.MHS av', 'SUMMARY.MHS 5m', + 'SUMMARY.Work Utility', + 'SUMMARY.Found Blocks', + 'SUMMARY.Difficulty Accepted', + 'SUMMARY.Difficulty Rejected', + 'SUMMARY.Hardware Errors', + 'SUMMARY.Difficulty Stale'), + 'POOL' => array('Diff1 Shares', 'Difficulty Accepted', + 'Difficulty Rejected', 'Difficulty Stale') +); +# 'where', 'calc' and 'having' should list GEN/BGEN || replacements seperately +# 'group' must use the 'name1||name2' format for GEN/BGEN fields +$kanogenext = array( + 'SUMMARY+COIN' => array( + 'gen' => array('GHS Acc' => + 'round(pow(2,32) * SUMMARY.Difficulty Accepted / '. + 'SUMMARY.Elapsed / 10000000) / 100', + 'Mined' => + 'SUMMARY.Elapsed * SUMMARY.Work Utility / 60 / '. + 'COIN.Network Difficulty', + 'GHS av' => + 'SUMMARY.MHS av / 1000.0', + 'GHS 5m' => + 'SUMMARY.MHS 5m / 1000.0', + 'GHS WU' => + 'round(pow(2,32) * SUMMARY.Work Utility / 60 / '. + '10000000 ) / 100')), + 'POOL' => array( + 'group' => array('URL'), + 'calc' => array('Diff1 Shares' => 'sum', 'Difficulty Accepted' => 'sum', + 'Difficulty Rejected' => 'sum', + 'Difficulty Stale' => 'sum', 'Best Share' => 'max'), + 'gen' => array('Rej' => 'Difficulty Rejected / '. + 'max(1,Difficulty Accepted+Difficulty Rejected)', + 'Acc' => 'Difficulty Accepted / '. + 'max(1,Difficulty Accepted+Difficulty Rejected)')) +); +# +$syspage = array( + 'DATE' => null, + 'RIGS' => null, + 'SUMMARY' => array('#', 'Elapsed', 'MHS av', 'MHS 5m', 'Found Blocks=Blks', + 'Difficulty Accepted=DiffA', + 'Difficulty Rejected=DiffR', + 'Difficulty Stale=DiffS', 'Hardware Errors=HW', + 'Work Utility', 'Network Blocks=Net Blks', 'Total MH', + 'Best Share', 'Device Hardware%=Dev HW%', + 'Device Rejected%=Dev Rej%', + 'Pool Rejected%=Pool Rej%', 'Pool Stale%', + 'Last getwork'), + 'DEVS' => array('#', 'ID', 'Name', 'ASC', 'Device Elapsed', 'Enabled', + 'Status', 'No Device', 'Temperature=Temp', + 'MHS av', 'MHS 5s', 'MHS 5m', 'Diff1 Work', + 'Difficulty Accepted=DiffA', + 'Difficulty Rejected=DiffR', + 'Hardware Errors=HW', 'Work Utility', + 'Last Valid Work', 'Last Share Pool', + 'Last Share Time', 'Total MH', + 'Device Hardware%=Dev HW%', + 'Device Rejected%=Dev Rej%'), + 'POOL' => array('POOL', 'URL', 'Status', 'Priority', 'Quota', + 'Getworks', 'Diff1 Shares', + 'Difficulty Accepted=DiffA', + 'Difficulty Rejected=DiffR', + 'Difficulty Stale=DiffS', + 'Last Share Difficulty', + 'Last Share Time', + 'Best Share', 'Pool Rejected%=Pool Rej%', + 'Pool Stale%') +); +$syssum = array( + 'SUMMARY' => array('MHS av', 'MHS 5m', 'Found Blocks', + 'Difficulty Accepted', 'Difficulty Rejected', + 'Difficulty Stale', 'Hardware Errors', + 'Work Utility', 'Total MH'), + 'DEVS' => array('MHS av', 'MHS 5s', 'MHS 5m', 'Diff1 Work', + 'Difficulty Accepted', 'Difficulty Rejected', + 'Hardware Errors', 'Total MH'), + 'POOL' => array('Getworks', 'Diff1 Shares', 'Difficulty Accepted', + 'Difficulty Rejected', 'Difficulty Stale') +); +# +# $customsummarypages is an array of these Custom Summary Pages +# that you can override in myminer.php +# It can be 'Name' => 1 with 'Name' in any of $user_pages or $sys_pages +# and it can be a fully defined 'Name' => array(...) like in $sys_pages below +$customsummarypages = array( + 'Kano' => 1, + 'Mobile' => 1, + 'Stats' => 1, + 'Pools' => 1 +); +# +# $user_pages are the myminer.php definable version of $sys_pages +# It should contain a set of 'Name' => array(...) like in $sys_pages +# that $customsummarypages can refer to by 'Name' +# If a 'Name' is in both $user_pages and $sys_pages, then the one +# in $user_pages will override the one in $sys_pages +$user_pages = array(); +# +$here = $_SERVER['PHP_SELF']; +# +global $tablebegin, $tableend, $warnfont, $warnoff, $dfmt; +# +$tablebegin = ''; +$tableend = '
'; +$warnfont = ''; +$warnoff = ''; +$dfmt = 'H:i:s j-M-Y \U\T\CP'; +# +$miner_font_family = 'Verdana, Arial, sans-serif, sans'; +$miner_font_size = '13pt'; +# +$bad_font_family = '"Times New Roman", Times, serif'; +$bad_font_size = '18pt'; +# +# List of css names to add to the css style object +# e.g. array('td.cool' => false); +# true/false to not include the default $miner_font +# The css name/value pairs must be defined in $colouroverride below +$add_css_names = array(); +# +# Edit this or redefine it in myminer.php to change the colour scheme +# See $colourtable below for the list of names +$colouroverride = array(); +# +# Where to place the buttons: 'top' 'bot' 'both' +# anything else means don't show them - case sensitive +$placebuttons = 'top'; +# +# This below allows you to put your own settings into a seperate file +# so you don't need to update miner.php with your preferred settings +# every time a new version is released +# Just create the file 'myminer.php' in the same directory as +# 'miner.php' - and put your own settings in there +if (file_exists('myminer.php')) + include_once('myminer.php'); +# +# This is the system default that must always contain all necessary +# colours so it must be a constant +# You can override these values with $colouroverride +# The only one missing is $warnfont +# - which you can override directly anyway +global $colourtable; +$colourtable = array( + 'body bgcolor' => '#ecffff', + 'td color' => 'blue', + 'td.two color' => 'blue', + 'td.two background' => '#ecffff', + 'td.h color' => 'blue', + 'td.h background' => '#c4ffff', + 'td.err color' => 'black', + 'td.err background' => '#ff3050', + 'td.bad color' => 'black', + 'td.bad background' => '#ff3050', + 'td.warn color' => 'black', + 'td.warn background' => '#ffb050', + 'td.sta color' => 'green', + 'td.tot color' => 'blue', + 'td.tot background' => '#fff8f2', + 'td.lst color' => 'blue', + 'td.lst background' => '#ffffdd', + 'td.hi color' => 'blue', + 'td.hi background' => '#f6ffff', + 'td.lo color' => 'blue', + 'td.lo background' => '#deffff' +); +# +# A list of system default summary pages (defined further above) +# that you can use by 'Name' in $customsummarypages +global $sys_pages; +$sys_pages = array( + 'Mobile' => array($mobilepage, $mobilesum), + 'Stats' => array($statspage, $statssum), + 'Pools' => array($poolspage, $poolssum, $poolsext), + 'DevNot' => array($devnotpage, $devnotsum), + 'DevDet' => array($devdetpage, $devdetsum), + 'Proto' => array($protopage, $protosum, $protoext), + 'Kano' => array($kanogenpage, $kanogensum, $kanogenext), + 'Summary' => array($syspage, $syssum) +); +# +# Don't touch these 2 +$miner = null; +$port = null; +# +global $rigips; +$rigips = array(); +# +# Ensure it is only ever shown once +global $showndate; +$showndate = false; +# +global $rownum; +$rownum = 0; +# +// Login +global $ses; +$ses = 'rutroh'; +# +function getcsp($name, $systempage = false) +{ + global $customsummarypages, $user_pages, $sys_pages; + + if ($systempage === false) + { + if (!isset($customsummarypages[$name])) + return false; + + $csp = $customsummarypages[$name]; + if (is_array($csp)) + { + if (count($csp) < 2 || count($csp) > 3) + return false; + else + return $csp; + } + } + + if (isset($user_pages[$name])) + { + $csp = $user_pages[$name]; + if (!is_array($csp) || count($csp) < 2 || count($csp) > 3) + return false; + else + return $csp; + } + + if (isset($sys_pages[$name])) + { + $csp = $sys_pages[$name]; + if (!is_array($csp) || count($csp) < 2 || count($csp) > 3) + return false; + else + return $csp; + } + + return false; +} +# +function degenfields(&$sec, $name, $fields) +{ + global $allowgen; + + if (!is_array($fields)) + return; + + foreach ($fields as $num => $fld) + if (substr($fld, 0, 5) == 'BGEN.' || substr($fld, 0, 4) == 'GEN.') + { + $opts = explode('||', $fld, 2); + if ($allowgen) + { + if (count($opts) > 1) + $sec[$name][$num] = $opts[0]; + } + else + { + if (count($opts) > 1) + $sec[$name][$num] = $opts[1]; + else + unset($sec[$name][$num]); + } + } +} +# +# Allow BGEN/GEN fields to have a '||' replacement when gen is disabled +# N.B. if gen is disabled and all page fields are GBEN/GEN without '||' then +# the table will disappear +# Replacements can be in the page fields and then also the ext group fields +# All other $csp sections should list both separately +function degen(&$csp) +{ + $page = 0; + if (isset($csp[$page]) && is_array($csp[$page])) + foreach ($csp[$page] as $sec => $fields) + degenfields($csp[$page], $sec, $fields); + + $ext = 2; + if (isset($csp[$ext]) && is_array($csp[$ext])) + foreach ($csp[$ext] as $sec => $types) + if (is_array($types) && isset($types['group'])) + degenfields($types, 'group', $types['group']); +} +# +function getcss($cssname, $dom = false) +{ + global $colourtable, $colouroverride; + + $css = ''; + foreach ($colourtable as $cssdata => $value) + { + $cssobj = explode(' ', $cssdata, 2); + if ($cssobj[0] == $cssname) + { + if (isset($colouroverride[$cssdata])) + $value = $colouroverride[$cssdata]; + + if ($dom == true) + $css .= ' '.$cssobj[1].'='.$value; + else + $css .= $cssobj[1].':'.$value.'; '; + } + } + return $css; +} +# +function getdom($domname) +{ + return getcss($domname, true); +} +# +# N.B. don't call this before calling htmlhead() +function php_pr($cmd) +{ + global $here, $autorefresh; + + return "$here?ref=$autorefresh$cmd"; +} +# +function htmlhead($mcerr, $checkapi, $rig, $pg = null, $noscript = false) +{ + global $doctype, $title, $miner_font_family, $miner_font_size; + global $bad_font_family, $bad_font_size, $add_css_names; + global $error, $readonly, $poolinputs, $here; + global $ignorerefresh, $autorefresh; + + $extraparams = ''; + if ($rig != null && $rig != '') + $extraparams = "&rig=$rig"; + else + if ($pg != null && $pg != '') + $extraparams = "&pg=$pg"; + + if ($ignorerefresh == true || $autorefresh == 0) + $refreshmeta = ''; + else + { + $url = "$here?ref=$autorefresh$extraparams"; + $refreshmeta = "\n"; + } + + if ($readonly === false && $checkapi === true) + { + $error = null; + $access = api($rig, 'privileged'); + if ($error != null + || !isset($access['STATUS']['STATUS']) + || $access['STATUS']['STATUS'] != 'S') + $readonly = true; + } + $miner_font = "font-family:$miner_font_family; font-size:$miner_font_size;"; + $bad_font = "font-family:$bad_font_family; font-size:$bad_font_size;"; + + echo "$doctype$refreshmeta +$title + +\n"; +if ($noscript === false) +{ +echo "\n"; +} +?> + +
+ + 0); + do + { + $mcast_soc = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); + if ($mcast_soc === false || $mcast_soc == null) + { + $msg = "ERR: mcast send socket create(UDP) failed"; + if ($rigipsecurity === false) + { + $error = socket_strerror(socket_last_error()); + $error = "$msg '$error'\n"; + } + else + $error = "$msg\n"; + + socket_close($rep_soc); + return; + } + + $buf = "cgminer-$mcastcode-$mcastlistport"; + socket_sendto($mcast_soc, $buf, strlen($buf), 0, $mcastaddr, $mcastport); + socket_close($mcast_soc); + + $stt = microtime(true); + while (true) + { + $got = @socket_recvfrom($rep_soc, $buf, 32, MSG_DONTWAIT, $ip, $p); + if ($got !== false && $got > 0) + { + $ans = explode('-', $buf, 4); + if (count($ans) >= 3 && $ans[0] == 'cgm' && $ans[1] == 'FTW') + { + $rp = intval($ans[2]); + + if (count($ans) > 3) + $mdes = str_replace("\0", '', $ans[3]); + else + $mdes = ''; + + if (strlen($mdes) > 0) + $rig = "$ip:$rp:$mdes"; + else + $rig = "$ip:$rp"; + + if (!in_array($rig, $rigs)) + $rigs[] = $rig; + } + } + if ((microtime(true) - $stt) >= $mcasttimeout) + break; + + usleep(100000); + } + + if ($mcastexpect > 0 && count($rigs) >= $mcastexpect) + $doretry = false; + + } while ($doretry && --$retries > 0); + + socket_close($rep_soc); +} +# +function getrigs() +{ + global $rigs; + + mcastrigs(); + + sort($rigs); +} +# +function getsock($rig, $addr, $port) +{ + global $rigport, $rigips, $rignames, $rigipsecurity; + global $haderror, $error, $socksndtimeoutsec, $sockrcvtimeoutsec; + + $port = trim($port); + if (strlen($port) == 0) + $port = $rigport; + $error = null; + $socket = null; + $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); + if ($socket === false || $socket === null) + { + $haderror = true; + if ($rigipsecurity === false) + { + $error = socket_strerror(socket_last_error()); + $msg = "socket create(TCP) failed"; + $error = "ERR: $msg '$error'\n"; + } + else + $error = "ERR: socket create(TCP) failed\n"; + + return null; + } + + // Ignore if this fails since the socket connect may work anyway + // and nothing is gained by aborting if the option cannot be set + // since we don't know in advance if it can connect + socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, array('sec' => $socksndtimeoutsec, 'usec' => 0)); + socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, array('sec' => $sockrcvtimeoutsec, 'usec' => 0)); + + $res = socket_connect($socket, $addr, $port); + if ($res === false) + { + $haderror = true; + if ($rigipsecurity === false) + { + $error = socket_strerror(socket_last_error()); + $msg = "socket connect($addr,$port) failed"; + $error = "ERR: $msg '$error'\n"; + } + else + $error = "ERR: socket connect($rig) failed\n"; + + socket_close($socket); + return null; + } + if ($rignames !== false && !isset($rigips[$addr])) + if (socket_getpeername($socket, $ip) == true) + $rigips[$addr] = $ip; + return $socket; +} +# +function readsockline($socket) +{ + $line = ''; + while (true) + { + $byte = socket_read($socket, 1); + if ($byte === false || $byte === '') + break; + if ($byte === "\0") + break; + $line .= $byte; + } + return $line; +} +# +function api_convert_escape($str) +{ + $res = ''; + $len = strlen($str); + for ($i = 0; $i < $len; $i++) + { + $ch = substr($str, $i, 1); + if ($ch != '\\' || $i == ($len-1)) + $res .= $ch; + else + { + $i++; + $ch = substr($str, $i, 1); + switch ($ch) + { + case '|': + $res .= "\1"; + break; + case '\\': + $res .= "\2"; + break; + case '=': + $res .= "\3"; + break; + case ',': + $res .= "\4"; + break; + default: + $res .= $ch; + } + } + } + return $res; +} +# +function revert($str) +{ + return str_replace(array("\1", "\2", "\3", "\4"), array("|", "\\", "=", ","), $str); +} +# +function api($rig, $cmd) +{ + global $haderror, $error; + global $miner, $port, $hidefields; + + $socket = getsock($rig, $miner, $port); + if ($socket != null) + { + socket_write($socket, $cmd, strlen($cmd)); + $line = readsockline($socket); + socket_close($socket); + + if (strlen($line) == 0) + { + $haderror = true; + $error = "WARN: '$cmd' returned nothing\n"; + return $line; + } + +# print "$cmd returned '$line'\n"; + + $line = api_convert_escape($line); + + $data = array(); + + $objs = explode('|', $line); + foreach ($objs as $obj) + { + if (strlen($obj) > 0) + { + $items = explode(',', $obj); + $item = $items[0]; + $id = explode('=', $items[0], 2); + if (count($id) == 1 or !ctype_digit($id[1])) + $name = $id[0]; + else + $name = $id[0].$id[1]; + + if (strlen($name) == 0) + $name = 'null'; + + $sectionname = preg_replace('/\d/', '', $name); + + if (isset($data[$name])) + { + $num = 1; + while (isset($data[$name.$num])) + $num++; + $name .= $num; + } + + $counter = 0; + foreach ($items as $item) + { + $id = explode('=', $item, 2); + + if (isset($hidefields[$sectionname.'.'.$id[0]])) + continue; + + if (count($id) == 2) + $data[$name][$id[0]] = revert($id[1]); + else + $data[$name][$counter] = $id[0]; + + $counter++; + } + } + } + return $data; + } + return null; +} +# +function getparam($name, $both = false) +{ + $a = null; + if (isset($_POST[$name])) + $a = $_POST[$name]; + + if (($both === true) and ($a === null)) + { + if (isset($_GET[$name])) + $a = $_GET[$name]; + } + + if ($a == '' || $a == null) + return null; + + // limit to 1K just to be safe + return substr($a, 0, 1024); +} +# +function newtable() +{ + global $tablebegin, $rownum; + echo $tablebegin; + $rownum = 0; +} +# +function newrow() +{ + echo ''; +} +# +function othrow($row) +{ + return "$row"; +} +# +function otherrow($row) +{ + echo othrow($row); +} +# +function endrow() +{ + global $rownum; + echo ''; + $rownum++; +} +# +function endtable() +{ + global $tableend; + echo $tableend; +} +# +function classlastshare($when, $alldata, $warnclass, $errorclass) +{ + global $checklastshare; + + if ($checklastshare === false) + return ''; + + if ($when == 0) + return ''; + + if (!isset($alldata['MHS av'])) + return ''; + + if ($alldata['MHS av'] == 0) + return ''; + + if (!isset($alldata['Last Share Time'])) + return ''; + + if (!isset($alldata['Last Share Difficulty'])) + return ''; + + $expected = pow(2, 32) / ($alldata['MHS av'] * pow(10, 6)); + + // If the share difficulty changes while waiting on a share, + // this calculation will of course be incorrect + $expected *= $alldata['Last Share Difficulty']; + + $howlong = $when - $alldata['Last Share Time']; + if ($howlong < 1) + $howlong = 1; + + if ($howlong > ($expected * 12)) + return $errorclass; + + if ($howlong > ($expected * 8)) + return $warnclass; + + return ''; +} +# +function endzero($num) +{ + $rep = preg_replace('/0*$/', '', $num); + if ($rep === '') + $rep = '0'; + return $rep; +} +# +function fmt($section, $name, $value, $when, $alldata, $cf = NULL) +{ + global $dfmt, $rownum; + + if ($alldata == null) + $alldata = array(); + + $errorclass = 'err'; + $warnclass = 'warn'; + $lstclass = 'lst'; + $hiclass = 'hi'; + $loclass = 'lo'; + $c2class = 'two'; + $totclass = 'tot'; + $b = ' '; + + $class = ''; + + $nams = explode('.', $name); + if (count($nams) > 1) + $name = $nams[count($nams)-1]; + + $done = false; + if ($value === null) + { + $ret = $b; + $done = true; + } + else + if ($cf != NULL and function_exists($cf)) + { + list($ret, $class) = $cf($section, $name, $value, $when, $alldata, + $warnclass, $errorclass, $hiclass, $loclass, $totclass); + if ($ret !== '') + $done = true; + } + + if ($done === false) + { + $ret = $value; + /* + * To speed up the PHP, the case statement is just $name + * It used to be $section.'.'.$name + * If any names clash, the case code will need to check the value of + * $section to resolve the clash - as with 'Last Share Time' below + * If the code picks up a field that you wish to format differently, + * then you'll need a customsummarypage 'fmt' extension + */ + switch ($name) + { + case '0': + break; + case 'Last Share Time': + if ($section == 'total') + break; + if ($section == 'POOL') + { + if ($value == 0) + $ret = 'Never'; + else + $ret = date('H:i:s d-M', $value); + } + else + { + if ($value == 0 + || (isset($alldata['Last Share Pool']) && $alldata['Last Share Pool'] == -1)) + { + $ret = 'Never'; + $class = $warnclass; + } + else + { + $ret = date('H:i:s', $value); + $class = classlastshare($when, $alldata, $warnclass, $errorclass); + } + } + break; + case 'Last getwork': + case 'Last Valid Work': + if ($section == 'total') + break; + if ($value == 0) + $ret = 'Never'; + else + $ret = ($value - $when) . 's'; + break; + case 'Last Share Pool': + if ($section == 'total') + break; + if ($value == -1) + { + $ret = 'None'; + $class = $warnclass; + } + break; + case 'Elapsed': + case 'Device Elapsed': + if ($section == 'total') + break; + $s = $value % 60; + $value -= $s; + $value /= 60; + if ($value == 0) + $ret = $s.'s'; + else + { + $m = $value % 60; + $value -= $m; + $value /= 60; + if ($value == 0) + $ret = sprintf("%dm$b%02ds", $m, $s); + else + { + $h = $value % 24; + $value -= $h; + $value /= 24; + if ($value == 0) + $ret = sprintf("%dh$b%02dm$b%02ds", $h, $m, $s); + else + { + if ($value == 1) + $days = ''; + else + $days = 's'; + + $ret = sprintf("%dday$days$b%02dh$b%02dm$b%02ds", $value, $h, $m, $s); + } + } + } + break; + case 'Last Well': + if ($section == 'total') + break; + if ($value == '0') + { + $ret = 'Never'; + $class = $warnclass; + } + else + $ret = date('H:i:s', $value); + break; + case 'Last Not Well': + if ($section == 'total') + break; + if ($value == '0') + $ret = 'Never'; + else + { + $ret = date('H:i:s', $value); + $class = $errorclass; + } + break; + case 'Reason Not Well': + if ($section == 'total') + break; + if ($value != 'None') + $class = $errorclass; + break; + case 'Utility': + $ret = number_format($value, 2).'/m'; + if ($value == 0) + $class = $errorclass; + else + if (isset($alldata['Difficulty Accepted']) + && isset($alldata['Accepted']) + && isset($alldata['MHS av']) + && ($alldata['Difficulty Accepted'] > 0) + && ($alldata['Accepted'] > 0)) + { + $expected = 60 * $alldata['MHS av'] * (pow(10, 6) / pow(2, 32)); + if ($expected == 0) + $expected = 0.000001; // 1 H/s + + $da = $alldata['Difficulty Accepted']; + $a = $alldata['Accepted']; + $expected /= ($da / $a); + + $ratio = $value / $expected; + if ($ratio < 0.9) + $class = $loclass; + else + if ($ratio > 1.1) + $class = $hiclass; + } + break; + case 'Work Utility': + $ret = number_format($value, 2).'/m'; + break; + case 'Temperature': + if ($section == 'total') + break; + $ret = $value.'°C'; + if (!isset($alldata['GPU'])) + { + if ($value == 0) + $ret = ' '; + break; + } + case 'GPU Clock': + case 'Memory Clock': + case 'GPU Voltage': + case 'GPU Activity': + if ($section == 'total') + break; + if ($value == 0) + $class = $warnclass; + break; + case 'Fan Percent': + if ($section == 'total') + break; + if ($value == 0) + $class = $warnclass; + else + { + if ($value == 100) + $class = $errorclass; + else + if ($value > 85) + $class = $warnclass; + } + break; + case 'Fan Speed': + if ($section == 'total') + break; + if ($value == 0) + $class = $warnclass; + else + if (isset($alldata['Fan Percent'])) + { + $test = $alldata['Fan Percent']; + if ($test == 100) + $class = $errorclass; + else + if ($test > 85) + $class = $warnclass; + } + break; + case 'MHS av': + case 'MHS 5s': + case 'MHS 1m': + case 'MHS 5m': + case 'MHS 15m': + $parts = explode('.', $value, 2); + if (count($parts) == 1) + $dec = ''; + else + $dec = '.'.$parts[1]; + $ret = number_format((float)$parts[0]).$dec; + + if ($value == 0) + $class = $errorclass; + else + if (isset($alldata['Difficulty Accepted']) + && isset($alldata['Accepted']) + && isset($alldata['Utility']) + && ($alldata['Difficulty Accepted'] > 0) + && ($alldata['Accepted'] > 0)) + { + $expected = 60 * $value * (pow(10, 6) / pow(2, 32)); + if ($expected == 0) + $expected = 0.000001; // 1 H/s + + $da = $alldata['Difficulty Accepted']; + $a = $alldata['Accepted']; + $expected /= ($da / $a); + + $ratio = $alldata['Utility'] / $expected; + if ($ratio < 0.9) + $class = $hiclass; + else + if ($ratio > 1.1) + $class = $loclass; + } + break; + case 'Total MH': + case 'Getworks': + case 'Works': + case 'Accepted': + case 'Rejected': + case 'Local Work': + case 'Discarded': + case 'Diff1 Shares': + case 'Diff1 Work': + case 'Times Sent': + case 'Bytes Sent': + case 'Net Bytes Sent': + case 'Times Recv': + case 'Bytes Recv': + case 'Net Bytes Recv': + $parts = explode('.', $value, 2); + if (count($parts) == 1) + $dec = ''; + else + $dec = '.'.$parts[1]; + $ret = number_format((float)$parts[0]).$dec; + break; + case 'Hs': + case 'W': + case 'history_time': + case 'Pool Wait': + case 'Pool Max': + case 'Pool Min': + case 'Pool Av': + case 'Min Diff': + case 'Max Diff': + case 'Work Diff': + $parts = explode('.', $value, 2); + if (count($parts) == 1) + $dec = ''; + else + $dec = '.'.endzero($parts[1]); + $ret = number_format((float)$parts[0]).$dec; + break; + case 'Status': + if ($section == 'total') + break; + if ($value != 'Alive') + $class = $errorclass; + break; + case 'Enabled': + if ($section == 'total') + break; + if ($value != 'Y') + $class = $warnclass; + break; + case 'No Device': + if ($section == 'total') + break; + if ($value != 'false') + $class = $errorclass; + break; + case 'When': + case 'Current Block Time': + if ($section == 'total') + break; + $ret = date($dfmt, $value); + break; + case 'Last Share Difficulty': + if ($section == 'total') + break; + case 'Difficulty Accepted': + case 'Difficulty Rejected': + case 'Difficulty Stale': + if ($value != '') + $ret = number_format((float)$value, 2); + break; + case 'Device Hardware%': + case 'Device Rejected%': + case 'Pool Rejected%': + case 'Pool Stale%': + if ($section == 'total') + break; + if ($value != '') + $ret = number_format((float)$value, 2) . '%'; + break; + case 'Best Share': + if ($section == 'total') + break; + case 'Hardware Errors': + if ($value != '') + $ret = number_format((float)$value); + break; + // BUTTON. + case 'Rig': + case 'Pool': + case 'GPU': + break; + // Sample GEN fields + case 'Mined': + if ($value != '') + $ret = number_format((float)$value * 100.0, 3) . '%'; + break; + case 'Acc': + case 'Rej': + if ($value != '') + $ret = number_format((float)$value * 100.0, 2) . '%'; + break; + case 'GHS av': + case 'GHS 5m': + case 'GHS WU': + case 'GHS Acc': + if ($value != '') + $ret = number_format((float)$value, 2); + break; + case 'AvShr': + if ($section == 'total') + break; + if ($value != '') + $ret = number_format((float)$value, 2); + if ($value == 0) + $class = $warnclass; + break; + } + } + + if ($section == 'NOTIFY' && substr($name, 0, 1) == '*' && $value != '0') + $class = $errorclass; + + if ($class == '' && $section != 'POOL') + $class = classlastshare($when, $alldata, $lstclass, $lstclass); + + if ($class == '' && $section == 'total') + $class = $totclass; + + if ($class == '' && ($rownum % 2) == 0) + $class = $c2class; + + if ($ret === '') + $ret = $b; + + if ($class !== '') + $class = " class=$class"; + + return array($ret, $class); +} +# +global $poolcmd; +$poolcmd = array( 'Switch to' => 'switchpool', + 'Enable' => 'enablepool', + 'Disable' => 'disablepool', + 'Remove' => 'removepool' ); +# +function showhead($cmd, $values, $justnames = false) +{ + global $poolcmd, $readonly; + + newrow(); + + foreach ($values as $name => $value) + { + if ($name == '0' or $name == '') + $name = ' '; + echo ""; + } + + if ($justnames === false && $cmd == 'pools' && $readonly === false) + foreach ($poolcmd as $name => $pcmd) + echo ""; + + endrow(); +} +# +function showdatetime() +{ + global $dfmt; + + otherrow(''); +} +# +global $singlerigsum; +$singlerigsum = array( + 'devs' => array('MHS av' => 1, 'MHS 5s' => 1, 'MHS 1m' => 1, 'MHS 5m' => 1, + 'MHS 15m' => 1, 'Accepted' => 1, 'Rejected' => 1, + 'Hardware Errors' => 1, 'Utility' => 1, 'Total MH' => 1, + 'Diff1 Shares' => 1, 'Diff1 Work' => 1, + 'Difficulty Accepted' => 1, 'Difficulty Rejected' => 1), + 'pools' => array('Getworks' => 1, 'Accepted' => 1, 'Rejected' => 1, 'Discarded' => 1, + 'Stale' => 1, 'Get Failures' => 1, 'Remote Failures' => 1, + 'Diff1 Shares' => 1, 'Diff1 Work' => 1, + 'Difficulty Accepted' => 1, 'Difficulty Rejected' => 1, + 'Difficulty Stale' => 1), + 'notify' => array('*' => 1)); +# +function showtotal($total, $when, $oldvalues) +{ + global $rigtotals; + + list($showvalue, $class) = fmt('total', '', 'Total:', $when, null); + echo "$showvalue"; + + $skipfirst = true; + foreach ($oldvalues as $name => $value) + { + if ($skipfirst === true) + { + $skipfirst = false; + continue; + } + + if (isset($total[$name])) + $newvalue = $total[$name]; + else + $newvalue = ''; + + list($showvalue, $class) = fmt('total', $name, $newvalue, $when, null); + echo "$showvalue"; + } +} +# +function details($cmd, $list, $rig) +{ + global $dfmt, $poolcmd, $readonly, $showndate; + global $rownum, $rigtotals, $forcerigtotals, $singlerigsum; + + $when = 0; + + $stas = array('S' => 'Success', 'W' => 'Warning', 'I' => 'Informational', 'E' => 'Error', 'F' => 'Fatal'); + + newtable(); + + if ($showndate === false) + { + showdatetime(); + + endtable(); + newtable(); + + $showndate = true; + } + + if (isset($list['STATUS'])) + { + newrow(); + echo ''; + if (isset($list['STATUS']['When'])) + { + echo ''; + $when = $list['STATUS']['When']; + } + $sta = $list['STATUS']['STATUS']; + echo ''; + echo ''; + endrow(); + } + + if ($rigtotals === true && isset($singlerigsum[$cmd])) + $dototal = $singlerigsum[$cmd]; + else + $dototal = array(); + + $total = array(); + + $section = ''; + $oldvalues = null; + foreach ($list as $item => $values) + { + if ($item == 'STATUS') + continue; + + $sectionname = preg_replace('/\d/', '', $item); + + // Handle 'devs' possibly containing >1 table + if ($sectionname != $section) + { + if ($oldvalues != null && count($total) > 0 + && ($rownum > 2 || $forcerigtotals === true)) + showtotal($total, $when, $oldvalues); + + endtable(); + newtable(); + showhead($cmd, $values); + $section = $sectionname; + } + + newrow(); + + foreach ($values as $name => $value) + { + list($showvalue, $class) = fmt($section, $name, $value, $when, $values); + echo "$showvalue"; + + if (isset($dototal[$name]) + || (isset($dototal['*']) and substr($name, 0, 1) == '*')) + { + if (isset($total[$name])) + $total[$name] += $value; + else + $total[$name] = $value; + } + } + + if ($cmd == 'pools' && $readonly === false) + { + reset($values); + $pool = current($values); + foreach ($poolcmd as $name => $pcmd) + { + list($ignore, $class) = fmt('BUTTON', 'Pool', '', $when, $values); + echo ""; + if ($pool === false) + echo ' '; + else + { + echo ""; + } + echo ''; + } + } + endrow(); + + $oldvalues = $values; + } + + if ($oldvalues != null && count($total) > 0 + && ($rownum > 2 || $forcerigtotals === true)) + showtotal($total, $when, $oldvalues); + + endtable(); +} +# +global $devs; +$devs = null; +# +function gpubuttons($count, $rig) +{ + global $devs; + + $basic = array( 'GPU', 'Enable', 'Disable', 'Restart' ); + + $options = array( 'intensity' => 'Intensity', + 'fan' => 'Fan Percent', + 'engine' => 'GPU Clock', + 'mem' => 'Memory Clock', + 'vddc' => 'GPU Voltage' ); + + newtable(); + newrow(); + + foreach ($basic as $head) + echo ""; + + foreach ($options as $name => $des) + echo ""; + + $n = 0; + for ($c = 0; $c < $count; $c++) + { + endrow(); + newrow(); + + foreach ($basic as $name) + { + list($ignore, $class) = fmt('BUTTON', 'GPU', '', 0, null); + echo ""; + + if ($name == 'GPU') + echo $c; + else + { + echo ""; + } + + echo ''; + } + + foreach ($options as $name => $des) + { + list($ignore, $class) = fmt('BUTTON', 'GPU', '', 0, null); + echo ""; + + if (!isset($devs["GPU$c"][$des])) + echo ' '; + else + { + $value = $devs["GPU$c"][$des]; + echo ""; + echo ""; + $n++; + } + + echo ''; + } + + } + endrow(); + endtable(); +} +# +function processgpus($rig) +{ + global $error; + global $warnfont, $warnoff; + + $gpus = api($rig, 'gpucount'); + + if ($error != null) + otherrow(""); + else + { + if (!isset($gpus['GPUS']['Count'])) + { + $rw = ''; + otherrow($rw); + } + else + { + $count = $gpus['GPUS']['Count']; + if ($count == 0) + otherrow(''); + else + gpubuttons($count, $rig); + } + } +} +# +function showpoolinputs($rig, $ans) +{ + global $readonly, $poolinputs; + + if ($readonly === true || $poolinputs === false) + return; + + newtable(); + newrow(); + + $inps = array('Pool URL' => array('purl', 20), + 'Worker Name' => array('pwork', 10), + 'Worker Password' => array('ppass', 10)); + $b = ' '; + + echo ""; + + endrow(); + + if (count($ans) > 1) + { + newrow(); + + echo ''; + echo ""; + + endrow(); + } + endtable(); +} +# +function process($cmds, $rig) +{ + global $error, $devs; + global $warnfont, $warnoff; + + $count = count($cmds); + foreach ($cmds as $cmd => $des) + { + $process = api($rig, $cmd); + + if ($error != null) + { + otherrow(""); + break; + } + else + { + details($cmd, $process, $rig); + + if ($cmd == 'devs') + $devs = $process; + + if ($cmd == 'pools') + showpoolinputs($rig, $process); + + # Not after the last one + if (--$count > 0) + otherrow(''); + } + } +} +# +function rigname($rig, $rigname) +{ + global $rigs, $rignames, $rigips; + + if (isset($rigs[$rig])) + { + $parts = explode(':', $rigs[$rig], 3); + if (count($parts) == 3) + $rigname = $parts[2]; + else + if ($rignames !== false) + { + switch ($rignames) + { + case 'ip': + if (isset($parts[0]) && isset($rigips[$parts[0]])) + { + $ip = explode('.', $rigips[$parts[0]]); + if (count($ip) == 4) + $rigname = intval($ip[3]); + } + break; + case 'ipx': + if (isset($parts[0]) && isset($rigips[$parts[0]])) + { + $ip = explode('.', $rigips[$parts[0]]); + if (count($ip) == 4) + $rigname = intval($ip[3], 16); + } + break; + } + } + } + + return $rigname; +} +# +function riginput($rig, $rigname, $usebuttons) +{ + $rigname = rigname($rig, $rigname); + + if ($usebuttons === true) + return ""; + else + return "$rigname"; +} +# +function rigbutton($rig, $rigname, $when, $row, $usebuttons) +{ + list($value, $class) = fmt('BUTTON', 'Rig', '', $when, $row); + + if ($rig === '') + $ri = ' '; + else + $ri = riginput($rig, $rigname, $usebuttons); + + return ""; +} +# +function showrigs($anss, $headname, $rigname) +{ + global $rigbuttons; + + $dthead = array($headname => 1, 'STATUS' => 1, 'Description' => 1, 'When' => 1, 'API' => 1, 'CGMiner' => 1); + showhead('', $dthead); + + foreach ($anss as $rig => $ans) + { + if ($ans == null) + continue; + + newrow(); + + $when = 0; + if (isset($ans['STATUS']['When'])) + $when = $ans['STATUS']['When']; + + foreach ($ans as $item => $row) + { + if ($item != 'STATUS' && $item != 'VERSION') + continue; + + foreach ($dthead as $name => $x) + { + if ($item == 'STATUS' && $name == $headname) + echo rigbutton($rig, $rigname.$rig, $when, null, $rigbuttons); + else + { + if (isset($row[$name])) + { + list($showvalue, $class) = fmt('STATUS', $name, $row[$name], $when, null); + echo "$showvalue"; + } + } + } + } + endrow(); + } +} +# +function refreshbuttons() +{ + global $ignorerefresh, $changerefresh, $autorefresh; + + if ($ignorerefresh == false && $changerefresh == true) + { + echo '    '; + echo ""; + echo ""; + echo ""; + } +} +# +function pagebuttons($rig, $pg) +{ + global $readonly, $rigs, $rigbuttons, $userlist, $ses; + global $allowcustompages, $customsummarypages; + + if ($rig === null) + { + $prev = null; + $next = null; + + if ($pg === null) + $refresh = ''; + else + $refresh = "&pg=$pg"; + } + else + { + switch (count($rigs)) + { + case 0: + case 1: + $prev = null; + $next = null; + break; + case 2: + $prev = null; + $next = ($rig + 1) % count($rigs); + break; + default: + $prev = ($rig - 1) % count($rigs); + $next = ($rig + 1) % count($rigs); + break; + } + + $refresh = "&rig=$rig"; + } + + echo '"; +} +# +function doOne($rig, $preprocess) +{ + global $haderror, $readonly, $notify, $rigs; + global $placebuttons; + + if ($placebuttons == 'top' || $placebuttons == 'both') + pagebuttons($rig, null); + + if ($preprocess != null) + process(array($preprocess => $preprocess), $rig); + + $cmds = array( 'devs' => 'device list', + 'summary' => 'summary information', + 'pools' => 'pool list'); + + if ($notify) + $cmds['notify'] = 'device status'; + + $cmds['config'] = 'cgminer config'; + + process($cmds, $rig); + + if ($haderror == false && $readonly === false) + processgpus($rig); + + if ($placebuttons == 'bot' || $placebuttons == 'both') + pagebuttons($rig, null); +} +# +global $sectionmap; +# map sections to their api command +# DEVS is a special case that will match GPU, PGA or ASC +# so you can have a single table with both in it +# DATE is hard coded so not in here +$sectionmap = array( + 'RIGS' => 'version', + 'SUMMARY' => 'summary', + 'POOL' => 'pools', + 'DEVS' => 'devs', + 'EDEVS' => 'edevs', + 'GPU' => 'devs', // You would normally use DEVS + 'PGA' => 'devs', // You would normally use DEVS + 'ASC' => 'devs', // You would normally use DEVS + 'NOTIFY' => 'notify', + 'DEVDETAILS' => 'devdetails', + 'STATS' => 'stats', + 'ESTATS' => 'estats', + 'CONFIG' => 'config', + 'COIN' => 'coin', + 'USBSTATS' => 'usbstats'); +# +function joinfields($section1, $section2, $join, $results) +{ + global $sectionmap; + + $name1 = $sectionmap[$section1]; + $name2 = $sectionmap[$section2]; + $newres = array(); + + // foreach rig in section1 + foreach ($results[$name1] as $rig => $result) + { + $status = null; + + // foreach answer section in the rig api call + foreach ($result as $name1b => $fields1b) + { + if ($name1b == 'STATUS') + { + // remember the STATUS from section1 + $status = $result[$name1b]; + continue; + } + + // foreach answer section in the rig api call (for the other api command) + foreach ($results[$name2][$rig] as $name2b => $fields2b) + { + if ($name2b == 'STATUS') + continue; + + // If match the same field values of fields in $join + $match = true; + foreach ($join as $field) + if ($fields1b[$field] != $fields2b[$field]) + { + $match = false; + break; + } + + if ($match === true) + { + if ($status != null) + { + $newres[$rig]['STATUS'] = $status; + $status = null; + } + + $subsection = $section1.'+'.$section2; + $subsection .= preg_replace('/[^0-9]/', '', $name1b.$name2b); + + foreach ($fields1b as $nam => $val) + $newres[$rig][$subsection]["$section1.$nam"] = $val; + foreach ($fields2b as $nam => $val) + $newres[$rig][$subsection]["$section2.$nam"] = $val; + } + } + } + } + return $newres; +} +# +function joinlr($section1, $section2, $join, $results) +{ + global $sectionmap; + + $name1 = $sectionmap[$section1]; + $name2 = $sectionmap[$section2]; + $newres = array(); + + // foreach rig in section1 + foreach ($results[$name1] as $rig => $result) + { + $status = null; + + // foreach answer section in the rig api call + foreach ($result as $name1b => $fields1b) + { + if ($name1b == 'STATUS') + { + // remember the STATUS from section1 + $status = $result[$name1b]; + continue; + } + + // Build L string to be matched + // : means a string constant otherwise it's a field name + $Lval = ''; + foreach ($join['L'] as $field) + { + if (substr($field, 0, 1) == ':') + $Lval .= substr($field, 1); + else + $Lval .= $fields1b[$field]; + } + + // foreach answer section in the rig api call (for the other api command) + foreach ($results[$name2][$rig] as $name2b => $fields2b) + { + if ($name2b == 'STATUS') + continue; + + // Build R string and compare + // : means a string constant otherwise it's a field name + $Rval = ''; + foreach ($join['R'] as $field) + { + if (substr($field, 0, 1) == ':') + $Rval .= substr($field, 1); + else + $Rval .= $fields2b[$field]; + } + + if ($Lval === $Rval) + { + if ($status != null) + { + $newres[$rig]['STATUS'] = $status; + $status = null; + } + + $subsection = $section1.'+'.$section2; + $subsection .= preg_replace('/[^0-9]/', '', $name1b.$name2b); + + foreach ($fields1b as $nam => $val) + $newres[$rig][$subsection]["$section1.$nam"] = $val; + foreach ($fields2b as $nam => $val) + $newres[$rig][$subsection]["$section2.$nam"] = $val; + } + } + } + } + return $newres; +} +# +function joinall($section1, $section2, $results) +{ + global $sectionmap; + + $name1 = $sectionmap[$section1]; + $name2 = $sectionmap[$section2]; + $newres = array(); + + // foreach rig in section1 + foreach ($results[$name1] as $rig => $result) + { + // foreach answer section in the rig api call + foreach ($result as $name1b => $fields1b) + { + if ($name1b == 'STATUS') + { + // copy the STATUS from section1 + $newres[$rig][$name1b] = $result[$name1b]; + continue; + } + + // foreach answer section in the rig api call (for the other api command) + foreach ($results[$name2][$rig] as $name2b => $fields2b) + { + if ($name2b == 'STATUS') + continue; + + $subsection = $section1.'+'.$section2; + $subsection .= preg_replace('/[^0-9]/', '', $name1b.$name2b); + + foreach ($fields1b as $nam => $val) + $newres[$rig][$subsection]["$section1.$nam"] = $val; + foreach ($fields2b as $nam => $val) + $newres[$rig][$subsection]["$section2.$nam"] = $val; + } + } + } + return $newres; +} +# +function joinsections($sections, $results, $errors) +{ + global $sectionmap; + + // GPU's don't have Name,ID fields - so create them + foreach ($results as $section => $res) + foreach ($res as $rig => $result) + foreach ($result as $name => $fields) + { + $subname = preg_replace('/[0-9]/', '', $name); + if ($subname == 'GPU' and isset($result[$name]['GPU'])) + { + $results[$section][$rig][$name]['Name'] = 'GPU'; + $results[$section][$rig][$name]['ID'] = $result[$name]['GPU']; + } + } + + foreach ($sections as $section => $fields) + if ($section != 'DATE' && !isset($sectionmap[$section])) + { + $both = explode('+', $section, 2); + if (count($both) > 1) + { + switch($both[0]) + { + case 'SUMMARY': + switch($both[1]) + { + case 'POOL': + case 'DEVS': + case 'EDEVS': + case 'CONFIG': + case 'COIN': + $sectionmap[$section] = $section; + $results[$section] = joinall($both[0], $both[1], $results); + break; + default: + $errors[] = "Error: Invalid section '$section'"; + break; + } + break; + case 'DEVS': + case 'EDEVS': + switch($both[1]) + { + case 'NOTIFY': + case 'DEVDETAILS': + case 'USBSTATS': + $join = array('Name', 'ID'); + $sectionmap[$section] = $section; + $results[$section] = joinfields($both[0], $both[1], $join, $results); + break; + case 'STATS': + case 'ESTATS': + $join = array('L' => array('Name','ID'), 'R' => array('ID')); + $sectionmap[$section] = $section; + $results[$section] = joinlr($both[0], $both[1], $join, $results); + break; + default: + $errors[] = "Error: Invalid section '$section'"; + break; + } + break; + case 'POOL': + switch($both[1]) + { + case 'STATS': + $join = array('L' => array(':POOL','POOL'), 'R' => array('ID')); + $sectionmap[$section] = $section; + $results[$section] = joinlr($both[0], $both[1], $join, $results); + break; + default: + $errors[] = "Error: Invalid section '$section'"; + break; + } + break; + default: + $errors[] = "Error: Invalid section '$section'"; + break; + } + } + else + $errors[] = "Error: Invalid section '$section'"; + } + + return array($results, $errors); +} +# +function secmatch($section, $field) +{ + if ($section == $field) + return true; + + if (($section == 'DEVS' || $section == 'EDEVS') + && ($field == 'GPU' || $field == 'PGA' || $field == 'ASC')) + return true; + + return false; +} +# +function customset($showfields, $sum, $section, $rig, $isbutton, $result, $total, $cf = NULL) +{ + global $rigbuttons; + + $rn = 0; + foreach ($result as $sec => $row) + { + $secname = preg_replace('/\d/', '', $sec); + + if ($sec != 'total') + if (!secmatch($section, $secname)) + continue; + + newrow(); + + $when = 0; + if (isset($result['STATUS']['When'])) + $when = $result['STATUS']['When']; + + + if ($isbutton) + echo rigbutton($rig, $rig, $when, $row, $rigbuttons); + else + { + list($ignore, $class) = fmt('total', '', '', $when, $row, $cf); + echo ""; + } + + foreach ($showfields as $name => $one) + { + if ($name === '#' and $sec != 'total') + { + $rn++; + $value = $rn; + if (isset($total[$name])) + $total[$name]++; + else + $total[$name] = 1; + } + elseif (isset($row[$name])) + { + $value = $row[$name]; + + if (isset($sum[$section][$name])) + { + if (isset($total[$name])) + $total[$name] += $value; + else + $total[$name] = $value; + } + } + else + { + if ($sec == 'total' && isset($total[$name])) + $value = $total[$name]; + else + $value = null; + } + + if (strpos($secname, '+') === false) + list($showvalue, $class) = fmt($secname, $name, $value, $when, $row, $cf); + else + { + if ($name != '#') + $parts = explode('.', $name, 2); + else + $parts[0] = $parts[1] = '#'; + list($showvalue, $class) = fmt($parts[0], $parts[1], $value, $when, $row, $cf); + } + + echo "$showvalue"; + } + endrow(); + } + return $total; +} +# +function docalc($func, $data) +{ + switch ($func) + { + case 'sum': + $tot = 0; + foreach ($data as $val) + $tot += $val; + return $tot; + case 'avg': + $tot = 0; + foreach ($data as $val) + $tot += $val; + return ($tot / count($data)); + case 'min': + $ans = null; + foreach ($data as $val) + if ($ans === null) + $ans = $val; + else + if ($val < $ans) + $ans = $val; + return $ans; + case 'max': + $ans = null; + foreach ($data as $val) + if ($ans === null) + $ans = $val; + else + if ($val > $ans) + $ans = $val; + return $ans; + case 'lo': + $ans = null; + foreach ($data as $val) + if ($ans === null) + $ans = $val; + else + if (strcasecmp($val, $ans) < 0) + $ans = $val; + return $ans; + case 'hi': + $ans = null; + foreach ($data as $val) + if ($ans === null) + $ans = $val; + else + if (strcasecmp($val, $ans) > 0) + $ans = $val; + return $ans; + case 'count': + return count($data); + case 'any': + default: + return $data[0]; + } +} +# +function docompare($row, $test) +{ + // invalid $test data means true + if (count($test) < 2) + return true; + + if (isset($row[$test[0]])) + $val = $row[$test[0]]; + else + $val = null; + + if ($test[1] == 'set') + return ($val !== null); + + if ($val === null || count($test) < 3) + return true; + + switch($test[1]) + { + case '=': + return ($val == $test[2]); + case '<': + return ($val < $test[2]); + case '<=': + return ($val <= $test[2]); + case '>': + return ($val > $test[2]); + case '>=': + return ($val >= $test[2]); + case 'eq': + return (strcasecmp($val, $test[2]) == 0); + case 'lt': + return (strcasecmp($val, $test[2]) < 0); + case 'le': + return (strcasecmp($val, $test[2]) <= 0); + case 'gt': + return (strcasecmp($val, $test[2]) > 0); + case 'ge': + return (strcasecmp($val, $test[2]) >= 0); + default: + return true; + } +} +# +function processcompare($which, $ext, $section, $res) +{ + if (isset($ext[$section][$which])) + { + $proc = $ext[$section][$which]; + if ($proc !== null) + { + $res2 = array(); + foreach ($res as $rig => $result) + foreach ($result as $sec => $row) + { + $secname = preg_replace('/\d/', '', $sec); + if (!secmatch($section, $secname)) + $res2[$rig][$sec] = $row; + else + { + $keep = true; + foreach ($proc as $test) + if (!docompare($row, $test)) + { + $keep = false; + break; + } + if ($keep) + $res2[$rig][$sec] = $row; + } + } + + $res = $res2; + } + } + return $res; +} +# +function ss($a, $b) +{ + $la = strlen($a); + $lb = strlen($b); + if ($la != $lb) + return $lb - $la; + return strcmp($a, $b); +} +# +# If you are developing a customsummarypage that uses BGEN or GEN, +# you may want to remove the '@' in front of '@eval()' to help with debugging +# The '@' removes php comments from the web log about missing fields +# Since there are many forks of cgminer that break the API or do not +# keep their fork up to date with current cgminer, the addition of +# '@' solves the problem of generating unnecessary and excessive web logs +# about the eval() +function genfld($row, $calc) +{ + uksort($row, "ss"); + + foreach ($row as $name => $value) + if (strstr($calc, $name) !== FALSE) + $calc = str_replace($name, $value, $calc); + + @eval("\$val = $calc;"); + + if (!isset($val)) + return ''; + else + return $val; +} +# +function dogen($ext, $wg, $gname, $section, &$res, &$fields) +{ + $gen = $ext[$section][$wg]; + + foreach ($gen as $fld => $calc) + $fields[] = "$gname.$fld"; + + foreach ($res as $rig => $result) + foreach ($result as $sec => $row) + { + $secname = preg_replace('/\d/', '', $sec); + if (secmatch($section, $secname)) + foreach ($gen as $fld => $calc) + { + $name = "$gname.$fld"; + + $val = genfld($row, $calc); + + $res[$rig][$sec][$name] = $val; + } + } +} +# +function processext($ext, $section, $res, &$fields) +{ + global $allowgen; + + $res = processcompare('where', $ext, $section, $res); + + // Generated fields (functions of other fields before grouping) + if ($allowgen === true && isset($ext[$section]['bgen'])) + dogen($ext, 'bgen', 'BGEN', $section, $res, $fields); + + if (isset($ext[$section]['group'])) + { + $grp = $ext[$section]['group']; + $calc = $ext[$section]['calc']; + if ($grp !== null) + { + $interim = array(); + $res2 = array(); + $cou = 0; + foreach ($res as $rig => $result) + foreach ($result as $sec => $row) + { + $secname = preg_replace('/\d/', '', $sec); + if (!secmatch($section, $secname)) + { + // STATUS may be problematic ... + if (!isset($res2[$sec])) + $res2[$sec] = $row; + } + else + { + $grpkey = ''; + $newrow = array(); + foreach ($grp as $field) + { + if (isset($row[$field])) + { + $grpkey .= $row[$field].'.'; + $newrow[$field] = $row[$field]; + } + else + $grpkey .= '.'; + } + + if (!isset($interim[$grpkey])) + { + $interim[$grpkey]['grp'] = $newrow; + $interim[$grpkey]['sec'] = $secname.$cou; + $cou++; + } + + if ($calc !== null) + foreach ($calc as $field => $func) + { + if (isset($row[$field])) + { + if (!isset($interim[$grpkey]['cal'][$field])) + $interim[$grpkey]['cal'][$field] = array(); + $interim[$grpkey]['cal'][$field][] = $row[$field]; + } + } + } + } + + // Build the rest of $res2 from $interim + foreach ($interim as $rowkey => $row) + { + $key = $row['sec']; + foreach ($row['grp'] as $field => $value) + $res2[$key][$field] = $value; + foreach ($row['cal'] as $field => $data) + $res2[$key][$field] = docalc($calc[$field], $data); + } + + $res = array('' => $res2); + } + } + + // Generated fields (functions of other fields after grouping) + if ($allowgen === true && isset($ext[$section]['gen'])) + dogen($ext, 'gen', 'GEN', $section, $res, $fields); + + return processcompare('having', $ext, $section, $res); +} +# +function processcustompage($pagename, $sections, $sum, $ext, $namemap) +{ + global $sectionmap; + global $miner, $port; + global $rigs, $error; + global $warnfont, $warnoff; + global $dfmt; + global $readonly, $showndate; + + $cmds = array(); + $errors = array(); + foreach ($sections as $section => $fields) + { + $all = explode('+', $section); + foreach ($all as $section) + { + if (isset($sectionmap[$section])) + { + $cmd = $sectionmap[$section]; + if (!isset($cmds[$cmd])) + $cmds[$cmd] = 1; + } + else + if ($section != 'DATE') + $errors[] = "Error: unknown section '$section' in custom summary page '$pagename'"; + } + } + + $results = array(); + foreach ($rigs as $num => $rig) + { + $parts = explode(':', $rig, 3); + if (count($parts) >= 1) + { + $miner = $parts[0]; + if (count($parts) >= 2) + $port = $parts[1]; + else + $port = ''; + + if (count($parts) > 2) + $name = $parts[2]; + else + $name = $rig; + + foreach ($cmds as $cmd => $one) + { + $process = api($name, $cmd); + + if ($error != null) + { + $errors[] = "Error getting $cmd for $name $warnfont$error$warnoff"; + break; + } + else + $results[$cmd][$num] = $process; + } + } + else + otherrow(''); + } + + // Show API errors at the top + if (count($errors) > 0) + { + foreach ($errors as $err) + otherrow(""); + $errors = array(); + } + + $shownsomething = false; + if (count($results) > 0) + { + list($results, $errors) = joinsections($sections, $results, $errors); + $first = true; + foreach ($sections as $section => $fields) + { + if ($section === 'DATE') + { + if ($shownsomething) + otherrow(''); + + newtable(); + showdatetime(); + endtable(); + // On top of the next table + $shownsomething = false; + continue; + } + + if ($section === 'RIGS') + { + if ($shownsomething) + otherrow(''); + + newtable(); + showrigs($results['version'], 'Rig', ''); + endtable(); + $shownsomething = true; + continue; + } + + if (isset($results[$sectionmap[$section]])) + { + if (isset($ext[$section]['fmt'])) + $cf = $ext[$section]['fmt']; + else + $cf = NULL; + + $rigresults = processext($ext, $section, $results[$sectionmap[$section]], $fields); + + $showfields = array(); + $showhead = array(); + foreach ($fields as $field) + foreach ($rigresults as $result) + foreach ($result as $sec => $row) + { + $secname = preg_replace('/\d/', '', $sec); + if (secmatch($section, $secname)) + { + if ($field === '*') + { + foreach ($row as $f => $v) + { + $showfields[$f] = 1; + $map = $section.'.'.$f; + if (isset($namemap[$map])) + $showhead[$namemap[$map]] = 1; + else + $showhead[$f] = 1; + } + } + elseif ($field === '#') + { + $showfields[$field] = 1; + $showhead[$field] = 1; + } + elseif (isset($row[$field])) + { + $showfields[$field] = 1; + $map = $section.'.'.$field; + if (isset($namemap[$map])) + $showhead[$namemap[$map]] = 1; + else + $showhead[$field] = 1; + } + } + } + + if (count($showfields) > 0) + { + if ($shownsomething) + otherrow(''); + + newtable(); + if (count($rigresults) == 1 && isset($rigresults[''])) + $ri = array('' => 1) + $showhead; + else + $ri = array('Rig' => 1) + $showhead; + showhead('', $ri, true); + + $total = array(); + $add = array('total' => array()); + + foreach ($rigresults as $num => $result) + $total = customset($showfields, $sum, $section, $num, true, $result, $total, $cf); + + if (count($total) > 0) + customset($showfields, $sum, $section, 'Σ', false, $add, $total, $cf); + + $first = false; + + endtable(); + $shownsomething = true; + } + } + } + } + + if (count($errors) > 0) + { + if (count($results) > 0) + otherrow(''); + + foreach ($errors as $err) + otherrow(""); + } +} +# +function showcustompage($pagename, $systempage = false) +{ + global $customsummarypages; + global $placebuttons; + + if ($placebuttons == 'top' || $placebuttons == 'both') + pagebuttons(null, $pagename); + + if ($systempage === false && !isset($customsummarypages[$pagename])) + { + otherrow(""); + return; + } + + $csp = getcsp($pagename, $systempage); + if ($csp === false) + { + otherrow(""); + return; + } + + degen($csp); + + $page = $csp[0]; + $namemap = array(); + foreach ($page as $name => $fields) + { + if ($fields === null) + $page[$name] = array(); + else + foreach ($fields as $num => $field) + { + $pos = strpos($field, '='); + if ($pos !== false) + { + $names = explode('=', $field, 2); + if (strlen($names[1]) > 0) + $namemap[$name.'.'.$names[0]] = $names[1]; + $page[$name][$num] = $names[0]; + } + } + } + + $ext = null; + if (isset($csp[2])) + $ext = $csp[2]; + + $sum = $csp[1]; + if ($sum === null) + $sum = array(); + + // convert them to searchable via isset() + foreach ($sum as $section => $fields) + { + $newfields = array(); + foreach ($fields as $field) + $newfields[$field] = 1; + $sum[$section] = $newfields; + } + + if (count($page) <= 1) + { + otherrow(""); + return; + } + + processcustompage($pagename, $page, $sum, $ext, $namemap); + + if ($placebuttons == 'bot' || $placebuttons == 'both') + pagebuttons(null, $pagename); +} +# +function onlylogin() +{ + global $here; + + htmlhead('', false, null, null, true); + +?> + + +No rigs $action"); + return; + } + else + { + if ($mcast === true && count($rigs) < $mcastexpect) + $mcerr = othrow('"); + } + + if ($ignorerefresh == false) + { + $ref = trim(getparam('ref', true)); + if ($ref != null && $ref != '') + $autorefresh = intval($ref); + } + + if ($pagesonly !== true) + { + $rig = trim(getparam('rig', true)); + + $arg = trim(getparam('arg', true)); + $preprocess = null; + if ($arg != null and $arg != '') + { + if ($rig != null and $rig != '' and $rig >= 0 and $rig < count($rigs)) + { + $parts = explode(':', $rigs[$rig], 3); + if (count($parts) >= 1) + { + $miner = $parts[0]; + if (count($parts) >= 2) + $port = $parts[1]; + else + $port = ''; + + if ($readonly !== true) + $preprocess = $arg; + } + } + } + } + + if ($allowcustompages === true) + { + $pg = urlencode(trim(getparam('pg', true))); + if ($pagesonly === true) + { + if ($pg !== null && $pg !== '') + { + if ($userlist !== null && isset($userlist['def']) + && !in_array($pg, $userlist['def'])) + $pg = null; + } + else + { + if ($userlist !== null && isset($userlist['def'])) + foreach ($userlist['def'] as $pglook) + if (getcsp($pglook) !== false) + { + $pg = $pglook; + break; + } + } + } + + if ($pg !== null && $pg !== '') + { + htmlhead($mcerr, false, null, $pg); + showcustompage($pg); + return; + } + } + + if ($pagesonly === true) + { + onlylogin(); + return; + } + + if (count($rigs) == 1) + { + $parts = explode(':', $rigs[0], 3); + if (count($parts) >= 1) + { + $miner = $parts[0]; + if (count($parts) >= 2) + $port = $parts[1]; + else + $port = ''; + + htmlhead($mcerr, true, 0); + doOne(0, $preprocess); + } + else + { + minhead($mcerr); + otherrow(''); + } + + return; + } + + if ($rig != null and $rig != '' and $rig >= 0 and $rig < count($rigs)) + { + $parts = explode(':', $rigs[$rig], 3); + if (count($parts) >= 1) + { + $miner = $parts[0]; + if (count($parts) >= 2) + $port = $parts[1]; + else + $port = ''; + + htmlhead($mcerr, true, 0); + doOne($rig, $preprocess); + } + else + { + minhead($mcerr); + otherrow(''); + } + + return; + } + + htmlhead($mcerr, false, null); + + if ($preprocess != null) + process(array($preprocess => $preprocess), $rig); + + if (getcsp('Summary', true) !== false) + showcustompage('Summary', true); +} +# +if ($mcast === true) + getrigs(); +display(); +# +?> +
$name$nameDate: '.date($dfmt).'Computer: '.$list['STATUS']['Description'].'When: '.date($dfmt, $list['STATUS']['When']).'Status: '.$stas[$sta].'Message: '.$list['STATUS']['Msg'].'$head$desError getting GPU count: $warnfont$error$warnoffNo GPU count returned: '.$warnfont; + $rw .= $gpus['STATUS']['STATUS'].' '.$gpus['STATUS']['Msg']; + $rw .= $warnoff.'No GPUs Add a pool: "; + + foreach ($inps as $text => $name) + echo "$text: "; + + echo " Set pool priorities: Comma list of pool numbers: "; + echo "Error getting $des: $warnfont$error$warnoff

$ri
'; + if ($userlist === null || isset($_SESSION[$ses])) + { + if ($prev !== null) + echo riginput($prev, 'Prev', true).' '; + + echo " "; + + if ($next !== null) + echo riginput($next, 'Next', true).' '; + echo ' '; + if (count($rigs) > 1 and getcsp('Summary', true) !== false) + echo " "; + } + + if ($allowcustompages === true) + { + if ($userlist === null || isset($_SESSION[$ses])) + $list = $customsummarypages; + else + { + if ($userlist !== null && isset($userlist['def'])) + $list = array_flip($userlist['def']); + else + $list = array(); + } + + foreach ($list as $pagename => $data) + if (getcsp($pagename) !== false) + echo " "; + } + + echo ' '; + if ($rig !== null && $readonly === false) + { + $rg = ''; + if (count($rigs) > 1) + $rg = " Rig $rig"; + echo ""; + echo " "; + } + refreshbuttons(); + if (isset($_SESSION[$ses])) + echo " "; + else + if ($userlist !== null) + echo " "; + + echo "
$rigBad "$rigs" array$err    $errUnknown custom summary page '$pagename'Invalid custom summary page '$pagename'Invalid custom summary page '$pagename' no content
 
+
+ + +
+ + +
+ + + + + + + + +
 
+

LOGIN

Username:
+
Password:
+
+
+
Found '.count($rigs)." rigs but expected at least $mcastexpectInvalid "$rigs" arrayInvalid "$rigs" array
+ diff --git a/mknsis.sh b/mknsis.sh new file mode 100644 index 0000000..cc97dbe --- /dev/null +++ b/mknsis.sh @@ -0,0 +1,34 @@ +#!/bin/sh + +MINGW_PATH=/usr/i686-pc-mingw32/sys-root/mingw/bin + +OUT_BASE="cpuminer-installer" +OUT_EXE="$OUT_BASE.exe" + +PATH=$PATH:$MINGW_PATH \ + nsiswrapper --run \ + --name "CPU miner" \ + --outfile "$OUT_EXE" \ + minerd.exe \ + $MINGW_PATH/libcurl-4.dll=libcurl-4.dll \ + $MINGW_PATH/pthreadgc2.dll=pthreadgc2.dll \ + $MINGW_PATH/libidn-11.dll=libidn-11.dll \ + $MINGW_PATH/libssh2-1.dll=libssh2-1.dll \ + $MINGW_PATH/libssl-10.dll=libssl-10.dll \ + $MINGW_PATH/zlib1.dll=zlib1.dll \ + $MINGW_PATH/libcrypto-10.dll=libcrypto-10.dll \ + $MINGW_PATH/libiconv-2.dll=libiconv-2.dll \ + $MINGW_PATH/libintl-8.dll=libintl-8.dll + +chmod 0755 "$OUT_EXE" +zip -9 "$OUT_BASE" "$OUT_EXE" +rm -f "$OUT_EXE" + +chmod 0644 "$OUT_BASE.zip" + +echo -n "SHA1: " +sha1sum "$OUT_BASE.zip" + +echo -n "MD5: " +md5sum "$OUT_BASE.zip" + diff --git a/noncedup.c b/noncedup.c new file mode 100644 index 0000000..6b94fd6 --- /dev/null +++ b/noncedup.c @@ -0,0 +1,99 @@ +/* + * Copyright 2014 Andrew Smith + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#include "miner.h" +#include "klist.h" + +// Nonce +typedef struct nitem { + uint32_t work_id; + uint32_t nonce; + struct timeval when; +} NITEM; + +#define DATAN(_item) ((NITEM *)(_item->data)) + +struct dupdata { + int timelimit; + K_LIST *nfree_list; + K_STORE *nonce_list; + uint64_t checked; + uint64_t dups; +}; + +void dupalloc(struct cgpu_info *cgpu, int timelimit) +{ + struct dupdata *dup; + + dup = calloc(1, sizeof(*dup)); + if (unlikely(!dup)) + quithere(1, "Failed to calloc dupdata"); + + dup->timelimit = timelimit; + dup->nfree_list = k_new_list("Nonces", sizeof(NITEM), 1024, 0, true); + dup->nonce_list = k_new_store(dup->nfree_list); + + cgpu->dup_data = dup; +} + +void dupcounters(struct cgpu_info *cgpu, uint64_t *checked, uint64_t *dups) +{ + struct dupdata *dup = (struct dupdata *)(cgpu->dup_data); + + if (!dup) { + *checked = 0; + *dups = 0; + } else { + *checked = dup->checked; + *dups = dup->dups; + } +} + +bool isdupnonce(struct cgpu_info *cgpu, struct work *work, uint32_t nonce) +{ + struct dupdata *dup = (struct dupdata *)(cgpu->dup_data); + struct timeval now; + bool unique = true; + K_ITEM *item; + + if (!dup) + return false; + + cgtime(&now); + dup->checked++; + K_WLOCK(dup->nfree_list); + item = dup->nonce_list->tail; + while (unique && item) { + if (DATAN(item)->work_id == work->id && DATAN(item)->nonce == nonce) { + unique = false; + applog(LOG_WARNING, "%s%d: Duplicate nonce %08x", + cgpu->drv->name, cgpu->device_id, nonce); + } else + item = item->prev; + } + if (unique) { + item = k_unlink_head(dup->nfree_list); + DATAN(item)->work_id = work->id; + DATAN(item)->nonce = nonce; + memcpy(&(DATAN(item)->when), &now, sizeof(now)); + k_add_head(dup->nonce_list, item); + } + item = dup->nonce_list->tail; + while (item && tdiff(&(DATAN(item)->when), &now) > dup->timelimit) { + item = k_unlink_tail(dup->nonce_list); + k_add_head(dup->nfree_list, item); + item = dup->nonce_list->tail; + } + K_WUNLOCK(dup->nfree_list); + + if (!unique) + dup->dups++; + + return !unique; +} diff --git a/sha2.c b/sha2.c new file mode 100644 index 0000000..6777b28 --- /dev/null +++ b/sha2.c @@ -0,0 +1,208 @@ +/* + * FIPS 180-2 SHA-224/256/384/512 implementation + * Last update: 02/02/2007 + * Issue date: 04/30/2005 + * + * Copyright (C) 2013, Con Kolivas + * Copyright (C) 2005, 2007 Olivier Gay + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include + +#include "sha2.h" + +#define UNPACK32(x, str) \ +{ \ + *((str) + 3) = (uint8_t) ((x) ); \ + *((str) + 2) = (uint8_t) ((x) >> 8); \ + *((str) + 1) = (uint8_t) ((x) >> 16); \ + *((str) + 0) = (uint8_t) ((x) >> 24); \ +} + +#define PACK32(str, x) \ +{ \ + *(x) = ((uint32_t) *((str) + 3) ) \ + | ((uint32_t) *((str) + 2) << 8) \ + | ((uint32_t) *((str) + 1) << 16) \ + | ((uint32_t) *((str) + 0) << 24); \ +} + +#define SHA256_SCR(i) \ +{ \ + w[i] = SHA256_F4(w[i - 2]) + w[i - 7] \ + + SHA256_F3(w[i - 15]) + w[i - 16]; \ +} + +uint32_t sha256_h0[8] = + {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, + 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19}; + +uint32_t sha256_k[64] = + {0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, + 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, + 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, + 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2}; + +/* SHA-256 functions */ + +void sha256_transf(sha256_ctx *ctx, const unsigned char *message, + unsigned int block_nb) +{ + uint32_t w[64]; + uint32_t wv[8]; + uint32_t t1, t2; + const unsigned char *sub_block; + int i; + + int j; + + for (i = 0; i < (int) block_nb; i++) { + sub_block = message + (i << 6); + + for (j = 0; j < 16; j++) { + PACK32(&sub_block[j << 2], &w[j]); + } + + for (j = 16; j < 64; j++) { + SHA256_SCR(j); + } + + for (j = 0; j < 8; j++) { + wv[j] = ctx->h[j]; + } + + for (j = 0; j < 64; j++) { + t1 = wv[7] + SHA256_F2(wv[4]) + CH(wv[4], wv[5], wv[6]) + + sha256_k[j] + w[j]; + t2 = SHA256_F1(wv[0]) + MAJ(wv[0], wv[1], wv[2]); + wv[7] = wv[6]; + wv[6] = wv[5]; + wv[5] = wv[4]; + wv[4] = wv[3] + t1; + wv[3] = wv[2]; + wv[2] = wv[1]; + wv[1] = wv[0]; + wv[0] = t1 + t2; + } + + for (j = 0; j < 8; j++) { + ctx->h[j] += wv[j]; + } + } +} + +void sha256(const unsigned char *message, unsigned int len, unsigned char *digest) +{ + sha256_ctx ctx; + + sha256_init(&ctx); + sha256_update(&ctx, message, len); + sha256_final(&ctx, digest); +} + +void sha256_init(sha256_ctx *ctx) +{ + int i; + for (i = 0; i < 8; i++) { + ctx->h[i] = sha256_h0[i]; + } + + ctx->len = 0; + ctx->tot_len = 0; +} + +void sha256_update(sha256_ctx *ctx, const unsigned char *message, + unsigned int len) +{ + unsigned int block_nb; + unsigned int new_len, rem_len, tmp_len; + const unsigned char *shifted_message; + + tmp_len = SHA256_BLOCK_SIZE - ctx->len; + rem_len = len < tmp_len ? len : tmp_len; + + memcpy(&ctx->block[ctx->len], message, rem_len); + + if (ctx->len + len < SHA256_BLOCK_SIZE) { + ctx->len += len; + return; + } + + new_len = len - rem_len; + block_nb = new_len / SHA256_BLOCK_SIZE; + + shifted_message = message + rem_len; + + sha256_transf(ctx, ctx->block, 1); + sha256_transf(ctx, shifted_message, block_nb); + + rem_len = new_len % SHA256_BLOCK_SIZE; + + memcpy(ctx->block, &shifted_message[block_nb << 6], + rem_len); + + ctx->len = rem_len; + ctx->tot_len += (block_nb + 1) << 6; +} + +void sha256_final(sha256_ctx *ctx, unsigned char *digest) +{ + unsigned int block_nb; + unsigned int pm_len; + unsigned int len_b; + + int i; + + block_nb = (1 + ((SHA256_BLOCK_SIZE - 9) + < (ctx->len % SHA256_BLOCK_SIZE))); + + len_b = (ctx->tot_len + ctx->len) << 3; + pm_len = block_nb << 6; + + memset(ctx->block + ctx->len, 0, pm_len - ctx->len); + ctx->block[ctx->len] = 0x80; + UNPACK32(len_b, ctx->block + pm_len - 4); + + sha256_transf(ctx, ctx->block, block_nb); + + for (i = 0 ; i < 8; i++) { + UNPACK32(ctx->h[i], &digest[i << 2]); + } +} diff --git a/sha2.h b/sha2.h new file mode 100644 index 0000000..71d4404 --- /dev/null +++ b/sha2.h @@ -0,0 +1,70 @@ +/* + * FIPS 180-2 SHA-224/256/384/512 implementation + * Last update: 02/02/2007 + * Issue date: 04/30/2005 + * + * Copyright (C) 2013, Con Kolivas + * Copyright (C) 2005, 2007 Olivier Gay + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "config.h" +#include "miner.h" + +#ifndef SHA2_H +#define SHA2_H + +#define SHA256_DIGEST_SIZE ( 256 / 8) +#define SHA256_BLOCK_SIZE ( 512 / 8) + +#define SHFR(x, n) (x >> n) +#define ROTR(x, n) ((x >> n) | (x << ((sizeof(x) << 3) - n))) +#define CH(x, y, z) ((x & y) ^ (~x & z)) +#define MAJ(x, y, z) ((x & y) ^ (x & z) ^ (y & z)) + +#define SHA256_F1(x) (ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22)) +#define SHA256_F2(x) (ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25)) +#define SHA256_F3(x) (ROTR(x, 7) ^ ROTR(x, 18) ^ SHFR(x, 3)) +#define SHA256_F4(x) (ROTR(x, 17) ^ ROTR(x, 19) ^ SHFR(x, 10)) + +typedef struct { + unsigned int tot_len; + unsigned int len; + unsigned char block[2 * SHA256_BLOCK_SIZE]; + uint32_t h[8]; +} sha256_ctx; + +extern uint32_t sha256_k[64]; + +void sha256_init(sha256_ctx * ctx); +void sha256_update(sha256_ctx *ctx, const unsigned char *message, + unsigned int len); +void sha256_final(sha256_ctx *ctx, unsigned char *digest); +void sha256(const unsigned char *message, unsigned int len, + unsigned char *digest); + +#endif /* !SHA2_H */ diff --git a/sha2_c5.c b/sha2_c5.c new file mode 100644 index 0000000..fe1068d --- /dev/null +++ b/sha2_c5.c @@ -0,0 +1,310 @@ +/* + * FIPS-180-2 compliant SHA-256 implementation + * + * Copyright (C) 2011, Con Kolivas + * Copyright (C) 2006-2010, Brainspark B.V. + * + * This file is part of PolarSSL (http://www.polarssl.org) + * Lead Maintainer: Paul Bakker + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +/* + * The SHA-256 Secure Hash Standard was published by NIST in 2002. + * + * http://csrc.nist.gov/publications/fips/fips180-2/fips180-2.pdf + */ +#include + +#include "sha2_c5.h" + +extern void dump_hex(uint8_t *data, uint16_t len); +/* + * 32-bit integer manipulation macros (big endian) + */ +#ifndef GET_ULONG_BE +#define GET_ULONG_BE(n,b,i) \ +{ \ + (n) = ( (uint32_t) (b)[(i) ] << 24 ) \ + | ( (uint32_t) (b)[(i) + 1] << 16 ) \ + | ( (uint32_t) (b)[(i) + 2] << 8 ) \ + | ( (uint32_t) (b)[(i) + 3] ); \ +} +#endif + +#ifndef PUT_ULONG_BE +#define PUT_ULONG_BE(n,b,i) \ +{ \ + (b)[(i) ] = (unsigned char) ( (n) >> 24 ); \ + (b)[(i) + 1] = (unsigned char) ( (n) >> 16 ); \ + (b)[(i) + 2] = (unsigned char) ( (n) >> 8 ); \ + (b)[(i) + 3] = (unsigned char) ( (n) ); \ +} +#endif + +/* + * SHA-256 context setup + */ +void sha2_starts( sha2_context *ctx ) +{ + ctx->total[0] = 0; + ctx->total[1] = 0; + + ctx->state[0] = 0x6A09E667; + ctx->state[1] = 0xBB67AE85; + ctx->state[2] = 0x3C6EF372; + ctx->state[3] = 0xA54FF53A; + ctx->state[4] = 0x510E527F; + ctx->state[5] = 0x9B05688C; + ctx->state[6] = 0x1F83D9AB; + ctx->state[7] = 0x5BE0CD19; +} + +void sha2_process( sha2_context *ctx, const unsigned char data[64] ) +{ + uint32_t temp1, temp2, W[64]; + uint32_t A, B, C, D, E, F, G, H; + + GET_ULONG_BE( W[ 0], data, 0 ); + GET_ULONG_BE( W[ 1], data, 4 ); + GET_ULONG_BE( W[ 2], data, 8 ); + GET_ULONG_BE( W[ 3], data, 12 ); + GET_ULONG_BE( W[ 4], data, 16 ); + GET_ULONG_BE( W[ 5], data, 20 ); + GET_ULONG_BE( W[ 6], data, 24 ); + GET_ULONG_BE( W[ 7], data, 28 ); + GET_ULONG_BE( W[ 8], data, 32 ); + GET_ULONG_BE( W[ 9], data, 36 ); + GET_ULONG_BE( W[10], data, 40 ); + GET_ULONG_BE( W[11], data, 44 ); + GET_ULONG_BE( W[12], data, 48 ); + GET_ULONG_BE( W[13], data, 52 ); + GET_ULONG_BE( W[14], data, 56 ); + GET_ULONG_BE( W[15], data, 60 ); + +#define SHR(x,n) ((x & 0xFFFFFFFF) >> n) +#define ROTR(x,n) (SHR(x,n) | (x << (32 - n))) + +#define S0(x) (ROTR(x, 7) ^ ROTR(x,18) ^ SHR(x, 3)) +#define S1(x) (ROTR(x,17) ^ ROTR(x,19) ^ SHR(x,10)) + +#define S2(x) (ROTR(x, 2) ^ ROTR(x,13) ^ ROTR(x,22)) +#define S3(x) (ROTR(x, 6) ^ ROTR(x,11) ^ ROTR(x,25)) + +#define F0(x,y,z) ((x & y) | (z & (x | y))) +#define F1(x,y,z) (z ^ (x & (y ^ z))) + +#define R(t) \ +( \ + W[t] = S1(W[t - 2]) + W[t - 7] + \ + S0(W[t - 15]) + W[t - 16] \ +) + +#define P(a,b,c,d,e,f,g,h,x,K) \ +{ \ + temp1 = h + S3(e) + F1(e,f,g) + K + x; \ + temp2 = S2(a) + F0(a,b,c); \ + d += temp1; h = temp1 + temp2; \ +} + + A = ctx->state[0]; + B = ctx->state[1]; + C = ctx->state[2]; + D = ctx->state[3]; + E = ctx->state[4]; + F = ctx->state[5]; + G = ctx->state[6]; + H = ctx->state[7]; + + P( A, B, C, D, E, F, G, H, W[ 0], 0x428A2F98 ); + P( H, A, B, C, D, E, F, G, W[ 1], 0x71374491 ); + P( G, H, A, B, C, D, E, F, W[ 2], 0xB5C0FBCF ); + P( F, G, H, A, B, C, D, E, W[ 3], 0xE9B5DBA5 ); + P( E, F, G, H, A, B, C, D, W[ 4], 0x3956C25B ); + P( D, E, F, G, H, A, B, C, W[ 5], 0x59F111F1 ); + P( C, D, E, F, G, H, A, B, W[ 6], 0x923F82A4 ); + P( B, C, D, E, F, G, H, A, W[ 7], 0xAB1C5ED5 ); + P( A, B, C, D, E, F, G, H, W[ 8], 0xD807AA98 ); + P( H, A, B, C, D, E, F, G, W[ 9], 0x12835B01 ); + P( G, H, A, B, C, D, E, F, W[10], 0x243185BE ); + P( F, G, H, A, B, C, D, E, W[11], 0x550C7DC3 ); + P( E, F, G, H, A, B, C, D, W[12], 0x72BE5D74 ); + P( D, E, F, G, H, A, B, C, W[13], 0x80DEB1FE ); + P( C, D, E, F, G, H, A, B, W[14], 0x9BDC06A7 ); + P( B, C, D, E, F, G, H, A, W[15], 0xC19BF174 ); + P( A, B, C, D, E, F, G, H, R(16), 0xE49B69C1 ); + P( H, A, B, C, D, E, F, G, R(17), 0xEFBE4786 ); + P( G, H, A, B, C, D, E, F, R(18), 0x0FC19DC6 ); + P( F, G, H, A, B, C, D, E, R(19), 0x240CA1CC ); + P( E, F, G, H, A, B, C, D, R(20), 0x2DE92C6F ); + P( D, E, F, G, H, A, B, C, R(21), 0x4A7484AA ); + P( C, D, E, F, G, H, A, B, R(22), 0x5CB0A9DC ); + P( B, C, D, E, F, G, H, A, R(23), 0x76F988DA ); + P( A, B, C, D, E, F, G, H, R(24), 0x983E5152 ); + P( H, A, B, C, D, E, F, G, R(25), 0xA831C66D ); + P( G, H, A, B, C, D, E, F, R(26), 0xB00327C8 ); + P( F, G, H, A, B, C, D, E, R(27), 0xBF597FC7 ); + P( E, F, G, H, A, B, C, D, R(28), 0xC6E00BF3 ); + P( D, E, F, G, H, A, B, C, R(29), 0xD5A79147 ); + P( C, D, E, F, G, H, A, B, R(30), 0x06CA6351 ); + P( B, C, D, E, F, G, H, A, R(31), 0x14292967 ); + P( A, B, C, D, E, F, G, H, R(32), 0x27B70A85 ); + P( H, A, B, C, D, E, F, G, R(33), 0x2E1B2138 ); + P( G, H, A, B, C, D, E, F, R(34), 0x4D2C6DFC ); + P( F, G, H, A, B, C, D, E, R(35), 0x53380D13 ); + P( E, F, G, H, A, B, C, D, R(36), 0x650A7354 ); + P( D, E, F, G, H, A, B, C, R(37), 0x766A0ABB ); + P( C, D, E, F, G, H, A, B, R(38), 0x81C2C92E ); + P( B, C, D, E, F, G, H, A, R(39), 0x92722C85 ); + P( A, B, C, D, E, F, G, H, R(40), 0xA2BFE8A1 ); + P( H, A, B, C, D, E, F, G, R(41), 0xA81A664B ); + P( G, H, A, B, C, D, E, F, R(42), 0xC24B8B70 ); + P( F, G, H, A, B, C, D, E, R(43), 0xC76C51A3 ); + P( E, F, G, H, A, B, C, D, R(44), 0xD192E819 ); + P( D, E, F, G, H, A, B, C, R(45), 0xD6990624 ); + P( C, D, E, F, G, H, A, B, R(46), 0xF40E3585 ); + P( B, C, D, E, F, G, H, A, R(47), 0x106AA070 ); + P( A, B, C, D, E, F, G, H, R(48), 0x19A4C116 ); + P( H, A, B, C, D, E, F, G, R(49), 0x1E376C08 ); + P( G, H, A, B, C, D, E, F, R(50), 0x2748774C ); + P( F, G, H, A, B, C, D, E, R(51), 0x34B0BCB5 ); + P( E, F, G, H, A, B, C, D, R(52), 0x391C0CB3 ); + P( D, E, F, G, H, A, B, C, R(53), 0x4ED8AA4A ); + P( C, D, E, F, G, H, A, B, R(54), 0x5B9CCA4F ); + P( B, C, D, E, F, G, H, A, R(55), 0x682E6FF3 ); + P( A, B, C, D, E, F, G, H, R(56), 0x748F82EE ); + P( H, A, B, C, D, E, F, G, R(57), 0x78A5636F ); + P( G, H, A, B, C, D, E, F, R(58), 0x84C87814 ); + P( F, G, H, A, B, C, D, E, R(59), 0x8CC70208 ); + P( E, F, G, H, A, B, C, D, R(60), 0x90BEFFFA ); + P( D, E, F, G, H, A, B, C, R(61), 0xA4506CEB ); + P( C, D, E, F, G, H, A, B, R(62), 0xBEF9A3F7 ); + P( B, C, D, E, F, G, H, A, R(63), 0xC67178F2 ); + + ctx->state[0] += A; + ctx->state[1] += B; + ctx->state[2] += C; + ctx->state[3] += D; + ctx->state[4] += E; + ctx->state[5] += F; + ctx->state[6] += G; + ctx->state[7] += H; +} + +/* + * SHA-256 process buffer + */ +void sha2_update( sha2_context *ctx, const unsigned char *input, int ilen ) +{ + int fill; + uint32_t left; + + if( ilen <= 0 ) + return; + + left = ctx->total[0] & 0x3F; + fill = 64 - left; + + ctx->total[0] += ilen; + ctx->total[0] &= 0xFFFFFFFF; + + if( ctx->total[0] < (uint32_t) ilen ) + ctx->total[1]++; + + if( left && ilen >= fill ) + { + memcpy( (void *) (ctx->buffer + left), + (void *) input, fill ); + sha2_process( ctx, ctx->buffer ); + input += fill; + ilen -= fill; + left = 0; + } + + while( ilen >= 64 ) + { + sha2_process( ctx, input ); + input += 64; + ilen -= 64; + } + + if( ilen > 0 ) + { + memcpy((void *) (ctx->buffer + left), + (void *) input, ilen ); + } + /* + printk("ctx sha2_update:"); + dump_hex((uint8_t*)ctx,sizeof(*ctx)); + */ +} + +static const unsigned char sha2_padding[64] = +{ + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* + * SHA-256 final digest + */ +void sha2_finish( sha2_context *ctx, unsigned char output[32] ) +{ + uint32_t last, padn; + uint32_t high, low; + unsigned char msglen[8]; + + high = ( ctx->total[0] >> 29 ) + | ( ctx->total[1] << 3 ); + low = ( ctx->total[0] << 3 ); + + PUT_ULONG_BE( high, msglen, 0 ); + PUT_ULONG_BE( low, msglen, 4 ); + + last = ctx->total[0] & 0x3F; + padn = ( last < 56 ) ? ( 56 - last ) : ( 120 - last ); + + sha2_update( ctx, (unsigned char *) sha2_padding, padn ); + sha2_update( ctx, msglen, 8 ); + + PUT_ULONG_BE( ctx->state[0], output, 0 ); + PUT_ULONG_BE( ctx->state[1], output, 4 ); + PUT_ULONG_BE( ctx->state[2], output, 8 ); + PUT_ULONG_BE( ctx->state[3], output, 12 ); + PUT_ULONG_BE( ctx->state[4], output, 16 ); + PUT_ULONG_BE( ctx->state[5], output, 20 ); + PUT_ULONG_BE( ctx->state[6], output, 24 ); + + PUT_ULONG_BE( ctx->state[7], output, 28 ); +} + +/* + * output = SHA-256( input buffer ) + */ +void sha2( const unsigned char *input, int ilen, + unsigned char output[32] ) +{ + sha2_context ctx; + + sha2_starts( &ctx ); + sha2_update( &ctx, input, ilen ); + sha2_finish( &ctx, output ); + + memset(&ctx, 0, sizeof(sha2_context)); +} diff --git a/sha2_c5.h b/sha2_c5.h new file mode 100644 index 0000000..11619e9 --- /dev/null +++ b/sha2_c5.h @@ -0,0 +1,96 @@ +/** + * \file sha2.h + * + * Copyright (C) 2011, Con Kolivas + * Copyright (C) 2006-2010, Brainspark B.V. + * + * This file is part of PolarSSL (http://www.polarssl.org) + * Lead Maintainer: Paul Bakker + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "miner.h" + +#ifndef POLARSSL_SHA2_H +#define POLARSSL_SHA2_H + + + +/** + * \brief SHA-256 context structure + */ +typedef struct +{ + uint32_t total[2]; /*!< number of bytes processed */ + uint32_t state[8]; /*!< intermediate digest state */ + unsigned char buffer[64]; /*!< data block being processed */ + + unsigned char ipad[64]; /*!< HMAC: inner padding */ + unsigned char opad[64]; /*!< HMAC: outer padding */ +} +sha2_context; + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \brief SHA-256 context setup + * + * \param ctx context to be initialized + */ +void sha2_starts( sha2_context *ctx); + +/** + * \brief SHA-256 process buffer + * + * \param ctx SHA-256 context + * \param input buffer holding the data + * \param ilen length of the input data + */ +void sha2_update( sha2_context *ctx, const unsigned char *input, int ilen ); + +/** + * \brief SHA-256 final digest + * + * \param ctx SHA-256 context + * \param output SHA-256 checksum result + */ +void sha2_finish( sha2_context *ctx, unsigned char output[32] ); + +/** + * \brief Output = SHA-256( input buffer ) + * + * \param input buffer holding the data + * \param ilen length of the input data + * \param output SHA-256 checksum result + */ +void sha2( const unsigned char *input, int ilen, + unsigned char output[32]); + + +void sha2_process( sha2_context *ctx, const unsigned char data[64] ); + +#ifdef __cplusplus +} +#endif + +#endif /* sha2.h */ + + diff --git a/spi-context.c b/spi-context.c new file mode 100644 index 0000000..e0ec139 --- /dev/null +++ b/spi-context.c @@ -0,0 +1,94 @@ +/* + * generic SPI functions + * + * Copyright 2013, 2014 Zefir Kurtisi + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#include "spi-context.h" + +#include "logging.h" +#include "miner.h" + +#include +#include +#include +#include + +struct spi_ctx *spi_init(struct spi_config *config) +{ + char dev_fname[PATH_MAX]; + struct spi_ctx *ctx; + + if (config == NULL) + return NULL; + + sprintf(dev_fname, SPI_DEVICE_TEMPLATE, config->bus, config->cs_line); + + int fd = open(dev_fname, O_RDWR); + if (fd < 0) { + applog(LOG_ERR, "SPI: Can not open SPI device %s", dev_fname); + return NULL; + } + + if ((ioctl(fd, SPI_IOC_WR_MODE, &config->mode) < 0) || + (ioctl(fd, SPI_IOC_RD_MODE, &config->mode) < 0) || + (ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &config->bits) < 0) || + (ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &config->bits) < 0) || + (ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &config->speed) < 0) || + (ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &config->speed) < 0)) { + applog(LOG_ERR, "SPI: ioctl error on SPI device %s", dev_fname); + close(fd); + return NULL; + } + + ctx = malloc(sizeof(*ctx)); + assert(ctx != NULL); + + ctx->fd = fd; + ctx->config = *config; + applog(LOG_WARNING, "SPI '%s': mode=%hhu, bits=%hhu, speed=%u", + dev_fname, ctx->config.mode, ctx->config.bits, + ctx->config.speed); + return ctx; +} + +extern void spi_exit(struct spi_ctx *ctx) +{ + if (NULL == ctx) + return; + + close(ctx->fd); + free(ctx); +} + +extern bool spi_transfer(struct spi_ctx *ctx, uint8_t *txbuf, + uint8_t *rxbuf, int len) +{ + struct spi_ioc_transfer xfr; + int ret; + + if (rxbuf != NULL) + memset(rxbuf, 0xff, len); + + ret = len; + + xfr.tx_buf = (unsigned long)txbuf; + xfr.rx_buf = (unsigned long)rxbuf; + xfr.len = len; + xfr.speed_hz = ctx->config.speed; + xfr.delay_usecs = ctx->config.delay; + xfr.bits_per_word = ctx->config.bits; + xfr.cs_change = 0; + xfr.pad = 0; + + ret = ioctl(ctx->fd, SPI_IOC_MESSAGE(1), &xfr); + if (ret < 1) + applog(LOG_ERR, "SPI: ioctl error on SPI device: %d", ret); + + return ret > 0; +} diff --git a/spi-context.h b/spi-context.h new file mode 100644 index 0000000..a341e69 --- /dev/null +++ b/spi-context.h @@ -0,0 +1,48 @@ +#ifndef SPI_CONTEXT_H +#define SPI_CONTEXT_H + +#include +#include +#include +#include + +#define SPI_DEVICE_TEMPLATE "/dev/spidev%d.%d" +#define DEFAULT_SPI_BUS 0 +#define DEFAULT_SPI_CS_LINE 0 +#define DEFAULT_SPI_MODE SPI_MODE_0 +#define DEFAULT_SPI_BITS_PER_WORD 8 +#define DEFAULT_SPI_SPEED 1500000 +#define DEFAULT_SPI_DELAY_USECS 0 + +struct spi_config { + int bus; + int cs_line; + uint8_t mode; + uint32_t speed; + uint8_t bits; + uint16_t delay; +}; + +static const struct spi_config default_spi_config = { + .bus = DEFAULT_SPI_BUS, + .cs_line = DEFAULT_SPI_CS_LINE, + .mode = DEFAULT_SPI_MODE, + .speed = DEFAULT_SPI_SPEED, + .bits = DEFAULT_SPI_BITS_PER_WORD, + .delay = DEFAULT_SPI_DELAY_USECS, +}; + +struct spi_ctx { + int fd; + struct spi_config config; +}; + +/* create SPI context with given configuration, returns NULL on failure */ +extern struct spi_ctx *spi_init(struct spi_config *config); +/* close descriptor and free resources */ +extern void spi_exit(struct spi_ctx *ctx); +/* process RX/TX transfer, ensure buffers are long enough */ +extern bool spi_transfer(struct spi_ctx *ctx, uint8_t *txbuf, + uint8_t *rxbuf, int len); + +#endif /* SPI_CONTEXT_H */ diff --git a/usbtest.py b/usbtest.py new file mode 100644 index 0000000..79f79cb --- /dev/null +++ b/usbtest.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python2.7 +# +# Original version supplied to me (Kano/kanoi) by xiangfu +# +# Modified to allow supplying the data to send +# +# Linux usAge: ./ubstest.py /dev/ttyUSB0 0xhexcodes|string|icarus +# OR python ubstest.py /dev/ttyUSB0 0xhexcodes|string|icarus +# +# Windows usAge: ./ubstest.py COM1 0xhexcodes|string|icarus +# +# sends the data sepcified to the USB device and waits +# for a reply then displays it +# +# the data can be: +# 0xhexcodes: e.g. 0x68656c6c6f20776f726c640a +# would send "hello world\n" +# +# string: e.g. sendsometext +# +# icarus: sends 2 known block payloads for an icarus device +# and shows the expected and actual answers if it's +# a working V3 icarus + +import sys +import serial +import binascii + +if len(sys.argv) < 2: + sys.stderr.write("usAge: " + sys.argv[0] + " device strings...\n") + sys.stderr.write(" where device is either like /dev/ttyUSB0 or COM1\n") + sys.stderr.write(" and strings are either '0xXXXX' or 'text'\n") + sys.stderr.write(" if the first string is 'icarus' the rest are ignored\n") + sys.stderr.write(" and 2 valid icarus test payloads are sent with results displayed\n") + sys.stderr.write("\nAfter any command is sent it waits up to 30 seconds for a reply\n"); + sys.exit("Aborting") + +# Open with a 10 second timeout - just to be sure +ser = serial.Serial(sys.argv[1], 115200, serial.EIGHTBITS, serial.PARITY_NONE, serial.STOPBITS_ONE, 10, False, False, 5, False, None) + +if sys.argv[2] == "icarus": + + # This show how Icarus use the block and midstate data + # This will produce nonce 063c5e01 + block = "0000000120c8222d0497a7ab44a1a2c7bf39de941c9970b1dc7cdc400000079700000000e88aabe1f353238c668d8a4df9318e614c10c474f8cdf8bc5f6397b946c33d7c4e7242c31a098ea500000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000" + midstate = "33c5bf5751ec7f7e056443b5aee3800331432c83f404d9de38b94ecbf907b92d" + + rdata2 = block.decode('hex')[95:63:-1] + rmid = midstate.decode('hex')[::-1] + payload = rmid + rdata2 + + print("Push payload to icarus: " + binascii.hexlify(payload)) + ser.write(payload) + + b=ser.read(4) + print("Result:(should be: 063c5e01): " + binascii.hexlify(b)) + + # Just another test + payload2 = "ce92099c5a80bb81c52990d5c0924c625fd25a535640607d5a4bdf8174e2c8d500000000000000000000000080000000000000000b290c1a42313b4f21b5bcb8" + print("Push payload to icarus: " + payload2) + ser.write(payload2.decode('hex')) + + b=ser.read(4) + print("Result:(should be: 8e0b31c5): " + binascii.hexlify(b)) +else: + data = "" + for arg in sys.argv[2::]: + if arg[0:2:] == '0x': + data += arg[2::].decode('hex') + else: + data += arg + + print("Sending: 0x" + binascii.hexlify(data)) + ser.write(data) + + # If you're expecting more than one linefeed terminated reply, + # you'll only see the first one + # AND with no linefeed, this will wait the 10 seconds before returning + print("Waiting up to 10 seconds ...") + b=ser.readline() + print("Result: hex 0x" + binascii.hexlify(b)) + + # This could mess up the display - do it last + print("Result: asc '" + b + "'") + +ser.close() diff --git a/usbutils.c b/usbutils.c new file mode 100644 index 0000000..758ddd1 --- /dev/null +++ b/usbutils.c @@ -0,0 +1,4456 @@ +/* + * Copyright 2012-2013 Andrew Smith + * Copyright 2013-2014 Con Kolivas + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#include "config.h" + +#include +#include +#include + +#include "logging.h" +#include "miner.h" +#include "usbutils.h" + +static pthread_mutex_t cgusb_lock; +static pthread_mutex_t cgusbres_lock; +static cglock_t cgusb_fd_lock; +static cgtimer_t usb11_cgt; + +#define NODEV(err) ((err) != LIBUSB_SUCCESS && (err) != LIBUSB_ERROR_TIMEOUT) + +#define NOCONTROLDEV(err) ((err) < 0 && NODEV(err)) + +/* + * WARNING - these assume DEVLOCK(cgpu, pstate) is called first and + * DEVUNLOCK(cgpu, pstate) in called in the same function with the same pstate + * given to DEVLOCK. + * You must call DEVUNLOCK(cgpu, pstate) before exiting the function or it will leave + * the thread Cancelability unrestored + */ +#define DEVWLOCK(cgpu, _pth_state) do { \ + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &_pth_state); \ + cg_wlock(&cgpu->usbinfo.devlock); \ + } while (0) + +#define DEVWUNLOCK(cgpu, _pth_state) do { \ + cg_wunlock(&cgpu->usbinfo.devlock); \ + pthread_setcancelstate(_pth_state, NULL); \ + } while (0) + +#define DEVRLOCK(cgpu, _pth_state) do { \ + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &_pth_state); \ + cg_rlock(&cgpu->usbinfo.devlock); \ + } while (0) + +#define DEVRUNLOCK(cgpu, _pth_state) do { \ + cg_runlock(&cgpu->usbinfo.devlock); \ + pthread_setcancelstate(_pth_state, NULL); \ + } while (0) + +#define USB_CONFIG 1 + +#define BITFURY_TIMEOUT_MS 999 +#define DRILLBIT_TIMEOUT_MS 999 +#define ICARUS_TIMEOUT_MS 999 +#define BMSC_TIMEOUT_MS 999 + +#ifdef WIN32 +#define BFLSC_TIMEOUT_MS 999 +#define BITFORCE_TIMEOUT_MS 999 +#define MODMINER_TIMEOUT_MS 999 +#define AVALON_TIMEOUT_MS 999 +#define AVALON4_TIMEOUT_MS 999 +#define BITMAIN_TIMEOUT_MS 999 +#define KLONDIKE_TIMEOUT_MS 999 +#define COINTERRA_TIMEOUT_MS 999 +#define HASHFAST_TIMEOUT_MS 999 +#define HASHRATIO_TIMEOUT_MS 999 +#define BLOCKERUPTER_TIMEOUT_MS 999 + +/* The safety timeout we use, cancelling async transfers on windows that fail + * to timeout on their own. */ +#define WIN_CALLBACK_EXTRA 40 +#define WIN_WRITE_CBEXTRA 5000 +#else +#define BFLSC_TIMEOUT_MS 300 +#define BITFORCE_TIMEOUT_MS 200 +#define MODMINER_TIMEOUT_MS 100 +#define AVALON_TIMEOUT_MS 200 +#define AVALON4_TIMEOUT_MS 50 +#define BITMAIN_TIMEOUT_MS 200 +#define KLONDIKE_TIMEOUT_MS 200 +#define COINTERRA_TIMEOUT_MS 200 +#define HASHFAST_TIMEOUT_MS 500 +#define HASHRATIO_TIMEOUT_MS 200 +#define BLOCKERUPTER_TIMEOUT_MS 300 +#endif + +#define USB_EPS(_intx, _epinfosx) { \ + .interface = _intx, \ + .ctrl_transfer = _intx, \ + .epinfo_count = ARRAY_SIZE(_epinfosx), \ + .epinfos = _epinfosx \ + } + +#define USB_EPS_CTRL(_inty, _ctrlinty, _epinfosy) { \ + .interface = _inty, \ + .ctrl_transfer = _ctrlinty, \ + .epinfo_count = ARRAY_SIZE(_epinfosy), \ + .epinfos = _epinfosy \ + } + +/* Linked list of all async transfers in progress. Protected by cgusb_fd_lock. + * This allows us to not stop the usb polling thread till all are complete, and + * to find cancellable transfers. */ +static struct list_head ut_list; + +#ifdef USE_BFLSC +static struct usb_epinfo bflsc_epinfos[] = { + { LIBUSB_TRANSFER_TYPE_BULK, 512, EPI(1), 0, 0 }, + { LIBUSB_TRANSFER_TYPE_BULK, 512, EPO(2), 0, 0 } +}; + +static struct usb_intinfo bflsc_ints[] = { + USB_EPS(0, bflsc_epinfos) +}; +#endif + +#ifdef USE_BITFORCE +// N.B. transfer size is 512 with USB2.0, but only 64 with USB1.1 +static struct usb_epinfo bfl_epinfos[] = { + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPI(1), 0, 0 }, + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPO(2), 0, 0 } +}; + +static struct usb_intinfo bfl_ints[] = { + USB_EPS(0, bfl_epinfos) +}; +#endif + +#ifdef USE_BITFURY +static struct usb_epinfo bfu0_epinfos[] = { + { LIBUSB_TRANSFER_TYPE_INTERRUPT, 8, EPI(2), 0, 0 } +}; + +static struct usb_epinfo bfu1_epinfos[] = { + { LIBUSB_TRANSFER_TYPE_BULK, 16, EPI(3), 0, 0 }, + { LIBUSB_TRANSFER_TYPE_BULK, 16, EPO(4), 0, 0 } +}; + +/* Default to interface 1 */ +static struct usb_intinfo bfu_ints[] = { + USB_EPS(1, bfu1_epinfos), + USB_EPS(0, bfu0_epinfos) +}; + +static struct usb_epinfo bxf0_epinfos[] = { + { LIBUSB_TRANSFER_TYPE_INTERRUPT, 8, EPI(1), 0, 0 } +}; + +static struct usb_epinfo bxf1_epinfos[] = { + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPI(2), 0, 0 }, + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPO(2), 0, 0 } +}; + +static struct usb_intinfo bxf_ints[] = { + USB_EPS(1, bxf1_epinfos), + USB_EPS(0, bxf0_epinfos) +}; + +static struct usb_epinfo nfu_epinfos[] = { + { LIBUSB_TRANSFER_TYPE_INTERRUPT, 64, EPI(1), 0, 0 }, + { LIBUSB_TRANSFER_TYPE_INTERRUPT, 64, EPO(1), 0, 0 }, +}; + +static struct usb_intinfo nfu_ints[] = { + USB_EPS(0, nfu_epinfos) +}; + +static struct usb_epinfo bxm_epinfos[] = { + { LIBUSB_TRANSFER_TYPE_BULK, 512, EPI(1), 0, 0 }, + { LIBUSB_TRANSFER_TYPE_BULK, 512, EPO(2), 0, 0 } +}; + +static struct usb_intinfo bxm_ints[] = { + USB_EPS(0, bxm_epinfos) +}; +#endif + +#ifdef USE_BLOCKERUPTER +// BlockErupter Device +static struct usb_epinfo bet_epinfos[] = { + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPI(1), 0, 0 }, + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPO(1), 0, 0 } +}; + +static struct usb_intinfo bet_ints[] = { + USB_EPS(0, bet_epinfos) +}; +#endif + +#ifdef USE_DRILLBIT +// Drillbit Bitfury devices +static struct usb_epinfo drillbit_int_epinfos[] = { + { LIBUSB_TRANSFER_TYPE_INTERRUPT, 8, EPI(3), 0, 0 } +}; + +static struct usb_epinfo drillbit_bulk_epinfos[] = { + { LIBUSB_TRANSFER_TYPE_BULK, 16, EPI(1), 0, 0 }, + { LIBUSB_TRANSFER_TYPE_BULK, 16, EPO(2), 0, 0 }, +}; + +/* Default to interface 1 */ +static struct usb_intinfo drillbit_ints[] = { + USB_EPS(1, drillbit_bulk_epinfos), + USB_EPS(0, drillbit_int_epinfos) +}; +#endif + +#ifdef USE_HASHFAST +#include "driver-hashfast.h" + +static struct usb_epinfo hfa0_epinfos[] = { + { LIBUSB_TRANSFER_TYPE_INTERRUPT, 8, EPI(3), 0, 0 } +}; + +static struct usb_epinfo hfa1_epinfos[] = { + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPI(1), 0, 0 }, + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPO(2), 0, 0 } +}; + +/* Default to interface 1 */ +static struct usb_intinfo hfa_ints[] = { + USB_EPS(1, hfa1_epinfos), + USB_EPS(0, hfa0_epinfos) +}; +#endif + +#ifdef USE_HASHRATIO +static struct usb_epinfo hro_epinfos[] = { + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPI(1), 0, 0 }, + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPO(2), 0, 0 } +}; + +static struct usb_intinfo hro_ints[] = { + USB_EPS(0, hro_epinfos) +}; +#endif + +#ifdef USE_MODMINER +static struct usb_epinfo mmq_epinfos[] = { + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPI(3), 0, 0 }, + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPO(3), 0, 0 } +}; + +static struct usb_intinfo mmq_ints[] = { + USB_EPS(1, mmq_epinfos) +}; +#endif + +#ifdef USE_AVALON +static struct usb_epinfo ava_epinfos[] = { + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPI(1), 0, 0 }, + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPO(2), 0, 0 } +}; + +static struct usb_intinfo ava_ints[] = { + USB_EPS(0, ava_epinfos) +}; +#endif + +#ifdef USE_AVALON2 +static struct usb_epinfo ava2_epinfos[] = { + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPI(3), 0, 0 }, + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPO(2), 0, 0 } +}; + +static struct usb_intinfo ava2_ints[] = { + USB_EPS(0, ava2_epinfos) +}; +#endif + +#ifdef USE_AVALON4 +static struct usb_epinfo ava4_epinfos[] = { + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPI(1), 0, 0 }, + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPO(1), 0, 0 } +}; + +static struct usb_intinfo ava4_ints[] = { + USB_EPS(1, ava4_epinfos) +}; +#endif + +#ifdef USE_KLONDIKE +static struct usb_epinfo kln_epinfos[] = { + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPI(1), 0, 0 }, + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPO(1), 0, 0 } +}; + +static struct usb_intinfo kln_ints[] = { + USB_EPS(0, kln_epinfos) +}; + +static struct usb_epinfo kli0_epinfos[] = { + { LIBUSB_TRANSFER_TYPE_INTERRUPT, 8, EPI(1), 0, 0 } +}; + +static struct usb_epinfo kli1_epinfos[] = { + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPI(2), 0, 0 }, + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPO(2), 0, 0 } +}; + +static struct usb_intinfo kli_ints[] = { + USB_EPS(1, kli1_epinfos), + USB_EPS(0, kli0_epinfos) +}; +#endif + +#ifdef USE_ICARUS +static struct usb_epinfo ica_epinfos[] = { + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPI(3), 0, 0 }, + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPO(2), 0, 0 } +}; + +static struct usb_intinfo ica_ints[] = { + USB_EPS(0, ica_epinfos) +}; + +static struct usb_epinfo ica1_epinfos0[] = { + { LIBUSB_TRANSFER_TYPE_INTERRUPT, 16, EPI(0x82), 0, 0 } +}; + +static struct usb_epinfo ica1_epinfos1[] = { + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPI(0x81), 0, 0 }, + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPO(0x01), 0, 0 } +}; + +static struct usb_intinfo ica1_ints[] = { + USB_EPS(1, ica1_epinfos1), + USB_EPS(0, ica1_epinfos0) +}; + +static struct usb_epinfo amu_epinfos[] = { + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPI(1), 0, 0 }, + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPO(1), 0, 0 } +}; + +static struct usb_intinfo amu_ints[] = { + USB_EPS(0, amu_epinfos) +}; + +static struct usb_epinfo llt_epinfos[] = { + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPI(1), 0, 0 }, + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPO(2), 0, 0 } +}; + +static struct usb_intinfo llt_ints[] = { + USB_EPS(0, llt_epinfos) +}; + +static struct usb_epinfo cmr1_epinfos[] = { + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPI(1), 0, 0 }, + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPO(2), 0, 0 } +}; + +static struct usb_intinfo cmr1_ints[] = { + USB_EPS(0, cmr1_epinfos) +}; + +static struct usb_epinfo cmr2_epinfos0[] = { + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPI(1), 0, 0 }, + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPO(2), 0, 0 } +}; +static struct usb_epinfo cmr2_epinfos1[] = { + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPI(3), 0, 0 }, + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPO(4), 0, 0 }, +}; +static struct usb_epinfo cmr2_epinfos2[] = { + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPI(5), 0, 0 }, + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPO(6), 0, 0 }, +}; +static struct usb_epinfo cmr2_epinfos3[] = { + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPI(7), 0, 0 }, + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPO(8), 0, 0 } +}; + +static struct usb_intinfo cmr2_ints[] = { + USB_EPS_CTRL(0, 1, cmr2_epinfos0), + USB_EPS_CTRL(1, 2, cmr2_epinfos1), + USB_EPS_CTRL(2, 3, cmr2_epinfos2), + USB_EPS_CTRL(3, 4, cmr2_epinfos3) +}; +#endif + +#ifdef USE_COINTERRA +static struct usb_epinfo cointerra_epinfos[] = { + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPI(1), 0, 0 }, + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPO(1), 0, 0 } +}; + +static struct usb_intinfo cointerra_ints[] = { + USB_EPS(0, cointerra_epinfos) +}; +#endif + +#ifdef USE_BMSC +static struct usb_epinfo ica_epinfos[] = { + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPI(3), 0, 0 }, + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPO(2), 0, 0 } +}; + +static struct usb_intinfo ica_ints[] = { + USB_EPS(0, ica_epinfos) +}; + +static struct usb_epinfo amu_epinfos[] = { + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPI(1), 0, 0 }, + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPO(1), 0, 0 } +}; + +static struct usb_intinfo amu_ints[] = { + USB_EPS(0, amu_epinfos) +}; + +static struct usb_epinfo llt_epinfos[] = { + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPI(1), 0, 0 }, + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPO(2), 0, 0 } +}; + +static struct usb_intinfo llt_ints[] = { + USB_EPS(0, llt_epinfos) +}; + +static struct usb_epinfo cmr1_epinfos[] = { + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPI(1), 0, 0 }, + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPO(2), 0, 0 } +}; + +static struct usb_intinfo cmr1_ints[] = { + USB_EPS(0, cmr1_epinfos) +}; + +static struct usb_epinfo cmr2_epinfos0[] = { + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPI(1), 0, 0 }, + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPO(2), 0, 0 } +}; +static struct usb_epinfo cmr2_epinfos1[] = { + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPI(3), 0, 0 }, + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPO(4), 0, 0 }, +}; +static struct usb_epinfo cmr2_epinfos2[] = { + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPI(5), 0, 0 }, + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPO(6), 0, 0 }, +}; +static struct usb_epinfo cmr2_epinfos3[] = { + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPI(7), 0, 0 }, + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPO(8), 0, 0 } +}; + +static struct usb_intinfo cmr2_ints[] = { + USB_EPS_CTRL(0, 1, cmr2_epinfos0), + USB_EPS_CTRL(1, 2, cmr2_epinfos1), + USB_EPS_CTRL(2, 3, cmr2_epinfos2), + USB_EPS_CTRL(3, 4, cmr2_epinfos3) +}; +#endif + +#ifdef USE_BITMAIN +static struct usb_epinfo btm_epinfos[] = { + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPI(1), 0, 0 }, +#ifdef WIN32 + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPO(2), 0, 0 } +#else + { LIBUSB_TRANSFER_TYPE_BULK, 64, EPO(1), 0, 0 } +#endif +}; + +static struct usb_intinfo btm_ints[] = { + USB_EPS(0, btm_epinfos) +}; +#endif + +#define IDVENDOR_FTDI 0x0403 + +#define INTINFO(_ints) \ + .intinfo_count = ARRAY_SIZE(_ints), \ + .intinfos = _ints + +#define USBEP(_usbdev, _intinfo, _epinfo) (_usbdev->found->intinfos[_intinfo].epinfos[_epinfo].ep) +#define THISIF(_found, _this) (_found->intinfos[_this].interface) +#define USBIF(_usbdev, _this) THISIF(_usbdev->found, _this) + +// TODO: Add support for (at least) Isochronous endpoints +static struct usb_find_devices find_dev[] = { +#ifdef USE_BFLSC + { + .drv = DRIVER_bflsc, + .name = "BAS", + .ident = IDENT_BAS, + .idVendor = IDVENDOR_FTDI, + .idProduct = 0x6014, + //.iManufacturer = "Butterfly Labs", + .iProduct = "BitFORCE SHA256 SC", + .config = 1, + .timeout = BFLSC_TIMEOUT_MS, + .latency = LATENCY_STD, + INTINFO(bflsc_ints) }, + { + .drv = DRIVER_bflsc, + .name = "BMA", + .ident = IDENT_BMA, + .idVendor = IDVENDOR_FTDI, + .idProduct = 0x6014, + //.iManufacturer = "BUTTERFLY LABS" + .iProduct = "BitFORCE SC-28nm", + .config = 1, + .timeout = BFLSC_TIMEOUT_MS, + .latency = LATENCY_STD, + INTINFO(bflsc_ints) }, + { + .drv = DRIVER_bflsc, + .name = "BMA", + .ident = IDENT_BMA, + .idVendor = IDVENDOR_FTDI, + .idProduct = 0x6014, + .iManufacturer = "BUTTERFLY LABS", + .iProduct = "BitFORCE SHA256", + .config = 1, + .timeout = BFLSC_TIMEOUT_MS, + .latency = LATENCY_STD, + INTINFO(bflsc_ints) }, +#endif +#ifdef USE_BITFORCE + { + .drv = DRIVER_bitforce, + .name = "BFL", + .ident = IDENT_BFL, + .idVendor = IDVENDOR_FTDI, + .idProduct = 0x6014, + .iManufacturer = "Butterfly Labs Inc.", + .iProduct = "BitFORCE SHA256", + .config = 1, + .timeout = BITFORCE_TIMEOUT_MS, + .latency = LATENCY_STD, + INTINFO(bfl_ints) }, +#endif +#ifdef USE_BITFURY + { + .drv = DRIVER_bitfury, + .name = "BF1", + .ident = IDENT_BF1, + .idVendor = 0x03eb, + .idProduct = 0x204b, + .config = 1, + .timeout = BITFURY_TIMEOUT_MS, + .latency = LATENCY_UNUSED, + //.iManufacturer = "BPMC", + .iProduct = "Bitfury BF1", + INTINFO(bfu_ints) + }, + { + .drv = DRIVER_bitfury, + .name = "BXF", + .ident = IDENT_BXF, + .idVendor = 0x198c, + .idProduct = 0xb1f1, + .config = 1, + .timeout = BITFURY_TIMEOUT_MS, + .latency = LATENCY_UNUSED, + .iManufacturer = "c-scape", + .iProduct = "bi?fury", + INTINFO(bxf_ints) + }, + { + .drv = DRIVER_bitfury, + .name = "OSM", + .ident = IDENT_OSM, + .idVendor = 0x198c, + .idProduct = 0xb1f1, + .config = 1, + .timeout = BITFURY_TIMEOUT_MS, + .latency = LATENCY_UNUSED, + .iManufacturer = "c-scape", + .iProduct = "OneString", + INTINFO(bxf_ints) + }, + { + .drv = DRIVER_bitfury, + .name = "NFU", + .ident = IDENT_NFU, + .idVendor = 0x04d8, + .idProduct = 0x00de, + .config = 1, + .timeout = BITFURY_TIMEOUT_MS, + .latency = LATENCY_UNUSED, + INTINFO(nfu_ints) + }, + { + .drv = DRIVER_bitfury, + .name = "BXM", + .ident = IDENT_BXM, + .idVendor = 0x0403, + .idProduct = 0x6014, + .config = 1, + .timeout = BITFURY_TIMEOUT_MS, + .latency = LATENCY_UNUSED, + INTINFO(bxm_ints) + }, +#endif +#ifdef USE_BLOCKERUPTER + { + .drv = DRIVER_blockerupter, + .name = "BET", + .ident = IDENT_BET, + .idVendor = 0x10c4, + .idProduct = 0xea60, + .config = 1, + .timeout = BLOCKERUPTER_TIMEOUT_MS, + .latency = LATENCY_UNUSED, + INTINFO(bet_ints) }, + +#endif +#ifdef USE_DRILLBIT + { + .drv = DRIVER_drillbit, + .name = "DRB", + .ident = IDENT_DRB, + .idVendor = 0x03eb, + .idProduct = 0x2404, + .config = 1, + .timeout = DRILLBIT_TIMEOUT_MS, + .latency = LATENCY_UNUSED, + .iManufacturer = "Drillbit Systems", + .iProduct = NULL, /* Can be Thumb or Eight, same driver */ + INTINFO(drillbit_ints) + }, +#endif +#ifdef USE_MODMINER + { + .drv = DRIVER_modminer, + .name = "MMQ", + .ident = IDENT_MMQ, + .idVendor = 0x1fc9, + .idProduct = 0x0003, + .config = 1, + .timeout = MODMINER_TIMEOUT_MS, + .latency = LATENCY_UNUSED, + INTINFO(mmq_ints) }, +#endif +#ifdef USE_AVALON + { + .drv = DRIVER_avalon, + .name = "BTB", + .ident = IDENT_BTB, + .idVendor = IDVENDOR_FTDI, + .idProduct = 0x6001, + .iManufacturer = "Burnin Electronics", + .iProduct = "BitBurner", + .config = 1, + .timeout = AVALON_TIMEOUT_MS, + .latency = 10, + INTINFO(ava_ints) }, + { + .drv = DRIVER_avalon, + .name = "BBF", + .ident = IDENT_BBF, + .idVendor = IDVENDOR_FTDI, + .idProduct = 0x6001, + .iManufacturer = "Burnin Electronics", + .iProduct = "BitBurner Fury", + .config = 1, + .timeout = AVALON_TIMEOUT_MS, + .latency = 10, + INTINFO(ava_ints) }, + { + .drv = DRIVER_avalon, + .name = "AVA", + .ident = IDENT_AVA, + .idVendor = IDVENDOR_FTDI, + .idProduct = 0x6001, + .config = 1, + .timeout = AVALON_TIMEOUT_MS, + .latency = 10, + INTINFO(ava_ints) }, +#endif +#ifdef USE_AVALON2 + { + .drv = DRIVER_avalon2, + .name = "AV2", + .ident = IDENT_AV2, + .idVendor = 0x067b, + .idProduct = 0x2303, + .config = 1, + .timeout = AVALON_TIMEOUT_MS, + .latency = LATENCY_UNUSED, + INTINFO(ava2_ints) }, +#endif +#ifdef USE_AVALON4 + { + .drv = DRIVER_avalon4, + .name = "AV4", + .ident = IDENT_AV4, + .idVendor = 0x29f1, + .idProduct = 0x33f2, + .iManufacturer = "CANAAN", + .iProduct = "USB2IIC Converter", + .config = 1, + .timeout = AVALON4_TIMEOUT_MS, + .latency = LATENCY_UNUSED, + INTINFO(ava4_ints) }, +#endif +#ifdef USE_HASHFAST + { + .drv = DRIVER_hashfast, + .name = "HFA", + .ident = IDENT_HFA, + .idVendor = HF_USB_VENDOR_ID, + .idProduct = HF_USB_PRODUCT_ID_G1, + .iManufacturer = "HashFast LLC", + .iProduct = "M1 Module", + .config = 1, + .timeout = HASHFAST_TIMEOUT_MS, + .latency = LATENCY_UNUSED, + INTINFO(hfa_ints) }, +#endif +#ifdef USE_HASHRATIO + { + .drv = DRIVER_hashratio, + .name = "HRO", + .ident = IDENT_HRO, + .idVendor = IDVENDOR_FTDI, + .idProduct = 0x6001, + .config = 1, + .timeout = HASHRATIO_TIMEOUT_MS, + .latency = LATENCY_UNUSED, + INTINFO(hro_ints) }, +#endif +#ifdef USE_KLONDIKE + { + .drv = DRIVER_klondike, + .name = "KLN", + .ident = IDENT_KLN, + .idVendor = 0x04D8, + .idProduct = 0xF60A, + .config = 1, + .timeout = KLONDIKE_TIMEOUT_MS, + .latency = 10, + INTINFO(kln_ints) }, + { + .drv = DRIVER_klondike, + .name = "KLI", + .ident = IDENT_KLN, + .idVendor = 0x04D8, + .idProduct = 0xF60A, + .config = 1, + .timeout = KLONDIKE_TIMEOUT_MS, + .latency = 10, + INTINFO(kli_ints) }, +#endif +#ifdef USE_ICARUS + { + .drv = DRIVER_icarus, + .name = "ICA", + .ident = IDENT_ICA, + .idVendor = 0x067b, + .idProduct = 0x2303, + .config = 1, + .timeout = ICARUS_TIMEOUT_MS, + .latency = LATENCY_UNUSED, + INTINFO(ica_ints) }, + { + .drv = DRIVER_icarus, + .name = "ICA", + .ident = IDENT_AVA, + .idVendor = 0x1fc9, + .idProduct = 0x0083, + .config = 1, + .timeout = ICARUS_TIMEOUT_MS, + .latency = LATENCY_UNUSED, + INTINFO(ica1_ints) }, + { + .drv = DRIVER_icarus, + .name = "AMU", + .ident = IDENT_AMU, + .idVendor = 0x10c4, + .idProduct = 0xea60, + .config = 1, + .timeout = ICARUS_TIMEOUT_MS, + .latency = LATENCY_UNUSED, + INTINFO(amu_ints) }, + { + .drv = DRIVER_icarus, + .name = "LIN", + .ident = IDENT_LIN, + .idVendor = 0x10c4, + .idProduct = 0xea60, + .config = 1, + .timeout = ICARUS_TIMEOUT_MS, + .latency = LATENCY_UNUSED, + INTINFO(amu_ints) }, + { + .drv = DRIVER_icarus, + .name = "ANU", + .ident = IDENT_ANU, + .idVendor = 0x10c4, + .idProduct = 0xea60, + .config = 1, + .timeout = ICARUS_TIMEOUT_MS, + .latency = LATENCY_UNUSED, + INTINFO(amu_ints) }, + { + .drv = DRIVER_icarus, + .name = "BLT", + .ident = IDENT_BLT, + .idVendor = IDVENDOR_FTDI, + .idProduct = 0x6001, + .iProduct = "FT232R USB UART", + .config = 1, + .timeout = ICARUS_TIMEOUT_MS, + .latency = LATENCY_STD, + INTINFO(llt_ints) }, + // For any that don't match the above "BLT" + { + .drv = DRIVER_icarus, + .name = "LLT", + .ident = IDENT_LLT, + .idVendor = IDVENDOR_FTDI, + .idProduct = 0x6001, + .config = 1, + .timeout = ICARUS_TIMEOUT_MS, + .latency = LATENCY_STD, + INTINFO(llt_ints) }, + { + .drv = DRIVER_icarus, + .name = "CMR", + .ident = IDENT_CMR1, + .idVendor = IDVENDOR_FTDI, + .idProduct = 0x6014, + .iProduct = "Cairnsmore1", + .config = 1, + .timeout = ICARUS_TIMEOUT_MS, + .latency = LATENCY_STD, + INTINFO(cmr1_ints) }, + { + .drv = DRIVER_icarus, + .name = "CMR", + .ident = IDENT_CMR2, + .idVendor = IDVENDOR_FTDI, + .idProduct = 0x8350, + .iProduct = "Cairnsmore1", + .config = 1, + .timeout = ICARUS_TIMEOUT_MS, + .latency = LATENCY_STD, + INTINFO(cmr2_ints) }, +#endif +#ifdef USE_COINTERRA + { + .drv = DRIVER_cointerra, + .name = "CTA", + .ident = IDENT_CTA, + .idVendor = 0x1cbe, + .idProduct = 0x0003, + .config = 1, + .timeout = COINTERRA_TIMEOUT_MS, + .latency = LATENCY_STD, + INTINFO(cointerra_ints) }, +#endif +#ifdef USE_BMSC + { + .drv = DRIVER_bmsc, + .name = "ICA", + .ident = IDENT_ICA, + .idVendor = 0x067b, + .idProduct = 0x2303, + .config = 1, + .timeout = ICARUS_TIMEOUT_MS, + .latency = LATENCY_UNUSED, + INTINFO(ica_ints) }, + { + .drv = DRIVER_bmsc, + .name = "AMU", + .ident = IDENT_AMU, + .idVendor = 0x10c4, + .idProduct = 0xea60, + .config = 1, + .timeout = ICARUS_TIMEOUT_MS, + .latency = LATENCY_UNUSED, + INTINFO(amu_ints) }, + { + .drv = DRIVER_bmsc, + .name = "ANU", + .ident = IDENT_ANU, + .idVendor = 0x10c4, + .idProduct = 0xea60, + .config = 1, + .timeout = ICARUS_TIMEOUT_MS, + .latency = LATENCY_UNUSED, + INTINFO(amu_ints) }, + { + .drv = DRIVER_bmsc, + .name = "BLT", + .ident = IDENT_BLT, + .idVendor = IDVENDOR_FTDI, + .idProduct = 0x6001, + .iProduct = "FT232R USB UART", + .config = 1, + .timeout = ICARUS_TIMEOUT_MS, + .latency = LATENCY_STD, + INTINFO(llt_ints) }, + // For any that don't match the above "BLT" + { + .drv = DRIVER_bmsc, + .name = "LLT", + .ident = IDENT_LLT, + .idVendor = IDVENDOR_FTDI, + .idProduct = 0x6001, + .config = 1, + .timeout = ICARUS_TIMEOUT_MS, + .latency = LATENCY_STD, + INTINFO(llt_ints) }, + { + .drv = DRIVER_bmsc, + .name = "CMR", + .ident = IDENT_CMR1, + .idVendor = IDVENDOR_FTDI, + .idProduct = 0x6014, + .iProduct = "Cairnsmore1", + .config = 1, + .timeout = ICARUS_TIMEOUT_MS, + .latency = LATENCY_STD, + INTINFO(cmr1_ints) }, + { + .drv = DRIVER_bmsc, + .name = "CMR", + .ident = IDENT_CMR2, + .idVendor = IDVENDOR_FTDI, + .idProduct = 0x8350, + .iProduct = "Cairnsmore1", + .config = 1, + .timeout = ICARUS_TIMEOUT_MS, + .latency = LATENCY_STD, + INTINFO(cmr2_ints) }, +#endif +#ifdef USE_BITMAIN + { + .drv = DRIVER_bitmain, + .name = "BMM", + .ident = IDENT_BMM, +#ifdef WIN32 + .idVendor = IDVENDOR_FTDI, + .idProduct = 0x6001, +#else + .idVendor = 0x4254, + .idProduct = 0x4153, +#endif + .config = 1, + .timeout = BITMAIN_TIMEOUT_MS, + .latency = 10, + INTINFO(btm_ints) }, + { + .drv = DRIVER_bitmain, + .name = "BMS", + .ident = IDENT_BMS, + .idVendor = IDVENDOR_FTDI, + .idProduct = 0x6602, + .config = 1, + .timeout = BITMAIN_TIMEOUT_MS, + .latency = 10, + INTINFO(btm_ints) }, +#endif + { DRIVER_MAX, NULL, 0, 0, 0, NULL, NULL, 0, 0, 0, 0, NULL } +}; + +#define STRBUFLEN 256 +static const char *BLANK = ""; +static const char *space = " "; +static const char *nodatareturned = "no data returned "; + +#if 0 // enable USBDEBUG - only during development testing + static const char *debug_true_str = "true"; + static const char *debug_false_str = "false"; + static const char *nodevstr = "=NODEV"; + #define bool_str(boo) ((boo) ? debug_true_str : debug_false_str) + #define isnodev(err) (NODEV(err) ? nodevstr : BLANK) + #define USBDEBUG(fmt, ...) applog(LOG_WARNING, fmt, ##__VA_ARGS__) +#else + #define USBDEBUG(fmt, ...) +#endif + +// For device limits by driver +static struct driver_count { + int count; + int limit; +} drv_count[DRIVER_MAX]; + +// For device limits by list of bus/dev +static struct usb_busdev { + int bus_number; + int device_address; +#ifdef WIN32 + void *resource1; + void *resource2; +#else + int fd; +#endif +} *busdev; + +static int busdev_count = 0; + +// Total device limit +static int total_count = 0; +static int total_limit = 999999; + +struct usb_in_use_list { + struct usb_busdev in_use; + struct usb_in_use_list *prev; + struct usb_in_use_list *next; +}; + +// List of in use devices +static struct usb_in_use_list *in_use_head = NULL; +static struct usb_in_use_list *blacklist_head = NULL; + +struct resource_work { + bool lock; + const char *dname; + uint8_t bus_number; + uint8_t device_address; + struct resource_work *next; +}; + +// Pending work for the reslock thread +struct resource_work *res_work_head = NULL; + +struct resource_reply { + uint8_t bus_number; + uint8_t device_address; + bool got; + struct resource_reply *next; +}; + +// Replies to lock requests +struct resource_reply *res_reply_head = NULL; + +// Some stats need to always be defined +#define SEQ0 0 +#define SEQ1 1 + +// NONE must be 0 - calloced +#define MODE_NONE 0 +#define MODE_CTRL_READ (1 << 0) +#define MODE_CTRL_WRITE (1 << 1) +#define MODE_BULK_READ (1 << 2) +#define MODE_BULK_WRITE (1 << 3) + +// Set this to 0 to remove stats processing +#define DO_USB_STATS 1 + +static bool stats_initialised = false; + +#if DO_USB_STATS + +#define MODE_SEP_STR "+" +#define MODE_NONE_STR "X" +#define MODE_CTRL_READ_STR "cr" +#define MODE_CTRL_WRITE_STR "cw" +#define MODE_BULK_READ_STR "br" +#define MODE_BULK_WRITE_STR "bw" + +// One for each CMD, TIMEOUT, ERROR +struct cg_usb_stats_item { + uint64_t count; + double total_delay; + double min_delay; + double max_delay; + struct timeval first; + struct timeval last; +}; + +#define CMD_CMD 0 +#define CMD_TIMEOUT 1 +#define CMD_ERROR 2 + +// One for each C_CMD +struct cg_usb_stats_details { + int seq; + uint32_t modes; + struct cg_usb_stats_item item[CMD_ERROR+1]; +}; + +// One for each device +struct cg_usb_stats { + char *name; + int device_id; + struct cg_usb_stats_details *details; +}; + +static struct cg_usb_stats *usb_stats = NULL; +static int next_stat = USB_NOSTAT; + +#define SECTOMS(s) ((int)((s) * 1000)) + +#define USB_STATS(sgpu_, sta_, fin_, err_, mode_, cmd_, seq_, tmo_) \ + stats(sgpu_, sta_, fin_, err_, mode_, cmd_, seq_, tmo_) +#define STATS_TIMEVAL(tv_) cgtime(tv_) +#define USB_REJECT(sgpu_, mode_) rejected_inc(sgpu_, mode_) + +#else +#define USB_STATS(sgpu_, sta_, fin_, err_, mode_, cmd_, seq_, tmo_) +#define STATS_TIMEVAL(tv_) +#define USB_REJECT(sgpu_, mode_) + +#endif // DO_USB_STATS + +/* Create usb_commands array from USB_PARSE_COMMANDS macro in usbutils.h */ +char *usb_commands[] = { + USB_PARSE_COMMANDS(JUMPTABLE) + "Null" +}; + +#ifdef EOL +#undef EOL +#endif +#define EOL "\n" + +static const char *DESDEV = "Device"; +static const char *DESCON = "Config"; +static const char *DESSTR = "String"; +static const char *DESINT = "Interface"; +static const char *DESEP = "Endpoint"; +static const char *DESHID = "HID"; +static const char *DESRPT = "Report"; +static const char *DESPHY = "Physical"; +static const char *DESHUB = "Hub"; + +static const char *EPIN = "In: "; +static const char *EPOUT = "Out: "; +static const char *EPX = "?: "; + +static const char *CONTROL = "Control"; +static const char *ISOCHRONOUS_X = "Isochronous+?"; +static const char *ISOCHRONOUS_N_X = "Isochronous+None+?"; +static const char *ISOCHRONOUS_N_D = "Isochronous+None+Data"; +static const char *ISOCHRONOUS_N_F = "Isochronous+None+Feedback"; +static const char *ISOCHRONOUS_N_I = "Isochronous+None+Implicit"; +static const char *ISOCHRONOUS_A_X = "Isochronous+Async+?"; +static const char *ISOCHRONOUS_A_D = "Isochronous+Async+Data"; +static const char *ISOCHRONOUS_A_F = "Isochronous+Async+Feedback"; +static const char *ISOCHRONOUS_A_I = "Isochronous+Async+Implicit"; +static const char *ISOCHRONOUS_D_X = "Isochronous+Adaptive+?"; +static const char *ISOCHRONOUS_D_D = "Isochronous+Adaptive+Data"; +static const char *ISOCHRONOUS_D_F = "Isochronous+Adaptive+Feedback"; +static const char *ISOCHRONOUS_D_I = "Isochronous+Adaptive+Implicit"; +static const char *ISOCHRONOUS_S_X = "Isochronous+Sync+?"; +static const char *ISOCHRONOUS_S_D = "Isochronous+Sync+Data"; +static const char *ISOCHRONOUS_S_F = "Isochronous+Sync+Feedback"; +static const char *ISOCHRONOUS_S_I = "Isochronous+Sync+Implicit"; +static const char *BULK = "Bulk"; +static const char *INTERRUPT = "Interrupt"; +static const char *UNKNOWN = "Unknown"; + +static const char *destype(uint8_t bDescriptorType) +{ + switch (bDescriptorType) { + case LIBUSB_DT_DEVICE: + return DESDEV; + case LIBUSB_DT_CONFIG: + return DESCON; + case LIBUSB_DT_STRING: + return DESSTR; + case LIBUSB_DT_INTERFACE: + return DESINT; + case LIBUSB_DT_ENDPOINT: + return DESEP; + case LIBUSB_DT_HID: + return DESHID; + case LIBUSB_DT_REPORT: + return DESRPT; + case LIBUSB_DT_PHYSICAL: + return DESPHY; + case LIBUSB_DT_HUB: + return DESHUB; + } + return UNKNOWN; +} + +static const char *epdir(uint8_t bEndpointAddress) +{ + switch (bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) { + case LIBUSB_ENDPOINT_IN: + return EPIN; + case LIBUSB_ENDPOINT_OUT: + return EPOUT; + } + return EPX; +} + +static const char *epatt(uint8_t bmAttributes) +{ + switch(bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) { + case LIBUSB_TRANSFER_TYPE_CONTROL: + return CONTROL; + case LIBUSB_TRANSFER_TYPE_BULK: + return BULK; + case LIBUSB_TRANSFER_TYPE_INTERRUPT: + return INTERRUPT; + case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS: + switch(bmAttributes & LIBUSB_ISO_SYNC_TYPE_MASK) { + case LIBUSB_ISO_SYNC_TYPE_NONE: + switch(bmAttributes & LIBUSB_ISO_USAGE_TYPE_MASK) { + case LIBUSB_ISO_USAGE_TYPE_DATA: + return ISOCHRONOUS_N_D; + case LIBUSB_ISO_USAGE_TYPE_FEEDBACK: + return ISOCHRONOUS_N_F; + case LIBUSB_ISO_USAGE_TYPE_IMPLICIT: + return ISOCHRONOUS_N_I; + } + return ISOCHRONOUS_N_X; + case LIBUSB_ISO_SYNC_TYPE_ASYNC: + switch(bmAttributes & LIBUSB_ISO_USAGE_TYPE_MASK) { + case LIBUSB_ISO_USAGE_TYPE_DATA: + return ISOCHRONOUS_A_D; + case LIBUSB_ISO_USAGE_TYPE_FEEDBACK: + return ISOCHRONOUS_A_F; + case LIBUSB_ISO_USAGE_TYPE_IMPLICIT: + return ISOCHRONOUS_A_I; + } + return ISOCHRONOUS_A_X; + case LIBUSB_ISO_SYNC_TYPE_ADAPTIVE: + switch(bmAttributes & LIBUSB_ISO_USAGE_TYPE_MASK) { + case LIBUSB_ISO_USAGE_TYPE_DATA: + return ISOCHRONOUS_D_D; + case LIBUSB_ISO_USAGE_TYPE_FEEDBACK: + return ISOCHRONOUS_D_F; + case LIBUSB_ISO_USAGE_TYPE_IMPLICIT: + return ISOCHRONOUS_D_I; + } + return ISOCHRONOUS_D_X; + case LIBUSB_ISO_SYNC_TYPE_SYNC: + switch(bmAttributes & LIBUSB_ISO_USAGE_TYPE_MASK) { + case LIBUSB_ISO_USAGE_TYPE_DATA: + return ISOCHRONOUS_S_D; + case LIBUSB_ISO_USAGE_TYPE_FEEDBACK: + return ISOCHRONOUS_S_F; + case LIBUSB_ISO_USAGE_TYPE_IMPLICIT: + return ISOCHRONOUS_S_I; + } + return ISOCHRONOUS_S_X; + } + return ISOCHRONOUS_X; + } + + return UNKNOWN; +} + +static void append(char **buf, char *append, size_t *off, size_t *len) +{ + int new = strlen(append); + if ((new + *off) >= *len) + { + *len *= 2; + *buf = realloc(*buf, *len); + if (unlikely(!*buf)) + quit(1, "USB failed to realloc append"); + } + + strcpy(*buf + *off, append); + *off += new; +} + +static bool setgetdes(ssize_t count, libusb_device *dev, struct libusb_device_handle *handle, struct libusb_config_descriptor **config, int cd, char **buf, size_t *off, size_t *len) +{ + char tmp[512]; + int err; + + err = libusb_set_configuration(handle, cd); + if (err) { + snprintf(tmp, sizeof(tmp), EOL " ** dev %d: Failed to set config descriptor to %d, err %d", + (int)count, cd, err); + append(buf, tmp, off, len); + return false; + } + + err = libusb_get_active_config_descriptor(dev, config); + if (err) { + snprintf(tmp, sizeof(tmp), EOL " ** dev %d: Failed to get active config descriptor set to %d, err %d", + (int)count, cd, err); + append(buf, tmp, off, len); + return false; + } + + snprintf(tmp, sizeof(tmp), EOL " ** dev %d: Set & Got active config descriptor to %d, err %d", + (int)count, cd, err); + append(buf, tmp, off, len); + return true; +} + +static void usb_full(ssize_t *count, libusb_device *dev, char **buf, size_t *off, size_t *len, int level) +{ + struct libusb_device_descriptor desc; + uint8_t bus_number; + uint8_t device_address; + struct libusb_device_handle *handle; + struct libusb_config_descriptor *config; + const struct libusb_interface_descriptor *idesc; + const struct libusb_endpoint_descriptor *epdesc; + unsigned char man[STRBUFLEN+1]; + unsigned char prod[STRBUFLEN+1]; + unsigned char ser[STRBUFLEN+1]; + char tmp[512]; + int err, i, j, k; + + err = libusb_get_device_descriptor(dev, &desc); + if (opt_usb_list_all && err) { + snprintf(tmp, sizeof(tmp), EOL ".USB dev %d: Failed to get descriptor, err %d", + (int)(++(*count)), err); + append(buf, tmp, off, len); + return; + } + + bus_number = libusb_get_bus_number(dev); + device_address = libusb_get_device_address(dev); + + if (!opt_usb_list_all) { + bool known = false; + + for (i = 0; find_dev[i].drv != DRIVER_MAX; i++) + if ((find_dev[i].idVendor == desc.idVendor) && + (find_dev[i].idProduct == desc.idProduct)) { + known = true; + break; + } + + if (!known) + return; + } + + (*count)++; + + if (level == 0) { + snprintf(tmp, sizeof(tmp), EOL ".USB dev %d: Bus %d Device %d ID: %04x:%04x", + (int)(*count), (int)bus_number, (int)device_address, + desc.idVendor, desc.idProduct); + } else { + snprintf(tmp, sizeof(tmp), EOL ".USB dev %d: Bus %d Device %d Device Descriptor:" EOL "\tLength: %d" EOL + "\tDescriptor Type: %s" EOL "\tUSB: %04x" EOL "\tDeviceClass: %d" EOL + "\tDeviceSubClass: %d" EOL "\tDeviceProtocol: %d" EOL "\tMaxPacketSize0: %d" EOL + "\tidVendor: %04x" EOL "\tidProduct: %04x" EOL "\tDeviceRelease: %x" EOL + "\tNumConfigurations: %d", + (int)(*count), (int)bus_number, (int)device_address, + (int)(desc.bLength), destype(desc.bDescriptorType), + desc.bcdUSB, (int)(desc.bDeviceClass), (int)(desc.bDeviceSubClass), + (int)(desc.bDeviceProtocol), (int)(desc.bMaxPacketSize0), + desc.idVendor, desc.idProduct, desc.bcdDevice, + (int)(desc.bNumConfigurations)); + } + append(buf, tmp, off, len); + + err = libusb_open(dev, &handle); + if (err) { + snprintf(tmp, sizeof(tmp), EOL " ** dev %d: Failed to open, err %d", (int)(*count), err); + append(buf, tmp, off, len); + return; + } + + err = libusb_get_string_descriptor_ascii(handle, desc.iManufacturer, man, STRBUFLEN); + if (err < 0) + snprintf((char *)man, sizeof(man), "** err:(%d) %s", err, libusb_error_name(err)); + + err = libusb_get_string_descriptor_ascii(handle, desc.iProduct, prod, STRBUFLEN); + if (err < 0) + snprintf((char *)prod, sizeof(prod), "** err:(%d) %s", err, libusb_error_name(err)); + + if (level == 0) { + libusb_close(handle); + snprintf(tmp, sizeof(tmp), EOL " Manufacturer: '%s'" EOL " Product: '%s'", man, prod); + append(buf, tmp, off, len); + return; + } + + if (libusb_kernel_driver_active(handle, 0) == 1) { + snprintf(tmp, sizeof(tmp), EOL " * dev %d: kernel attached", (int)(*count)); + append(buf, tmp, off, len); + } + + err = libusb_get_active_config_descriptor(dev, &config); + if (err) { + if (!setgetdes(*count, dev, handle, &config, 1, buf, off, len) + && !setgetdes(*count, dev, handle, &config, 0, buf, off, len)) { + libusb_close(handle); + snprintf(tmp, sizeof(tmp), EOL " ** dev %d: Failed to set config descriptor to %d or %d", + (int)(*count), 1, 0); + append(buf, tmp, off, len); + return; + } + } + + snprintf(tmp, sizeof(tmp), EOL " dev %d: Active Config:" EOL "\tDescriptorType: %s" EOL + "\tNumInterfaces: %d" EOL "\tConfigurationValue: %d" EOL + "\tAttributes: %d" EOL "\tMaxPower: %d", + (int)(*count), destype(config->bDescriptorType), + (int)(config->bNumInterfaces), (int)(config->iConfiguration), + (int)(config->bmAttributes), (int)(config->MaxPower)); + append(buf, tmp, off, len); + + for (i = 0; i < (int)(config->bNumInterfaces); i++) { + for (j = 0; j < config->interface[i].num_altsetting; j++) { + idesc = &(config->interface[i].altsetting[j]); + + snprintf(tmp, sizeof(tmp), EOL " _dev %d: Interface Descriptor %d:" EOL + "\tDescriptorType: %s" EOL "\tInterfaceNumber: %d" EOL + "\tNumEndpoints: %d" EOL "\tInterfaceClass: %d" EOL + "\tInterfaceSubClass: %d" EOL "\tInterfaceProtocol: %d", + (int)(*count), j, destype(idesc->bDescriptorType), + (int)(idesc->bInterfaceNumber), + (int)(idesc->bNumEndpoints), + (int)(idesc->bInterfaceClass), + (int)(idesc->bInterfaceSubClass), + (int)(idesc->bInterfaceProtocol)); + append(buf, tmp, off, len); + + for (k = 0; k < (int)(idesc->bNumEndpoints); k++) { + epdesc = &(idesc->endpoint[k]); + + snprintf(tmp, sizeof(tmp), EOL " __dev %d: Interface %d Endpoint %d:" EOL + "\tDescriptorType: %s" EOL + "\tEndpointAddress: %s0x%x" EOL + "\tAttributes: %s" EOL "\tMaxPacketSize: %d" EOL + "\tInterval: %d" EOL "\tRefresh: %d", + (int)(*count), (int)(idesc->bInterfaceNumber), k, + destype(epdesc->bDescriptorType), + epdir(epdesc->bEndpointAddress), + (int)(epdesc->bEndpointAddress), + epatt(epdesc->bmAttributes), + epdesc->wMaxPacketSize, + (int)(epdesc->bInterval), + (int)(epdesc->bRefresh)); + append(buf, tmp, off, len); + } + } + } + + libusb_free_config_descriptor(config); + config = NULL; + + err = libusb_get_string_descriptor_ascii(handle, desc.iSerialNumber, ser, STRBUFLEN); + if (err < 0) + snprintf((char *)ser, sizeof(ser), "** err:(%d) %s", err, libusb_error_name(err)); + + snprintf(tmp, sizeof(tmp), EOL " dev %d: More Info:" EOL "\tManufacturer: '%s'" EOL + "\tProduct: '%s'" EOL "\tSerial '%s'", + (int)(*count), man, prod, ser); + append(buf, tmp, off, len); + + libusb_close(handle); +} + +// Function to dump all USB devices +void usb_all(int level) +{ + libusb_device **list; + ssize_t count, i, j; + char *buf; + size_t len, off; + + count = libusb_get_device_list(NULL, &list); + if (count < 0) { + applog(LOG_ERR, "USB all: failed, err:(%d) %s", (int)count, libusb_error_name((int)count)); + return; + } + + if (count == 0) + applog(LOG_WARNING, "USB all: found no devices"); + else + { + len = 10000; + buf = malloc(len+1); + if (unlikely(!buf)) + quit(1, "USB failed to malloc buf in usb_all"); + + sprintf(buf, "USB all: found %d devices", (int)count); + off = strlen(buf); + + if (!opt_usb_list_all) + append(&buf, " - listing known devices", &off, &len); + + j = -1; + for (i = 0; i < count; i++) + usb_full(&j, list[i], &buf, &off, &len, level); + + _applog(LOG_WARNING, buf, false); + + free(buf); + + if (j == -1) + applog(LOG_WARNING, "No known USB devices"); + else + applog(LOG_WARNING, "%d %sUSB devices", + (int)(++j), opt_usb_list_all ? BLANK : "known "); + + } + + libusb_free_device_list(list, 1); +} + +static void cgusb_check_init() +{ + mutex_lock(&cgusb_lock); + + if (stats_initialised == false) { + // N.B. environment LIBUSB_DEBUG also sets libusb_set_debug() + if (opt_usbdump >= 0) { + libusb_set_debug(NULL, opt_usbdump); + usb_all(opt_usbdump); + } + stats_initialised = true; + } + + mutex_unlock(&cgusb_lock); +} + +const char *usb_cmdname(enum usb_cmds cmd) +{ + cgusb_check_init(); + + return usb_commands[cmd]; +} + +void usb_applog(struct cgpu_info *cgpu, enum usb_cmds cmd, char *msg, int amount, int err) +{ + if (msg && !*msg) + msg = NULL; + + if (!msg && amount == 0 && err == LIBUSB_SUCCESS) + msg = (char *)nodatareturned; + + applog(LOG_ERR, "%s%i: %s failed%s%s (err=%d amt=%d)", + cgpu->drv->name, cgpu->device_id, + usb_cmdname(cmd), + msg ? space : BLANK, msg ? msg : BLANK, + err, amount); +} + +#ifdef WIN32 +static void in_use_store_ress(uint8_t bus_number, uint8_t device_address, void *resource1, void *resource2) +{ + struct usb_in_use_list *in_use_tmp; + bool found = false, empty = true; + + mutex_lock(&cgusb_lock); + in_use_tmp = in_use_head; + while (in_use_tmp) { + if (in_use_tmp->in_use.bus_number == (int)bus_number && + in_use_tmp->in_use.device_address == (int)device_address) { + found = true; + + if (in_use_tmp->in_use.resource1) + empty = false; + in_use_tmp->in_use.resource1 = resource1; + + if (in_use_tmp->in_use.resource2) + empty = false; + in_use_tmp->in_use.resource2 = resource2; + + break; + } + in_use_tmp = in_use_tmp->next; + } + mutex_unlock(&cgusb_lock); + + if (found == false) + applog(LOG_ERR, "FAIL: USB store_ress not found (%d:%d)", + (int)bus_number, (int)device_address); + + if (empty == false) + applog(LOG_ERR, "FAIL: USB store_ress not empty (%d:%d)", + (int)bus_number, (int)device_address); +} + +static void in_use_get_ress(uint8_t bus_number, uint8_t device_address, void **resource1, void **resource2) +{ + struct usb_in_use_list *in_use_tmp; + bool found = false, empty = false; + + mutex_lock(&cgusb_lock); + in_use_tmp = in_use_head; + while (in_use_tmp) { + if (in_use_tmp->in_use.bus_number == (int)bus_number && + in_use_tmp->in_use.device_address == (int)device_address) { + found = true; + + if (!in_use_tmp->in_use.resource1) + empty = true; + *resource1 = in_use_tmp->in_use.resource1; + in_use_tmp->in_use.resource1 = NULL; + + if (!in_use_tmp->in_use.resource2) + empty = true; + *resource2 = in_use_tmp->in_use.resource2; + in_use_tmp->in_use.resource2 = NULL; + + break; + } + in_use_tmp = in_use_tmp->next; + } + mutex_unlock(&cgusb_lock); + + if (found == false) + applog(LOG_ERR, "FAIL: USB get_lock not found (%d:%d)", + (int)bus_number, (int)device_address); + + if (empty == true) + applog(LOG_ERR, "FAIL: USB get_lock empty (%d:%d)", + (int)bus_number, (int)device_address); +} +#else + +static void in_use_store_fd(uint8_t bus_number, uint8_t device_address, int fd) +{ + struct usb_in_use_list *in_use_tmp; + bool found = false; + + mutex_lock(&cgusb_lock); + in_use_tmp = in_use_head; + while (in_use_tmp) { + if (in_use_tmp->in_use.bus_number == (int)bus_number && + in_use_tmp->in_use.device_address == (int)device_address) { + found = true; + in_use_tmp->in_use.fd = fd; + break; + } + in_use_tmp = in_use_tmp->next; + } + mutex_unlock(&cgusb_lock); + + if (found == false) { + applog(LOG_ERR, "FAIL: USB store_fd not found (%d:%d)", + (int)bus_number, (int)device_address); + } +} + +static int in_use_get_fd(uint8_t bus_number, uint8_t device_address) +{ + struct usb_in_use_list *in_use_tmp; + bool found = false; + int fd = -1; + + mutex_lock(&cgusb_lock); + in_use_tmp = in_use_head; + while (in_use_tmp) { + if (in_use_tmp->in_use.bus_number == (int)bus_number && + in_use_tmp->in_use.device_address == (int)device_address) { + found = true; + fd = in_use_tmp->in_use.fd; + break; + } + in_use_tmp = in_use_tmp->next; + } + mutex_unlock(&cgusb_lock); + + if (found == false) { + applog(LOG_ERR, "FAIL: USB get_lock not found (%d:%d)", + (int)bus_number, (int)device_address); + } + return fd; +} +#endif + +static bool _in_use(struct usb_in_use_list *head, uint8_t bus_number, + uint8_t device_address) +{ + struct usb_in_use_list *in_use_tmp; + bool ret = false; + + in_use_tmp = head; + while (in_use_tmp) { + if (in_use_tmp->in_use.bus_number == (int)bus_number && + in_use_tmp->in_use.device_address == (int)device_address) { + ret = true; + break; + } + in_use_tmp = in_use_tmp->next; + if (in_use_tmp == head) + break; + } + return ret; +} + +static bool __is_in_use(uint8_t bus_number, uint8_t device_address) +{ + if (_in_use(in_use_head, bus_number, device_address)) + return true; + if (_in_use(blacklist_head, bus_number, device_address)) + return true; + return false; +} + +static bool is_in_use_bd(uint8_t bus_number, uint8_t device_address) +{ + bool ret; + + mutex_lock(&cgusb_lock); + ret = __is_in_use(bus_number, device_address); + mutex_unlock(&cgusb_lock); + return ret; +} + +static bool is_in_use(libusb_device *dev) +{ + return is_in_use_bd(libusb_get_bus_number(dev), libusb_get_device_address(dev)); +} + +static bool how_in_use(uint8_t bus_number, uint8_t device_address, bool *blacklisted) +{ + bool ret; + mutex_lock(&cgusb_lock); + ret = _in_use(in_use_head, bus_number, device_address); + if (!ret) { + if (_in_use(blacklist_head, bus_number, device_address)) + *blacklisted = true; + } + mutex_unlock(&cgusb_lock); + + return ret; +} + +void usb_list(void) +{ + struct libusb_device_descriptor desc; + struct libusb_device_handle *handle; + uint8_t bus_number; + uint8_t device_address; + libusb_device **list; + ssize_t count, i, j; + int err, total = 0; + + count = libusb_get_device_list(NULL, &list); + if (count < 0) { + applog(LOG_ERR, "USB list: failed, err:(%d) %s", (int)count, libusb_error_name((int)count)); + return; + } + if (count == 0) { + applog(LOG_WARNING, "USB list: found no devices"); + return; + } + for (i = 0; i < count; i++) { + bool known = false, blacklisted = false, active; + unsigned char manuf[256], prod[256]; + libusb_device *dev = list[i]; + + err = libusb_get_device_descriptor(dev, &desc); + if (err) { + applog(LOG_WARNING, "USB list: Failed to get descriptor %d", (int)i); + break; + } + + bus_number = libusb_get_bus_number(dev); + device_address = libusb_get_device_address(dev); + + for (j = 0; find_dev[j].drv != DRIVER_MAX; j++) { + if ((find_dev[j].idVendor == desc.idVendor) && + (find_dev[j].idProduct == desc.idProduct)) { + known = true; + break; + } + } + if (!known) + continue; + + err = libusb_open(dev, &handle); + if (err) { + applog(LOG_WARNING, "USB list: Failed to open %d", (int)i); + break; + } + libusb_get_string_descriptor_ascii(handle, desc.iManufacturer, manuf, 255); + libusb_get_string_descriptor_ascii(handle, desc.iProduct, prod, 255); + total++; + active = how_in_use(bus_number, device_address, &blacklisted); + simplelog(LOG_WARNING, "Bus %u Device %u ID: %04x:%04x %s %s %sactive %s", + bus_number, device_address, desc.idVendor, desc.idProduct, + manuf, prod, active ? "" : "in", blacklisted ? "blacklisted" : ""); + } + libusb_free_device_list(list, 1); + simplelog(LOG_WARNING, "%d total known USB device%s", total, total > 1 ? "s": ""); +} + +static void add_in_use(uint8_t bus_number, uint8_t device_address, bool blacklist) +{ + struct usb_in_use_list *in_use_tmp, **head; + bool found = false; + + mutex_lock(&cgusb_lock); + if (unlikely(!blacklist && __is_in_use(bus_number, device_address))) { + found = true; + goto nofway; + } + if (blacklist) + head = &blacklist_head; + else + head = &in_use_head; + + in_use_tmp = calloc(1, sizeof(*in_use_tmp)); + if (unlikely(!in_use_tmp)) + quit(1, "USB failed to calloc in_use_tmp"); + in_use_tmp->in_use.bus_number = (int)bus_number; + in_use_tmp->in_use.device_address = (int)device_address; + in_use_tmp->next = in_use_head; + if (*head) + (*head)->prev = in_use_tmp; + *head = in_use_tmp; +nofway: + mutex_unlock(&cgusb_lock); + + if (found) + applog(LOG_ERR, "FAIL: USB add already in use (%d:%d)", + (int)bus_number, (int)device_address); +} + +static void __remove_in_use(uint8_t bus_number, uint8_t device_address, bool blacklist) +{ + struct usb_in_use_list *in_use_tmp, **head; + bool found = false; + + mutex_lock(&cgusb_lock); + if (blacklist) + head = &blacklist_head; + else + head = &in_use_head; + + in_use_tmp = *head; + while (in_use_tmp) { + if (in_use_tmp->in_use.bus_number == (int)bus_number && + in_use_tmp->in_use.device_address == (int)device_address) { + found = true; + if (in_use_tmp == *head) { + *head = (*head)->next; + if (*head) + (*head)->prev = NULL; + } else { + in_use_tmp->prev->next = in_use_tmp->next; + if (in_use_tmp->next) + in_use_tmp->next->prev = in_use_tmp->prev; + } + free(in_use_tmp); + break; + } + in_use_tmp = in_use_tmp->next; + if (in_use_tmp == *head) + break; + } + + mutex_unlock(&cgusb_lock); + + if (!found) { + applog(LOG_ERR, "FAIL: USB remove not already in use (%d:%d)", + (int)bus_number, (int)device_address); + } +} + +static void remove_in_use(uint8_t bus_number, uint8_t device_address) +{ + __remove_in_use(bus_number, device_address, false); +} + +static bool cgminer_usb_lock_bd(struct device_drv *drv, uint8_t bus_number, uint8_t device_address) +{ + struct resource_work *res_work; + bool ret; + + applog(LOG_DEBUG, "USB lock %s %d-%d", drv->dname, (int)bus_number, (int)device_address); + + res_work = calloc(1, sizeof(*res_work)); + if (unlikely(!res_work)) + quit(1, "USB failed to calloc lock res_work"); + res_work->lock = true; + res_work->dname = (const char *)(drv->dname); + res_work->bus_number = bus_number; + res_work->device_address = device_address; + + mutex_lock(&cgusbres_lock); + res_work->next = res_work_head; + res_work_head = res_work; + mutex_unlock(&cgusbres_lock); + + cgsem_post(&usb_resource_sem); + + // TODO: add a timeout fail - restart the resource thread? + while (true) { + cgsleep_ms(50); + + mutex_lock(&cgusbres_lock); + if (res_reply_head) { + struct resource_reply *res_reply_prev = NULL; + struct resource_reply *res_reply = res_reply_head; + while (res_reply) { + if (res_reply->bus_number == bus_number && + res_reply->device_address == device_address) { + + if (res_reply_prev) + res_reply_prev->next = res_reply->next; + else + res_reply_head = res_reply->next; + + mutex_unlock(&cgusbres_lock); + + ret = res_reply->got; + + free(res_reply); + + return ret; + } + res_reply_prev = res_reply; + res_reply = res_reply->next; + } + } + mutex_unlock(&cgusbres_lock); + } +} + +static bool cgminer_usb_lock(struct device_drv *drv, libusb_device *dev) +{ + return cgminer_usb_lock_bd(drv, libusb_get_bus_number(dev), libusb_get_device_address(dev)); +} + +static void cgminer_usb_unlock_bd(struct device_drv *drv, uint8_t bus_number, uint8_t device_address) +{ + struct resource_work *res_work; + + applog(LOG_DEBUG, "USB unlock %s %d-%d", drv->dname, (int)bus_number, (int)device_address); + + res_work = calloc(1, sizeof(*res_work)); + if (unlikely(!res_work)) + quit(1, "USB failed to calloc unlock res_work"); + res_work->lock = false; + res_work->dname = (const char *)(drv->dname); + res_work->bus_number = bus_number; + res_work->device_address = device_address; + + mutex_lock(&cgusbres_lock); + res_work->next = res_work_head; + res_work_head = res_work; + mutex_unlock(&cgusbres_lock); + + cgsem_post(&usb_resource_sem); + + return; +} + +static void cgminer_usb_unlock(struct device_drv *drv, libusb_device *dev) +{ + cgminer_usb_unlock_bd(drv, libusb_get_bus_number(dev), libusb_get_device_address(dev)); +} + +static struct cg_usb_device *free_cgusb(struct cg_usb_device *cgusb) +{ + applog(LOG_DEBUG, "USB free %s", cgusb->found->name); + + if (cgusb->serial_string && cgusb->serial_string != BLANK) + free(cgusb->serial_string); + + if (cgusb->manuf_string && cgusb->manuf_string != BLANK) + free(cgusb->manuf_string); + + if (cgusb->prod_string && cgusb->prod_string != BLANK) + free(cgusb->prod_string); + + if (cgusb->descriptor) + free(cgusb->descriptor); + + free(cgusb->found); + + free(cgusb); + + return NULL; +} + +static void _usb_uninit(struct cgpu_info *cgpu) +{ + int ifinfo; + + // May have happened already during a failed initialisation + // if release_cgpu() was called due to a USB NODEV(err) + if (!cgpu->usbdev) + return; + + applog(LOG_DEBUG, "USB uninit %s%i", + cgpu->drv->name, cgpu->device_id); + + if (cgpu->usbdev->handle) { + for (ifinfo = cgpu->usbdev->found->intinfo_count - 1; ifinfo >= 0; ifinfo--) { + libusb_release_interface(cgpu->usbdev->handle, + THISIF(cgpu->usbdev->found, ifinfo)); + } +#ifdef LINUX + libusb_attach_kernel_driver(cgpu->usbdev->handle, THISIF(cgpu->usbdev->found, ifinfo)); +#endif + cg_wlock(&cgusb_fd_lock); + libusb_close(cgpu->usbdev->handle); + cgpu->usbdev->handle = NULL; + cg_wunlock(&cgusb_fd_lock); + } + cgpu->usbdev = free_cgusb(cgpu->usbdev); +} + +void usb_uninit(struct cgpu_info *cgpu) +{ + int pstate; + + DEVWLOCK(cgpu, pstate); + + _usb_uninit(cgpu); + + DEVWUNLOCK(cgpu, pstate); +} + +/* We have dropped the read devlock before entering this function but we pick + * up the write lock to prevent any attempts to work on dereferenced code once + * the nodev flag has been set. */ +static bool __release_cgpu(struct cgpu_info *cgpu) +{ + struct cg_usb_device *cgusb = cgpu->usbdev; + bool initted = cgpu->usbinfo.initialised; + struct cgpu_info *lookcgpu; + int i; + + // It has already been done + if (cgpu->usbinfo.nodev) + return false; + + applog(LOG_DEBUG, "USB release %s%i", + cgpu->drv->name, cgpu->device_id); + + if (initted) { + zombie_devs++; + total_count--; + drv_count[cgpu->drv->drv_id].count--; + } + + cgpu->usbinfo.nodev = true; + cgpu->usbinfo.nodev_count++; + cgtime(&cgpu->usbinfo.last_nodev); + + // Any devices sharing the same USB device should be marked also + for (i = 0; i < total_devices; i++) { + lookcgpu = get_devices(i); + if (lookcgpu != cgpu && lookcgpu->usbdev == cgusb) { + if (initted) { + total_count--; + drv_count[lookcgpu->drv->drv_id].count--; + } + + lookcgpu->usbinfo.nodev = true; + lookcgpu->usbinfo.nodev_count++; + cg_memcpy(&(lookcgpu->usbinfo.last_nodev), + &(cgpu->usbinfo.last_nodev), sizeof(struct timeval)); + lookcgpu->usbdev = NULL; + } + } + + _usb_uninit(cgpu); + return true; +} + +static void release_cgpu(struct cgpu_info *cgpu) +{ + if (__release_cgpu(cgpu)) + cgminer_usb_unlock_bd(cgpu->drv, cgpu->usbinfo.bus_number, cgpu->usbinfo.device_address); +} + +void blacklist_cgpu(struct cgpu_info *cgpu) +{ + if (cgpu->blacklisted) { + applog(LOG_WARNING, "Device already blacklisted"); + return; + } + cgpu->blacklisted = true; + add_in_use(cgpu->usbinfo.bus_number, cgpu->usbinfo.device_address, true); + if (__release_cgpu(cgpu)) + cgminer_usb_unlock_bd(cgpu->drv, cgpu->usbinfo.bus_number, cgpu->usbinfo.device_address); +} + +void whitelist_cgpu(struct cgpu_info *cgpu) +{ + if (!cgpu->blacklisted) { + applog(LOG_WARNING, "Device not blacklisted"); + return; + } + __remove_in_use(cgpu->usbinfo.bus_number, cgpu->usbinfo.device_address, true); + cgpu->blacklisted = false; +} + +/* + * Force a NODEV on a device so it goes back to hotplug + */ +void usb_nodev(struct cgpu_info *cgpu) +{ + int pstate; + + DEVWLOCK(cgpu, pstate); + + release_cgpu(cgpu); + + DEVWUNLOCK(cgpu, pstate); +} + +/* + * Use the same usbdev thus locking is across all related devices + */ +struct cgpu_info *usb_copy_cgpu(struct cgpu_info *orig) +{ + struct cgpu_info *copy; + int pstate; + + DEVWLOCK(orig, pstate); + + copy = calloc(1, sizeof(*copy)); + if (unlikely(!copy)) + quit(1, "Failed to calloc cgpu for %s in usb_copy_cgpu", orig->drv->dname); + + copy->name = orig->name; + copy->drv = copy_drv(orig->drv); + copy->deven = orig->deven; + copy->threads = orig->threads; + + copy->usbdev = orig->usbdev; + + cg_memcpy(&(copy->usbinfo), &(orig->usbinfo), sizeof(copy->usbinfo)); + + copy->usbinfo.nodev = (copy->usbdev == NULL); + + DEVWUNLOCK(orig, pstate); + + return copy; +} + +struct cgpu_info *usb_alloc_cgpu(struct device_drv *drv, int threads) +{ + struct cgpu_info *cgpu = calloc(1, sizeof(*cgpu)); + + if (unlikely(!cgpu)) + quit(1, "Failed to calloc cgpu for %s in usb_alloc_cgpu", drv->dname); + + cgpu->drv = drv; + cgpu->deven = DEV_ENABLED; + cgpu->threads = threads; + + cgpu->usbinfo.nodev = true; + + cglock_init(&cgpu->usbinfo.devlock); + + return cgpu; +} + +struct cgpu_info *usb_free_cgpu(struct cgpu_info *cgpu) +{ + if (cgpu->drv->copy) + free(cgpu->drv); + + free(cgpu->device_path); + + free(cgpu); + + return NULL; +} + +#define USB_INIT_FAIL 0 +#define USB_INIT_OK 1 +#define USB_INIT_IGNORE 2 + +static int _usb_init(struct cgpu_info *cgpu, struct libusb_device *dev, struct usb_find_devices *found) +{ + unsigned char man[STRBUFLEN+1], prod[STRBUFLEN+1]; + struct cg_usb_device *cgusb = NULL; + struct libusb_config_descriptor *config = NULL; + const struct libusb_interface_descriptor *idesc; + const struct libusb_endpoint_descriptor *epdesc; + unsigned char strbuf[STRBUFLEN+1]; + char devpath[32]; + char devstr[STRBUFLEN+1]; + int err, ifinfo, epinfo, alt, epnum, pstate; + int bad = USB_INIT_FAIL; + int cfg, claimed = 0, i; + + DEVWLOCK(cgpu, pstate); + + cgpu->usbinfo.bus_number = libusb_get_bus_number(dev); + cgpu->usbinfo.device_address = libusb_get_device_address(dev); + + if (found->intinfo_count > 1) { + snprintf(devpath, sizeof(devpath), "%d:%d-i%d", + (int)(cgpu->usbinfo.bus_number), + (int)(cgpu->usbinfo.device_address), + THISIF(found, 0)); + } else { + snprintf(devpath, sizeof(devpath), "%d:%d", + (int)(cgpu->usbinfo.bus_number), + (int)(cgpu->usbinfo.device_address)); + } + + cgpu->device_path = strdup(devpath); + + snprintf(devstr, sizeof(devstr), "- %s device %s", found->name, devpath); + + cgusb = calloc(1, sizeof(*cgusb)); + if (unlikely(!cgusb)) + quit(1, "USB failed to calloc _usb_init cgusb"); + cgusb->found = found; + + if (found->idVendor == IDVENDOR_FTDI) + cgusb->usb_type = USB_TYPE_FTDI; + + cgusb->ident = found->ident; + + cgusb->descriptor = calloc(1, sizeof(*(cgusb->descriptor))); + if (unlikely(!cgusb->descriptor)) + quit(1, "USB failed to calloc _usb_init cgusb descriptor"); + + err = libusb_get_device_descriptor(dev, cgusb->descriptor); + if (err) { + applog(LOG_DEBUG, + "USB init failed to get descriptor, err %d %s", + err, devstr); + goto dame; + } + + cg_wlock(&cgusb_fd_lock); + err = libusb_open(dev, &(cgusb->handle)); + cg_wunlock(&cgusb_fd_lock); + if (err) { + switch (err) { + case LIBUSB_ERROR_ACCESS: + applog(LOG_ERR, + "USB init, open device failed, err %d, " + "you don't have privilege to access %s", + err, devstr); + applog(LOG_ERR, "See README file included for help"); + break; +#ifdef WIN32 + // Windows specific message + case LIBUSB_ERROR_NOT_SUPPORTED: + applog(LOG_ERR, "USB init, open device failed, err %d, ", err); + applog(LOG_ERR, "You need to install a WinUSB driver for %s", devstr); + applog(LOG_ERR, "And associate %s with WinUSB using zadig", devstr); + applog(LOG_ERR, "See README.txt file included for help"); + break; +#endif + default: + applog(LOG_DEBUG, + "USB init, open failed, err %d %s", + err, devstr); + } + goto dame; + } + +#ifdef LINUX + for (ifinfo = 0; ifinfo < found->intinfo_count; ifinfo++) { + if (libusb_kernel_driver_active(cgusb->handle, THISIF(found, ifinfo)) == 1) { + applog(LOG_DEBUG, "USB init, kernel attached ... %s", devstr); + err = libusb_detach_kernel_driver(cgusb->handle, THISIF(found, ifinfo)); + if (err == 0) { + applog(LOG_DEBUG, + "USB init, kernel detached ifinfo %d interface %d" + " successfully %s", + ifinfo, THISIF(found, ifinfo), devstr); + } else { + applog(LOG_WARNING, + "USB init, kernel detach ifinfo %d interface %d failed," + " err %d in use? %s", + ifinfo, THISIF(found, ifinfo), err, devstr); + goto nokernel; + } + } + } +#endif + + err = libusb_get_string_descriptor_ascii(cgusb->handle, + cgusb->descriptor->iManufacturer, + man, STRBUFLEN); + if (err < 0) { + applog(LOG_DEBUG, + "USB init, failed to get iManufacturer, err %d %s", + err, devstr); + goto cldame; + } + if (found->iManufacturer) { + if (strcasecmp((char *)man, found->iManufacturer)) { + applog(LOG_DEBUG, "USB init, iManufacturer mismatch %s", + devstr); + applog(LOG_DEBUG, "Found %s vs %s", man, found->iManufacturer); + bad = USB_INIT_IGNORE; + goto cldame; + } + } else { + for (i = 0; find_dev[i].drv != DRIVER_MAX; i++) { + const char *iManufacturer = find_dev[i].iManufacturer; + /* If other drivers has an iManufacturer set that match, + * don't try to claim this device. */ + + if (!iManufacturer) + continue; + if (!strcasecmp((char *)man, iManufacturer)) { + applog(LOG_DEBUG, "USB init, alternative iManufacturer match %s", + devstr); + applog(LOG_DEBUG, "Found %s", iManufacturer); + bad = USB_INIT_IGNORE; + goto cldame; + } + } + } + + err = libusb_get_string_descriptor_ascii(cgusb->handle, + cgusb->descriptor->iProduct, + prod, STRBUFLEN); + if (err < 0) { + applog(LOG_DEBUG, + "USB init, failed to get iProduct, err %d %s", + err, devstr); + goto cldame; + } + if (found->iProduct) { + if (strcasecmp((char *)prod, found->iProduct)) { + applog(LOG_DEBUG, "USB init, iProduct mismatch %s", + devstr); + applog(LOG_DEBUG, "Found %s vs %s", prod, found->iProduct); + bad = USB_INIT_IGNORE; + goto cldame; + } + } else { + for (i = 0; find_dev[i].drv != DRIVER_MAX; i++) { + const char *iProduct = find_dev[i].iProduct; + /* Do same for iProduct as iManufacturer above */ + + if (!iProduct) + continue; + if (!strcasecmp((char *)prod, iProduct)) { + applog(LOG_DEBUG, "USB init, alternative iProduct match %s", + devstr); + applog(LOG_DEBUG, "Found %s", iProduct); + bad = USB_INIT_IGNORE; + goto cldame; + } + } + } + + cfg = -1; + err = libusb_get_configuration(cgusb->handle, &cfg); + if (err) + cfg = -1; + + // Try to set it if we can't read it or it's different + if (cfg != found->config) { + err = libusb_set_configuration(cgusb->handle, found->config); + if (err) { + switch(err) { + case LIBUSB_ERROR_BUSY: + applog(LOG_WARNING, + "USB init, set config %d in use %s", + found->config, devstr); + break; + default: + applog(LOG_DEBUG, + "USB init, failed to set config to %d, err %d %s", + found->config, err, devstr); + } + goto cldame; + } + } + + err = libusb_get_active_config_descriptor(dev, &config); + if (err) { + applog(LOG_DEBUG, + "USB init, failed to get config descriptor, err %d %s", + err, devstr); + goto cldame; + } + + int imax = -1; + for (ifinfo = 0; ifinfo < found->intinfo_count; ifinfo++) + if (found->intinfos[ifinfo].interface > imax) + imax = found->intinfos[ifinfo].interface; + + if ((int)(config->bNumInterfaces) <= imax) { + applog(LOG_DEBUG, "USB init bNumInterfaces %d <= interface max %d for %s", + (int)(config->bNumInterfaces), imax, devstr); + goto cldame; + } + + for (ifinfo = 0; ifinfo < found->intinfo_count; ifinfo++) + for (epinfo = 0; epinfo < found->intinfos[ifinfo].epinfo_count; epinfo++) + found->intinfos[ifinfo].epinfos[epinfo].found = false; + + for (ifinfo = 0; ifinfo < found->intinfo_count; ifinfo++) { + int interface = found->intinfos[ifinfo].interface; + for (alt = 0; alt < config->interface[interface].num_altsetting; alt++) { + idesc = &(config->interface[interface].altsetting[alt]); + for (epnum = 0; epnum < (int)(idesc->bNumEndpoints); epnum++) { + struct usb_epinfo *epinfos = found->intinfos[ifinfo].epinfos; + epdesc = &(idesc->endpoint[epnum]); + for (epinfo = 0; epinfo < found->intinfos[ifinfo].epinfo_count; epinfo++) { + if (!epinfos[epinfo].found) { + if (epdesc->bmAttributes == epinfos[epinfo].att + && epdesc->wMaxPacketSize >= epinfos[epinfo].size + && epdesc->bEndpointAddress == epinfos[epinfo].ep) { + epinfos[epinfo].found = true; + epinfos[epinfo].wMaxPacketSize = epdesc->wMaxPacketSize; + break; + } + } + } + } + } + } + + for (ifinfo = 0; ifinfo < found->intinfo_count; ifinfo++) + for (epinfo = 0; epinfo < found->intinfos[ifinfo].epinfo_count; epinfo++) + if (found->intinfos[ifinfo].epinfos[epinfo].found == false) { + applog(LOG_DEBUG, "USB init found (%d,%d) == false %s", + ifinfo, epinfo, devstr); + goto cldame; + } + + claimed = 0; + for (ifinfo = 0; ifinfo < found->intinfo_count; ifinfo++) { + err = libusb_claim_interface(cgusb->handle, THISIF(found, ifinfo)); + if (err == 0) + claimed++; + else { + switch(err) { + case LIBUSB_ERROR_BUSY: + applog(LOG_WARNING, + "USB init, claim ifinfo %d interface %d in use %s", + ifinfo, THISIF(found, ifinfo), devstr); + break; + default: + applog(LOG_DEBUG, + "USB init, claim ifinfo %d interface %d failed," + " err %d %s", + ifinfo, THISIF(found, ifinfo), err, devstr); + } + goto reldame; + } + } + + cfg = -1; + err = libusb_get_configuration(cgusb->handle, &cfg); + if (err) + cfg = -1; + if (cfg != found->config) { + applog(LOG_WARNING, + "USB init, incorrect config (%d!=%d) after claim of %s", + cfg, found->config, devstr); + goto reldame; + } + + cgusb->usbver = cgusb->descriptor->bcdUSB; + if (cgusb->usbver < 0x0200) { + cgusb->usb11 = true; + cgusb->tt = true; + } + +// TODO: allow this with the right version of the libusb include and running library +// cgusb->speed = libusb_get_device_speed(dev); + + err = libusb_get_string_descriptor_ascii(cgusb->handle, + cgusb->descriptor->iProduct, strbuf, STRBUFLEN); + if (err > 0) + cgusb->prod_string = strdup((char *)strbuf); + else + cgusb->prod_string = (char *)BLANK; + + err = libusb_get_string_descriptor_ascii(cgusb->handle, + cgusb->descriptor->iManufacturer, strbuf, STRBUFLEN); + if (err > 0) + cgusb->manuf_string = strdup((char *)strbuf); + else + cgusb->manuf_string = (char *)BLANK; + + err = libusb_get_string_descriptor_ascii(cgusb->handle, + cgusb->descriptor->iSerialNumber, strbuf, STRBUFLEN); + if (err > 0) + cgusb->serial_string = strdup((char *)strbuf); + else + cgusb->serial_string = (char *)BLANK; + +// TODO: ? +// cgusb->fwVersion <- for temp1/temp2 decision? or serial? (driver-modminer.c) +// cgusb->interfaceVersion + + applog(LOG_DEBUG, + "USB init %s usbver=%04x prod='%s' manuf='%s' serial='%s'", + devstr, cgusb->usbver, cgusb->prod_string, + cgusb->manuf_string, cgusb->serial_string); + + cgpu->usbdev = cgusb; + cgpu->usbinfo.nodev = false; + + libusb_free_config_descriptor(config); + + // Allow a name change based on the idVendor+idProduct + // N.B. must be done before calling add_cgpu() + if (strcasecmp(cgpu->drv->name, found->name)) { + if (!cgpu->drv->copy) + cgpu->drv = copy_drv(cgpu->drv); + cgpu->drv->name = (char *)(found->name); + } + + bad = USB_INIT_OK; + goto out_unlock; + +reldame: + + ifinfo = claimed; + while (ifinfo-- > 0) + libusb_release_interface(cgusb->handle, THISIF(found, ifinfo)); + +cldame: +#ifdef LINUX + libusb_attach_kernel_driver(cgusb->handle, THISIF(found, ifinfo)); + +nokernel: +#endif + cg_wlock(&cgusb_fd_lock); + libusb_close(cgusb->handle); + cgusb->handle = NULL; + cg_wunlock(&cgusb_fd_lock); + +dame: + + if (config) + libusb_free_config_descriptor(config); + + cgusb = free_cgusb(cgusb); + +out_unlock: + DEVWUNLOCK(cgpu, pstate); + + return bad; +} + +bool usb_init(struct cgpu_info *cgpu, struct libusb_device *dev, struct usb_find_devices *found_match) +{ + struct usb_find_devices *found_use = NULL; + int uninitialised_var(ret); + int i; + + for (i = 0; find_dev[i].drv != DRIVER_MAX; i++) { + if (find_dev[i].drv == found_match->drv && + find_dev[i].idVendor == found_match->idVendor && + find_dev[i].idProduct == found_match->idProduct) { + found_use = malloc(sizeof(*found_use)); + if (unlikely(!found_use)) + quit(1, "USB failed to malloc found_use"); + cg_memcpy(found_use, &(find_dev[i]), sizeof(*found_use)); + + ret = _usb_init(cgpu, dev, found_use); + + if (ret != USB_INIT_IGNORE) + break; + } + } + + if (ret == USB_INIT_FAIL) + applog(LOG_ERR, "%s detect (%d:%d) failed to initialise (incorrect device?)", + cgpu->drv->dname, + (int)(cgpu->usbinfo.bus_number), + (int)(cgpu->usbinfo.device_address)); + + return (ret == USB_INIT_OK); +} + +static bool usb_check_device(struct device_drv *drv, struct libusb_device *dev, struct usb_find_devices *look) +{ + struct libusb_device_descriptor desc; + int bus_number, device_address; + int err, i; + bool ok; + + err = libusb_get_device_descriptor(dev, &desc); + if (err) { + applog(LOG_DEBUG, "USB check device: Failed to get descriptor, err %d", err); + return false; + } + + if (desc.idVendor != look->idVendor || desc.idProduct != look->idProduct) { + applog(LOG_DEBUG, "%s looking for %s %04x:%04x but found %04x:%04x instead", + drv->name, look->name, look->idVendor, look->idProduct, desc.idVendor, desc.idProduct); + + return false; + } + + if (busdev_count > 0) { + bus_number = (int)libusb_get_bus_number(dev); + device_address = (int)libusb_get_device_address(dev); + ok = false; + for (i = 0; i < busdev_count; i++) { + if (bus_number == busdev[i].bus_number) { + if (busdev[i].device_address == -1 || + device_address == busdev[i].device_address) { + ok = true; + break; + } + } + } + if (!ok) { + applog(LOG_DEBUG, "%s rejected %s %04x:%04x with bus:dev (%d:%d)", + drv->name, look->name, look->idVendor, look->idProduct, + bus_number, device_address); + return false; + } + } + + applog(LOG_DEBUG, "%s looking for and found %s %04x:%04x", + drv->name, look->name, look->idVendor, look->idProduct); + + return true; +} + +static struct usb_find_devices *usb_check_each(int drvnum, struct device_drv *drv, struct libusb_device *dev) +{ + struct usb_find_devices *found; + int i; + + for (i = 0; find_dev[i].drv != DRIVER_MAX; i++) + if (find_dev[i].drv == drvnum) { + if (usb_check_device(drv, dev, &(find_dev[i]))) { + found = malloc(sizeof(*found)); + if (unlikely(!found)) + quit(1, "USB failed to malloc found"); + cg_memcpy(found, &(find_dev[i]), sizeof(*found)); + return found; + } + } + + return NULL; +} + +#define DRIVER_USB_CHECK_EACH(X) if (drv->drv_id == DRIVER_##X) \ + return usb_check_each(DRIVER_##X, drv, dev); + +static struct usb_find_devices *usb_check(__maybe_unused struct device_drv *drv, __maybe_unused struct libusb_device *dev) +{ + if (drv_count[drv->drv_id].count >= drv_count[drv->drv_id].limit) { + applog(LOG_DEBUG, + "USB scan devices3: %s limit %d reached", + drv->dname, drv_count[drv->drv_id].limit); + return NULL; + } + + DRIVER_PARSE_COMMANDS(DRIVER_USB_CHECK_EACH) + + return NULL; +} + +void __usb_detect(struct device_drv *drv, struct cgpu_info *(*device_detect)(struct libusb_device *, struct usb_find_devices *), + bool single) +{ + libusb_device **list; + ssize_t count, i; + struct usb_find_devices *found; + struct cgpu_info *cgpu; + + applog(LOG_DEBUG, "USB scan devices: checking for %s devices", drv->name); + + if (total_count >= total_limit) { + applog(LOG_DEBUG, "USB scan devices: total limit %d reached", total_limit); + return; + } + + if (drv_count[drv->drv_id].count >= drv_count[drv->drv_id].limit) { + applog(LOG_DEBUG, + "USB scan devices: %s limit %d reached", + drv->dname, drv_count[drv->drv_id].limit); + return; + } + + count = libusb_get_device_list(NULL, &list); + if (count < 0) { + applog(LOG_DEBUG, "USB scan devices: failed, err %d", (int)count); + return; + } + + if (count == 0) + applog(LOG_DEBUG, "USB scan devices: found no devices"); + else + cgsleep_ms(166); + + for (i = 0; i < count; i++) { + if (total_count >= total_limit) { + applog(LOG_DEBUG, "USB scan devices2: total limit %d reached", total_limit); + break; + } + + if (drv_count[drv->drv_id].count >= drv_count[drv->drv_id].limit) { + applog(LOG_DEBUG, + "USB scan devices2: %s limit %d reached", + drv->dname, drv_count[drv->drv_id].limit); + break; + } + + found = usb_check(drv, list[i]); + if (found != NULL) { + bool new_dev = false; + + if (is_in_use(list[i]) || cgminer_usb_lock(drv, list[i]) == false) + free(found); + else { + cgpu = device_detect(list[i], found); + if (!cgpu) + cgminer_usb_unlock(drv, list[i]); + else { + new_dev = true; + cgpu->usbinfo.initialised = true; + total_count++; + drv_count[drv->drv_id].count++; + } + free(found); + } + if (single && new_dev) + break; + } + } + + libusb_free_device_list(list, 1); +} + +#if DO_USB_STATS +static void modes_str(char *buf, uint32_t modes) +{ + bool first; + + *buf = '\0'; + + if (modes == MODE_NONE) + strcpy(buf, MODE_NONE_STR); + else { + first = true; + + if (modes & MODE_CTRL_READ) { + strcpy(buf, MODE_CTRL_READ_STR); + first = false; + } + + if (modes & MODE_CTRL_WRITE) { + if (!first) + strcat(buf, MODE_SEP_STR); + strcat(buf, MODE_CTRL_WRITE_STR); + first = false; + } + + if (modes & MODE_BULK_READ) { + if (!first) + strcat(buf, MODE_SEP_STR); + strcat(buf, MODE_BULK_READ_STR); + first = false; + } + + if (modes & MODE_BULK_WRITE) { + if (!first) + strcat(buf, MODE_SEP_STR); + strcat(buf, MODE_BULK_WRITE_STR); + first = false; + } + } +} +#endif + +// The stat data can be spurious due to not locking it before copying it - +// however that would require the stat() function to also lock and release +// a mutex every time a usb read or write is called which would slow +// things down more +struct api_data *api_usb_stats(__maybe_unused int *count) +{ +#if DO_USB_STATS + struct cg_usb_stats_details *details; + struct cg_usb_stats *sta; + struct api_data *root = NULL; + int device; + int cmdseq; + char modes_s[32]; + + if (next_stat == USB_NOSTAT) + return NULL; + + while (*count < next_stat * C_MAX * 2) { + device = *count / (C_MAX * 2); + cmdseq = *count % (C_MAX * 2); + + (*count)++; + + sta = &(usb_stats[device]); + details = &(sta->details[cmdseq]); + + // Only show stats that have results + if (details->item[CMD_CMD].count == 0 && + details->item[CMD_TIMEOUT].count == 0 && + details->item[CMD_ERROR].count == 0) + continue; + + root = api_add_string(root, "Name", sta->name, false); + root = api_add_int(root, "ID", &(sta->device_id), false); + root = api_add_const(root, "Stat", usb_commands[cmdseq/2], false); + root = api_add_int(root, "Seq", &(details->seq), true); + modes_str(modes_s, details->modes); + root = api_add_string(root, "Modes", modes_s, true); + root = api_add_uint64(root, "Count", + &(details->item[CMD_CMD].count), true); + root = api_add_double(root, "Total Delay", + &(details->item[CMD_CMD].total_delay), true); + root = api_add_double(root, "Min Delay", + &(details->item[CMD_CMD].min_delay), true); + root = api_add_double(root, "Max Delay", + &(details->item[CMD_CMD].max_delay), true); + root = api_add_uint64(root, "Timeout Count", + &(details->item[CMD_TIMEOUT].count), true); + root = api_add_double(root, "Timeout Total Delay", + &(details->item[CMD_TIMEOUT].total_delay), true); + root = api_add_double(root, "Timeout Min Delay", + &(details->item[CMD_TIMEOUT].min_delay), true); + root = api_add_double(root, "Timeout Max Delay", + &(details->item[CMD_TIMEOUT].max_delay), true); + root = api_add_uint64(root, "Error Count", + &(details->item[CMD_ERROR].count), true); + root = api_add_double(root, "Error Total Delay", + &(details->item[CMD_ERROR].total_delay), true); + root = api_add_double(root, "Error Min Delay", + &(details->item[CMD_ERROR].min_delay), true); + root = api_add_double(root, "Error Max Delay", + &(details->item[CMD_ERROR].max_delay), true); + root = api_add_timeval(root, "First Command", + &(details->item[CMD_CMD].first), true); + root = api_add_timeval(root, "Last Command", + &(details->item[CMD_CMD].last), true); + root = api_add_timeval(root, "First Timeout", + &(details->item[CMD_TIMEOUT].first), true); + root = api_add_timeval(root, "Last Timeout", + &(details->item[CMD_TIMEOUT].last), true); + root = api_add_timeval(root, "First Error", + &(details->item[CMD_ERROR].first), true); + root = api_add_timeval(root, "Last Error", + &(details->item[CMD_ERROR].last), true); + + return root; + } +#endif + return NULL; +} + +#if DO_USB_STATS +static void newstats(struct cgpu_info *cgpu) +{ + int i; + + mutex_lock(&cgusb_lock); + + cgpu->usbinfo.usbstat = next_stat + 1; + + usb_stats = realloc(usb_stats, sizeof(*usb_stats) * (next_stat+1)); + if (unlikely(!usb_stats)) + quit(1, "USB failed to realloc usb_stats %d", next_stat+1); + + usb_stats[next_stat].name = cgpu->drv->name; + usb_stats[next_stat].device_id = -1; + usb_stats[next_stat].details = calloc(2, sizeof(struct cg_usb_stats_details) * (C_MAX + 1)); + if (unlikely(!usb_stats[next_stat].details)) + quit(1, "USB failed to calloc details for %d", next_stat+1); + + for (i = 1; i < C_MAX * 2; i += 2) + usb_stats[next_stat].details[i].seq = 1; + + next_stat++; + + mutex_unlock(&cgusb_lock); +} +#endif + +void update_usb_stats(__maybe_unused struct cgpu_info *cgpu) +{ +#if DO_USB_STATS + if (cgpu->usbinfo.usbstat < 1) + newstats(cgpu); + + // we don't know the device_id until after add_cgpu() + usb_stats[cgpu->usbinfo.usbstat - 1].device_id = cgpu->device_id; +#endif +} + +#if DO_USB_STATS +static void stats(struct cgpu_info *cgpu, struct timeval *tv_start, struct timeval *tv_finish, int err, int mode, enum usb_cmds cmd, int seq, int timeout) +{ + struct cg_usb_stats_details *details; + double diff; + int item, extrams; + + if (cgpu->usbinfo.usbstat < 1) + newstats(cgpu); + + cgpu->usbinfo.tmo_count++; + + // timeout checks are only done when stats are enabled + extrams = SECTOMS(tdiff(tv_finish, tv_start)) - timeout; + if (extrams >= USB_TMO_0) { + uint32_t totms = (uint32_t)(timeout + extrams); + int offset = 0; + + if (extrams >= USB_TMO_2) { + applog(LOG_INFO, "%s%i: TIMEOUT %s took %dms but was %dms", + cgpu->drv->name, cgpu->device_id, + usb_cmdname(cmd), totms, timeout) ; + offset = 2; + } else if (extrams >= USB_TMO_1) + offset = 1; + + cgpu->usbinfo.usb_tmo[offset].count++; + cgpu->usbinfo.usb_tmo[offset].total_over += extrams; + cgpu->usbinfo.usb_tmo[offset].total_tmo += timeout; + if (cgpu->usbinfo.usb_tmo[offset].min_tmo == 0) { + cgpu->usbinfo.usb_tmo[offset].min_tmo = totms; + cgpu->usbinfo.usb_tmo[offset].max_tmo = totms; + } else { + if (cgpu->usbinfo.usb_tmo[offset].min_tmo > totms) + cgpu->usbinfo.usb_tmo[offset].min_tmo = totms; + if (cgpu->usbinfo.usb_tmo[offset].max_tmo < totms) + cgpu->usbinfo.usb_tmo[offset].max_tmo = totms; + } + } + + details = &(usb_stats[cgpu->usbinfo.usbstat - 1].details[cmd * 2 + seq]); + details->modes |= mode; + + diff = tdiff(tv_finish, tv_start); + + switch (err) { + case LIBUSB_SUCCESS: + item = CMD_CMD; + break; + case LIBUSB_ERROR_TIMEOUT: + item = CMD_TIMEOUT; + break; + default: + item = CMD_ERROR; + break; + } + + if (details->item[item].count == 0) { + details->item[item].min_delay = diff; + cg_memcpy(&(details->item[item].first), tv_start, sizeof(*tv_start)); + } else if (diff < details->item[item].min_delay) + details->item[item].min_delay = diff; + + if (diff > details->item[item].max_delay) + details->item[item].max_delay = diff; + + details->item[item].total_delay += diff; + cg_memcpy(&(details->item[item].last), tv_start, sizeof(*tv_start)); + details->item[item].count++; +} + +static void rejected_inc(struct cgpu_info *cgpu, uint32_t mode) +{ + struct cg_usb_stats_details *details; + int item = CMD_ERROR; + + if (cgpu->usbinfo.usbstat < 1) + newstats(cgpu); + + details = &(usb_stats[cgpu->usbinfo.usbstat - 1].details[C_REJECTED * 2 + 0]); + details->modes |= mode; + details->item[item].count++; +} +#endif + +#define USB_RETRY_MAX 5 + +struct usb_transfer { + cgsem_t cgsem; + struct libusb_transfer *transfer; + bool cancellable; + struct list_head list; +}; + +bool async_usb_transfers(void) +{ + bool ret; + + cg_rlock(&cgusb_fd_lock); + ret = !list_empty(&ut_list); + cg_runlock(&cgusb_fd_lock); + + return ret; +} + +/* Cancellable transfers should only be labelled as such if it is safe for them + * to effectively mimic timing out early. This flag is usually used to signify + * a read is waiting on a non-critical response that takes a long time and the + * driver wishes it be aborted if work restart message has been sent. */ +void cancel_usb_transfers(void) +{ + struct usb_transfer *ut; + int cancellations = 0; + + cg_wlock(&cgusb_fd_lock); + list_for_each_entry(ut, &ut_list, list) { + if (ut->cancellable) { + ut->cancellable = false; + libusb_cancel_transfer(ut->transfer); + cancellations++; + } + } + cg_wunlock(&cgusb_fd_lock); + + if (cancellations) + applog(LOG_DEBUG, "Cancelled %d USB transfers", cancellations); +} + +static void init_usb_transfer(struct usb_transfer *ut) +{ + cgsem_init(&ut->cgsem); + ut->transfer = libusb_alloc_transfer(0); + if (unlikely(!ut->transfer)) + quit(1, "Failed to libusb_alloc_transfer"); + ut->transfer->user_data = ut; + ut->cancellable = false; +} + +static void complete_usb_transfer(struct usb_transfer *ut) +{ + cg_wlock(&cgusb_fd_lock); + list_del(&ut->list); + cg_wunlock(&cgusb_fd_lock); + + cgsem_destroy(&ut->cgsem); + libusb_free_transfer(ut->transfer); +} + +static void LIBUSB_CALL transfer_callback(struct libusb_transfer *transfer) +{ + struct usb_transfer *ut = transfer->user_data; + + ut->cancellable = false; + cgsem_post(&ut->cgsem); +} + +static int usb_transfer_toerr(int ret) +{ + if (ret <= 0) + return ret; + + switch (ret) { + default: + case LIBUSB_TRANSFER_COMPLETED: + ret = LIBUSB_SUCCESS; + break; + case LIBUSB_TRANSFER_ERROR: + ret = LIBUSB_ERROR_IO; + break; + case LIBUSB_TRANSFER_TIMED_OUT: + case LIBUSB_TRANSFER_CANCELLED: + ret = LIBUSB_ERROR_TIMEOUT; + break; + case LIBUSB_TRANSFER_STALL: + ret = LIBUSB_ERROR_PIPE; + break; + case LIBUSB_TRANSFER_NO_DEVICE: + ret = LIBUSB_ERROR_NO_DEVICE; + break; + case LIBUSB_TRANSFER_OVERFLOW: + ret = LIBUSB_ERROR_OVERFLOW; + break; + } + return ret; +} + +/* Wait for callback function to tell us it has finished the USB transfer, but + * use our own timer to cancel the request if we go beyond the timeout. */ +static int callback_wait(struct usb_transfer *ut, int *transferred, unsigned int timeout) +{ + struct libusb_transfer *transfer= ut->transfer; + int ret; + + ret = cgsem_mswait(&ut->cgsem, timeout); + if (ret == ETIMEDOUT) { + /* We are emulating a timeout ourself here */ + libusb_cancel_transfer(transfer); + + /* Now wait for the callback function to be invoked. */ + cgsem_wait(&ut->cgsem); + } + ret = transfer->status; + ret = usb_transfer_toerr(ret); + + /* No need to sort out mutexes here since they won't be reused */ + *transferred = transfer->actual_length; + + return ret; +} + +static int usb_submit_transfer(struct usb_transfer *ut, struct libusb_transfer *transfer, + bool cancellable, bool tt) +{ + int err; + + INIT_LIST_HEAD(&ut->list); + + cg_wlock(&cgusb_fd_lock); + /* Imitate a transaction translator for writes to usb1.1 devices */ + if (tt) + cgsleep_ms_r(&usb11_cgt, 1); + err = libusb_submit_transfer(transfer); + if (likely(!err)) + ut->cancellable = cancellable; + list_add(&ut->list, &ut_list); + if (tt) + cgtimer_time(&usb11_cgt); + cg_wunlock(&cgusb_fd_lock); + + return err; +} + +static int +usb_perform_transfer(struct cgpu_info *cgpu, struct cg_usb_device *usbdev, int intinfo, + int epinfo, unsigned char *data, int length, int *transferred, + unsigned int timeout, __maybe_unused int mode, enum usb_cmds cmd, + __maybe_unused int seq, bool cancellable, bool tt) +{ + int bulk_timeout, callback_timeout = timeout, err_retries = 0; + struct libusb_device_handle *dev_handle = usbdev->handle; + struct usb_epinfo *usb_epinfo; + struct usb_transfer ut; + unsigned char endpoint; + bool interrupt; + int err, errn; +#if DO_USB_STATS + struct timeval tv_start, tv_finish; +#endif + unsigned char buf[512]; +#ifdef WIN32 + /* On windows the callback_timeout is a safety mechanism only. */ + bulk_timeout = timeout; + callback_timeout += WIN_CALLBACK_EXTRA; +#else + /* We give the transfer no timeout since we manage timeouts ourself on + * non windows. */ + bulk_timeout = 0; +#endif + + usb_epinfo = &(usbdev->found->intinfos[intinfo].epinfos[epinfo]); + interrupt = usb_epinfo->att == LIBUSB_TRANSFER_TYPE_INTERRUPT; + endpoint = usb_epinfo->ep; + + if (unlikely(!data)) { + applog(LOG_ERR, "USB error: usb_perform_transfer sent NULL data (%s,intinfo=%d,epinfo=%d,length=%d,timeout=%u,mode=%d,cmd=%s,seq=%d) endpoint=%d", + cgpu->drv->name, intinfo, epinfo, length, timeout, mode, usb_cmdname(cmd), seq, (int)endpoint); + err = LIBUSB_ERROR_IO; + goto out_fail; + } + /* Avoid any async transfers during shutdown to allow the polling + * thread to be shut down after all existing transfers are complete */ + if (opt_lowmem || cgpu->shutdown) + return libusb_bulk_transfer(dev_handle, endpoint, data, length, transferred, timeout); +err_retry: + init_usb_transfer(&ut); + + if ((endpoint & LIBUSB_ENDPOINT_DIR_MASK) == LIBUSB_ENDPOINT_OUT) { + cg_memcpy(buf, data, length); +#ifndef HAVE_LIBUSB + /* Older versions may not have this feature so only enable it + * when we know we're compiling with included static libusb. We + * only do this for bulk transfer, not interrupt. */ + if (!cgpu->nozlp && !interrupt) + ut.transfer->flags |= LIBUSB_TRANSFER_ADD_ZERO_PACKET; +#endif +#ifdef WIN32 + /* Writes on windows really don't like to be cancelled, but + * are prone to timeouts under heavy USB traffic, so make this + * a last resort cancellation delayed long after the write + * would have timed out on its own. */ + callback_timeout += WIN_WRITE_CBEXTRA; +#endif + } + + USBDEBUG("USB debug: @usb_perform_transfer(%s (nodev=%s),intinfo=%d,epinfo=%d,data=%p,length=%d,timeout=%u,mode=%d,cmd=%s,seq=%d) endpoint=%d", cgpu->drv->name, bool_str(cgpu->usbinfo.nodev), intinfo, epinfo, data, length, timeout, mode, usb_cmdname(cmd), seq, (int)endpoint); + + if (interrupt) { + libusb_fill_interrupt_transfer(ut.transfer, dev_handle, endpoint, + buf, length, transfer_callback, &ut, + bulk_timeout); + } else { + libusb_fill_bulk_transfer(ut.transfer, dev_handle, endpoint, buf, + length, transfer_callback, &ut, bulk_timeout); + } + STATS_TIMEVAL(&tv_start); + err = usb_submit_transfer(&ut, ut.transfer, cancellable, tt); + errn = errno; + if (!err) + err = callback_wait(&ut, transferred, callback_timeout); + else + err = usb_transfer_toerr(err); + complete_usb_transfer(&ut); + + STATS_TIMEVAL(&tv_finish); + USB_STATS(cgpu, &tv_start, &tv_finish, err, mode, cmd, seq, timeout); + + if (err < 0) { + applog(LOG_DEBUG, "%s%i: %s (amt=%d err=%d ern=%d)", + cgpu->drv->name, cgpu->device_id, + usb_cmdname(cmd), *transferred, err, errn); + } + + if (err == LIBUSB_ERROR_PIPE) { + int pipeerr, retries = 0; + + do { + cgpu->usbinfo.last_pipe = time(NULL); + cgpu->usbinfo.pipe_count++; + applog(LOG_INFO, "%s%i: libusb pipe error, trying to clear", + cgpu->drv->name, cgpu->device_id); + pipeerr = libusb_clear_halt(dev_handle, endpoint); + applog(LOG_DEBUG, "%s%i: libusb pipe error%scleared", + cgpu->drv->name, cgpu->device_id, err ? " not " : " "); + + if (pipeerr) + cgpu->usbinfo.clear_fail_count++; + } while (pipeerr && ++retries < USB_RETRY_MAX); + if (!pipeerr && ++err_retries < USB_RETRY_MAX) + goto err_retry; + } + if (err == LIBUSB_ERROR_IO && ++err_retries < USB_RETRY_MAX) + goto err_retry; +out_fail: + if (NODEV(err)) + *transferred = 0; + else if ((endpoint & LIBUSB_ENDPOINT_DIR_MASK) == LIBUSB_ENDPOINT_IN && *transferred) + cg_memcpy(data, buf, *transferred); + + return err; +} + +void usb_reset(struct cgpu_info *cgpu) +{ + int pstate, err = 0; + + DEVRLOCK(cgpu, pstate); + if (!cgpu->usbinfo.nodev) { + err = libusb_reset_device(cgpu->usbdev->handle); + applog(LOG_WARNING, "%s %i attempted reset got err:(%d) %s", + cgpu->drv->name, cgpu->device_id, err, libusb_error_name(err)); + } + if (NODEV(err)) { + cg_ruwlock(&cgpu->usbinfo.devlock); + release_cgpu(cgpu); + DEVWUNLOCK(cgpu, pstate); + } else + DEVRUNLOCK(cgpu, pstate); +} + +int _usb_read(struct cgpu_info *cgpu, int intinfo, int epinfo, char *buf, size_t bufsiz, + int *processed, int timeout, const char *end, enum usb_cmds cmd, bool readonce, bool cancellable) +{ + unsigned char *ptr, usbbuf[USB_READ_BUFSIZE]; + struct timeval read_start, tv_finish; + int bufleft, err, got, tot, pstate, tried_reset; + struct cg_usb_device *usbdev; + unsigned int initial_timeout; + bool first = true; + size_t usbbufread; + int endlen = 0; + char *eom = NULL; + double done; + bool ftdi; + + memset(usbbuf, 0, USB_READ_BUFSIZE); + memset(buf, 0, bufsiz); + + if (end) + endlen = strlen(end); + + DEVRLOCK(cgpu, pstate); + if (cgpu->usbinfo.nodev) { + *processed = 0; + USB_REJECT(cgpu, MODE_BULK_READ); + + err = LIBUSB_ERROR_NO_DEVICE; + goto out_noerrmsg; + } + + usbdev = cgpu->usbdev; + /* Interrupt transfers are guaranteed to be of an expected size (we hope) */ + if (usbdev->found->intinfos[intinfo].epinfos[epinfo].att == LIBUSB_TRANSFER_TYPE_INTERRUPT) + usbbufread = bufsiz; + else + usbbufread = 512; + + ftdi = (usbdev->usb_type == USB_TYPE_FTDI); + + USBDEBUG("USB debug: _usb_read(%s (nodev=%s),intinfo=%d,epinfo=%d,buf=%p,bufsiz=%d,proc=%p,timeout=%u,end=%s,cmd=%s,ftdi=%s,readonce=%s)", cgpu->drv->name, bool_str(cgpu->usbinfo.nodev), intinfo, epinfo, buf, (int)bufsiz, processed, timeout, end ? (char *)str_text((char *)end) : "NULL", usb_cmdname(cmd), bool_str(ftdi), bool_str(readonce)); + + if (bufsiz > USB_MAX_READ) + quit(1, "%s USB read request %d too large (max=%d)", cgpu->drv->name, (int)bufsiz, USB_MAX_READ); + + if (timeout == DEVTIMEOUT) + timeout = usbdev->found->timeout; + + tot = usbdev->bufamt; + bufleft = bufsiz - tot; + if (tot) + cg_memcpy(usbbuf, usbdev->buffer, tot); + ptr = usbbuf + tot; + usbdev->bufamt = 0; + + err = LIBUSB_SUCCESS; + if (end != NULL) + eom = strstr((const char *)usbbuf, end); + + initial_timeout = timeout; + cgtime(&read_start); + tried_reset = 0; + while (bufleft > 0 && !eom) { + err = usb_perform_transfer(cgpu, usbdev, intinfo, epinfo, ptr, usbbufread, + &got, timeout, MODE_BULK_READ, cmd, + first ? SEQ0 : SEQ1, cancellable, false); + cgtime(&tv_finish); + ptr[got] = '\0'; + + USBDEBUG("USB debug: @_usb_read(%s (nodev=%s)) first=%s err=%d%s got=%d ptr='%s' usbbufread=%d", cgpu->drv->name, bool_str(cgpu->usbinfo.nodev), bool_str(first), err, isnodev(err), got, (char *)str_text((char *)ptr), (int)usbbufread); + + if (ftdi) { + // first 2 bytes returned are an FTDI status + if (got > 2) { + got -= 2; + memmove(ptr, ptr+2, got+1); + } else { + got = 0; + *ptr = '\0'; + } + } + + tot += got; + if (end != NULL) + eom = strstr((const char *)usbbuf, end); + + /* Attempt a usb reset for an error that will otherwise cause + * this device to drop out provided we know the device still + * might exist. */ + if (err && err != LIBUSB_ERROR_TIMEOUT) { + applog(LOG_WARNING, "%s %i %s usb read err:(%d) %s", cgpu->drv->name, + cgpu->device_id, usb_cmdname(cmd), err, libusb_error_name(err)); + if (err != LIBUSB_ERROR_NO_DEVICE && !tried_reset) { + err = libusb_reset_device(usbdev->handle); + tried_reset = 1; // don't call reset twice in a row + applog(LOG_WARNING, "%s %i attempted reset got err:(%d) %s", + cgpu->drv->name, cgpu->device_id, err, libusb_error_name(err)); + } + } else { + tried_reset = 0; + } + ptr += got; + bufleft -= got; + if (bufleft < 1) + err = LIBUSB_SUCCESS; + + if (err || readonce) + break; + + + first = false; + + done = tdiff(&tv_finish, &read_start); + // N.B. this is: return last err with whatever size has already been read + timeout = initial_timeout - (done * 1000); + if (timeout <= 0) + break; + } + + /* If we found the end of message marker, just use that data and + * return success. */ + if (eom) { + size_t eomlen = (void *)eom - (void *)usbbuf + endlen; + + if (eomlen < bufsiz) { + bufsiz = eomlen; + err = LIBUSB_SUCCESS; + } + } + + // N.B. usbdev->buffer was emptied before the while() loop + if (tot > (int)bufsiz) { + usbdev->bufamt = tot - bufsiz; + cg_memcpy(usbdev->buffer, usbbuf + bufsiz, usbdev->bufamt); + tot -= usbdev->bufamt; + usbbuf[tot] = '\0'; + applog(LOG_DEBUG, "USB: %s%i read1 buffering %d extra bytes", + cgpu->drv->name, cgpu->device_id, usbdev->bufamt); + } + + *processed = tot; + cg_memcpy((char *)buf, (const char *)usbbuf, (tot < (int)bufsiz) ? tot + 1 : (int)bufsiz); + +out_noerrmsg: + if (NODEV(err)) { + cg_ruwlock(&cgpu->usbinfo.devlock); + release_cgpu(cgpu); + DEVWUNLOCK(cgpu, pstate); + } else + DEVRUNLOCK(cgpu, pstate); + + return err; +} + +int _usb_write(struct cgpu_info *cgpu, int intinfo, int epinfo, char *buf, size_t bufsiz, int *processed, int timeout, enum usb_cmds cmd) +{ + struct timeval write_start, tv_finish; + struct cg_usb_device *usbdev; + unsigned int initial_timeout; + int err, sent, tot, pstate, tried_reset; + bool first = true; + double done; + + DEVRLOCK(cgpu, pstate); + + USBDEBUG("USB debug: _usb_write(%s (nodev=%s),intinfo=%d,epinfo=%d,buf='%s',bufsiz=%d,proc=%p,timeout=%u,cmd=%s)", cgpu->drv->name, bool_str(cgpu->usbinfo.nodev), intinfo, epinfo, (char *)str_text(buf), (int)bufsiz, processed, timeout, usb_cmdname(cmd)); + + *processed = 0; + + if (cgpu->usbinfo.nodev) { + USB_REJECT(cgpu, MODE_BULK_WRITE); + + err = LIBUSB_ERROR_NO_DEVICE; + goto out_noerrmsg; + } + + usbdev = cgpu->usbdev; + if (timeout == DEVTIMEOUT) + timeout = usbdev->found->timeout; + + tot = 0; + err = LIBUSB_SUCCESS; + initial_timeout = timeout; + cgtime(&write_start); + tried_reset = 0; + while (bufsiz > 0) { + int tosend = bufsiz; + + /* USB 1.1 devices don't handle zero packets well so split them + * up to not have the final transfer equal to the wMaxPacketSize + * or they will stall waiting for more data. */ + if (usbdev->usb11) { + struct usb_epinfo *ue = &usbdev->found->intinfos[intinfo].epinfos[epinfo]; + + if (tosend == ue->wMaxPacketSize) { + tosend >>= 1; + if (unlikely(!tosend)) + tosend = 1; + } + } + err = usb_perform_transfer(cgpu, usbdev, intinfo, epinfo, (unsigned char *)buf, + tosend, &sent, timeout, MODE_BULK_WRITE, + cmd, first ? SEQ0 : SEQ1, false, usbdev->tt); + cgtime(&tv_finish); + + USBDEBUG("USB debug: @_usb_write(%s (nodev=%s)) err=%d%s sent=%d", cgpu->drv->name, bool_str(cgpu->usbinfo.nodev), err, isnodev(err), sent); + + tot += sent; + + /* Unlike reads, even a timeout error is unrecoverable on + * writes. */ + if (err) { + applog(LOG_WARNING, "%s %i %s usb write err:(%d) %s", cgpu->drv->name, + cgpu->device_id, usb_cmdname(cmd), err, libusb_error_name(err)); + if (err != LIBUSB_ERROR_NO_DEVICE && !tried_reset) { + err = libusb_reset_device(usbdev->handle); + tried_reset = 1; // don't try reset twice in a row + applog(LOG_WARNING, "%s %i attempted reset got err:(%d) %s", + cgpu->drv->name, cgpu->device_id, err, libusb_error_name(err)); + } + } else { + tried_reset = 0; + } + if (err) + break; + + buf += sent; + bufsiz -= sent; + + first = false; + + done = tdiff(&tv_finish, &write_start); + // N.B. this is: return last err with whatever size was written + timeout = initial_timeout - (done * 1000); + if (timeout <= 0) + break; + } + + *processed = tot; + +out_noerrmsg: + if (NODEV(err)) { + cg_ruwlock(&cgpu->usbinfo.devlock); + release_cgpu(cgpu); + DEVWUNLOCK(cgpu, pstate); + } else + DEVRUNLOCK(cgpu, pstate); + + return err; +} + +/* As we do for bulk reads, emulate a sync function for control transfers using + * our own timeouts that takes the same parameters as libusb_control_transfer. + */ +static int usb_control_transfer(struct cgpu_info *cgpu, libusb_device_handle *dev_handle, uint8_t bmRequestType, + uint8_t bRequest, uint16_t wValue, uint16_t wIndex, + unsigned char *buffer, uint16_t wLength, unsigned int timeout) +{ + struct usb_transfer ut; + unsigned char buf[70]; + int err, transferred; + bool tt = false; + + if (unlikely(cgpu->shutdown)) + return libusb_control_transfer(dev_handle, bmRequestType, bRequest, wValue, wIndex, buffer, wLength, timeout); + + init_usb_transfer(&ut); + libusb_fill_control_setup(buf, bmRequestType, bRequest, wValue, + wIndex, wLength); + if ((bmRequestType & LIBUSB_ENDPOINT_DIR_MASK) == LIBUSB_ENDPOINT_OUT) { + if (wLength) + cg_memcpy(buf + LIBUSB_CONTROL_SETUP_SIZE, buffer, wLength); + if (cgpu->usbdev->descriptor->bcdUSB < 0x0200) + tt = true; + } + libusb_fill_control_transfer(ut.transfer, dev_handle, buf, transfer_callback, + &ut, 0); + err = usb_submit_transfer(&ut, ut.transfer, false, tt); + if (!err) + err = callback_wait(&ut, &transferred, timeout); + if (err == LIBUSB_SUCCESS && transferred) { + if ((bmRequestType & LIBUSB_ENDPOINT_DIR_MASK) == LIBUSB_ENDPOINT_IN) + cg_memcpy(buffer, libusb_control_transfer_get_data(ut.transfer), + transferred); + err = transferred; + goto out; + } + err = usb_transfer_toerr(err); +out: + complete_usb_transfer(&ut); + return err; +} + +int __usb_transfer(struct cgpu_info *cgpu, uint8_t request_type, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, uint32_t *data, int siz, unsigned int timeout, __maybe_unused enum usb_cmds cmd) +{ + struct cg_usb_device *usbdev; +#if DO_USB_STATS + struct timeval tv_start, tv_finish; +#endif + unsigned char buf[64]; + uint32_t *buf32 = (uint32_t *)buf; + int err, i, bufsiz; + + USBDEBUG("USB debug: _usb_transfer(%s (nodev=%s),type=%"PRIu8",req=%"PRIu8",value=%"PRIu16",index=%"PRIu16",siz=%d,timeout=%u,cmd=%s)", cgpu->drv->name, bool_str(cgpu->usbinfo.nodev), request_type, bRequest, wValue, wIndex, siz, timeout, usb_cmdname(cmd)); + + if (cgpu->usbinfo.nodev) { + USB_REJECT(cgpu, MODE_CTRL_WRITE); + + err = LIBUSB_ERROR_NO_DEVICE; + goto out_; + } + usbdev = cgpu->usbdev; + if (timeout == DEVTIMEOUT) + timeout = usbdev->found->timeout; + + USBDEBUG("USB debug: @_usb_transfer() data=%s", bin2hex((unsigned char *)data, (size_t)siz)); + + if (siz > 0) { + bufsiz = siz - 1; + bufsiz >>= 2; + bufsiz++; + for (i = 0; i < bufsiz; i++) + buf32[i] = htole32(data[i]); + } + + USBDEBUG("USB debug: @_usb_transfer() buf=%s", bin2hex(buf, (size_t)siz)); + + STATS_TIMEVAL(&tv_start); + err = usb_control_transfer(cgpu, usbdev->handle, request_type, bRequest, + wValue, wIndex, buf, (uint16_t)siz, timeout); + STATS_TIMEVAL(&tv_finish); + USB_STATS(cgpu, &tv_start, &tv_finish, err, MODE_CTRL_WRITE, cmd, SEQ0, timeout); + + USBDEBUG("USB debug: @_usb_transfer(%s (nodev=%s)) err=%d%s", cgpu->drv->name, bool_str(cgpu->usbinfo.nodev), err, isnodev(err)); + + if (err < 0 && err != LIBUSB_ERROR_TIMEOUT) { + applog(LOG_WARNING, "%s %i usb transfer err:(%d) %s", cgpu->drv->name, cgpu->device_id, + err, libusb_error_name(err)); + } +out_: + return err; +} + +/* We use the write devlock for control transfers since some control transfers + * are rare but may be changing settings within the device causing problems + * if concurrent transfers are happening. Using the write lock serialises + * any transfers. */ +int _usb_transfer(struct cgpu_info *cgpu, uint8_t request_type, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, uint32_t *data, int siz, unsigned int timeout, enum usb_cmds cmd) +{ + int pstate, err; + + DEVWLOCK(cgpu, pstate); + + err = __usb_transfer(cgpu, request_type, bRequest, wValue, wIndex, data, siz, timeout, cmd); + + if (NOCONTROLDEV(err)) + release_cgpu(cgpu); + + DEVWUNLOCK(cgpu, pstate); + + return err; +} + +int _usb_transfer_read(struct cgpu_info *cgpu, uint8_t request_type, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, char *buf, int bufsiz, int *amount, unsigned int timeout, __maybe_unused enum usb_cmds cmd) +{ + struct cg_usb_device *usbdev; +#if DO_USB_STATS + struct timeval tv_start, tv_finish; +#endif + unsigned char tbuf[64]; + int err, pstate; + + DEVWLOCK(cgpu, pstate); + + USBDEBUG("USB debug: _usb_transfer_read(%s (nodev=%s),type=%"PRIu8",req=%"PRIu8",value=%"PRIu16",index=%"PRIu16",bufsiz=%d,timeout=%u,cmd=%s)", cgpu->drv->name, bool_str(cgpu->usbinfo.nodev), request_type, bRequest, wValue, wIndex, bufsiz, timeout, usb_cmdname(cmd)); + + if (cgpu->usbinfo.nodev) { + USB_REJECT(cgpu, MODE_CTRL_READ); + + err = LIBUSB_ERROR_NO_DEVICE; + goto out_noerrmsg; + } + usbdev = cgpu->usbdev; + if (timeout == DEVTIMEOUT) + timeout = usbdev->found->timeout; + + *amount = 0; + + memset(tbuf, 0, 64); + STATS_TIMEVAL(&tv_start); + err = usb_control_transfer(cgpu, usbdev->handle, request_type, bRequest, + wValue, wIndex, tbuf, (uint16_t)bufsiz, timeout); + STATS_TIMEVAL(&tv_finish); + USB_STATS(cgpu, &tv_start, &tv_finish, err, MODE_CTRL_READ, cmd, SEQ0, timeout); + cg_memcpy(buf, tbuf, bufsiz); + + USBDEBUG("USB debug: @_usb_transfer_read(%s (nodev=%s)) amt/err=%d%s%s%s", cgpu->drv->name, bool_str(cgpu->usbinfo.nodev), err, isnodev(err), err > 0 ? " = " : BLANK, err > 0 ? bin2hex((unsigned char *)buf, (size_t)err) : BLANK); + + if (err > 0) { + *amount = err; + err = 0; + } + if (err < 0 && err != LIBUSB_ERROR_TIMEOUT) { + applog(LOG_WARNING, "%s %i usb transfer read err:(%d) %s", cgpu->drv->name, cgpu->device_id, + err, libusb_error_name(err)); + } +out_noerrmsg: + if (NOCONTROLDEV(err)) + release_cgpu(cgpu); + + DEVWUNLOCK(cgpu, pstate); + + return err; +} + +#define FTDI_STATUS_B0_MASK (FTDI_RS0_CTS | FTDI_RS0_DSR | FTDI_RS0_RI | FTDI_RS0_RLSD) +#define FTDI_RS0_CTS (1 << 4) +#define FTDI_RS0_DSR (1 << 5) +#define FTDI_RS0_RI (1 << 6) +#define FTDI_RS0_RLSD (1 << 7) + +/* Clear to send for FTDI */ +int usb_ftdi_cts(struct cgpu_info *cgpu) +{ + char buf[2], ret; + int err, amount; + + err = _usb_transfer_read(cgpu, (uint8_t)FTDI_TYPE_IN, (uint8_t)5, + (uint16_t)0, (uint16_t)0, buf, 2, + &amount, DEVTIMEOUT, C_FTDI_STATUS); + /* We return true in case drivers are waiting indefinitely to try and + * write to something that's not there. */ + if (err) + return true; + + ret = buf[0] & FTDI_STATUS_B0_MASK; + return (ret & FTDI_RS0_CTS); +} + +int _usb_ftdi_set_latency(struct cgpu_info *cgpu, int intinfo) +{ + int err = 0; + int pstate; + + DEVWLOCK(cgpu, pstate); + + if (cgpu->usbdev) { + if (cgpu->usbdev->usb_type != USB_TYPE_FTDI) { + applog(LOG_ERR, "%s: bmgid %d latency request on non-FTDI device", + cgpu->drv->name, cgpu->cgminer_id); + err = LIBUSB_ERROR_NOT_SUPPORTED; + } else if (cgpu->usbdev->found->latency == LATENCY_UNUSED) { + applog(LOG_ERR, "%s: cgid %d invalid latency (UNUSED)", + cgpu->drv->name, cgpu->cgminer_id); + err = LIBUSB_ERROR_NOT_SUPPORTED; + } + + if (!err) + err = __usb_transfer(cgpu, FTDI_TYPE_OUT, FTDI_REQUEST_LATENCY, + cgpu->usbdev->found->latency, + USBIF(cgpu->usbdev, intinfo), + NULL, 0, DEVTIMEOUT, C_LATENCY); + } + + DEVWUNLOCK(cgpu, pstate); + + applog(LOG_DEBUG, "%s: bmgid %d %s got err %d", + cgpu->drv->name, cgpu->cgminer_id, + usb_cmdname(C_LATENCY), err); + + return err; +} + +void usb_buffer_clear(struct cgpu_info *cgpu) +{ + int pstate; + + DEVWLOCK(cgpu, pstate); + + if (cgpu->usbdev) + cgpu->usbdev->bufamt = 0; + + DEVWUNLOCK(cgpu, pstate); +} + +uint32_t usb_buffer_size(struct cgpu_info *cgpu) +{ + uint32_t ret = 0; + int pstate; + + DEVRLOCK(cgpu, pstate); + + if (cgpu->usbdev) + ret = cgpu->usbdev->bufamt; + + DEVRUNLOCK(cgpu, pstate); + + return ret; +} + +/* + * The value returned (0) when usbdev is NULL + * doesn't matter since it also means the next call to + * any usbutils function will fail with a nodev + * N.B. this is to get the interface number to use in a control_transfer + * which for some devices isn't actually the interface number + */ +int _usb_interface(struct cgpu_info *cgpu, int intinfo) +{ + int interface = 0; + int pstate; + + DEVRLOCK(cgpu, pstate); + + if (cgpu->usbdev) + interface = cgpu->usbdev->found->intinfos[intinfo].ctrl_transfer; + + DEVRUNLOCK(cgpu, pstate); + + return interface; +} + +enum sub_ident usb_ident(struct cgpu_info *cgpu) +{ + enum sub_ident ident = IDENT_UNK; + int pstate; + + DEVRLOCK(cgpu, pstate); + + if (cgpu->usbdev) + ident = cgpu->usbdev->ident; + + DEVRUNLOCK(cgpu, pstate); + + return ident; +} + +// Need to set all devices with matching usbdev +void usb_set_dev_start(struct cgpu_info *cgpu) +{ + struct cg_usb_device *cgusb; + struct cgpu_info *cgpu2; + struct timeval now; + int pstate; + + DEVWLOCK(cgpu, pstate); + + cgusb = cgpu->usbdev; + + // If the device wasn't dropped + if (cgusb != NULL) { + int i; + + cgtime(&now); + + for (i = 0; i < total_devices; i++) { + cgpu2 = get_devices(i); + if (cgpu2->usbdev == cgusb) + copy_time(&(cgpu2->dev_start_tv), &now); + } + } + + DEVWUNLOCK(cgpu, pstate); +} + +void usb_cleanup(void) +{ + struct cgpu_info *cgpu; + int count, pstate; + int i; + + hotplug_time = 0; + + cgsleep_ms(10); + + count = 0; + for (i = 0; i < total_devices; i++) { + cgpu = devices[i]; + switch (cgpu->drv->drv_id) { + case DRIVER_bflsc: + case DRIVER_bitforce: + case DRIVER_bitfury: + case DRIVER_cointerra: + case DRIVER_drillbit: + case DRIVER_modminer: + case DRIVER_icarus: + case DRIVER_avalon: + case DRIVER_avalon2: + case DRIVER_avalon4: + case DRIVER_bitmain: + case DRIVER_bmsc: + case DRIVER_klondike: + case DRIVER_hashfast: + DEVWLOCK(cgpu, pstate); + release_cgpu(cgpu); + DEVWUNLOCK(cgpu, pstate); + count++; + break; + default: + break; + } + } + + /* + * Must attempt to wait for the resource thread to release coz + * during a restart it won't automatically release them in linux + */ + if (count) { + struct timeval start, now; + + cgtime(&start); + while (42) { + cgsleep_ms(50); + + mutex_lock(&cgusbres_lock); + + if (!res_work_head) + break; + + cgtime(&now); + if (tdiff(&now, &start) > 0.366) { + applog(LOG_WARNING, + "usb_cleanup gave up waiting for resource thread"); + break; + } + + mutex_unlock(&cgusbres_lock); + } + mutex_unlock(&cgusbres_lock); + } + + cgsem_destroy(&usb_resource_sem); +} + +#define DRIVER_COUNT_FOUND(X) if (X##_drv.name && strcasecmp(ptr, X##_drv.name) == 0) { \ + drv_count[X##_drv.drv_id].limit = lim; \ + found = true; \ + } +void usb_initialise(void) +{ + char *fre, *ptr, *comma, *colon; + int bus, dev, lim, i; + bool found; + + INIT_LIST_HEAD(&ut_list); + + for (i = 0; i < DRIVER_MAX; i++) { + drv_count[i].count = 0; + drv_count[i].limit = 999999; + } + + cgusb_check_init(); + + if (opt_usb_select && *opt_usb_select) { + // Absolute device limit + if (*opt_usb_select == ':') { + total_limit = atoi(opt_usb_select+1); + if (total_limit < 0) + quit(1, "Invalid --usb total limit"); + // Comma list of bus:dev devices to match + } else if (isdigit(*opt_usb_select)) { + fre = ptr = strdup(opt_usb_select); + do { + comma = strchr(ptr, ','); + if (comma) + *(comma++) = '\0'; + + colon = strchr(ptr, ':'); + if (!colon) + quit(1, "Invalid --usb bus:dev missing ':'"); + + *(colon++) = '\0'; + + if (!isdigit(*ptr)) + quit(1, "Invalid --usb bus:dev - bus must be a number"); + + if (!isdigit(*colon) && *colon != '*') + quit(1, "Invalid --usb bus:dev - dev must be a number or '*'"); + + bus = atoi(ptr); + if (bus <= 0) + quit(1, "Invalid --usb bus:dev - bus must be > 0"); + + if (*colon == '*') + dev = -1; + else { + dev = atoi(colon); + if (dev <= 0) + quit(1, "Invalid --usb bus:dev - dev must be > 0 or '*'"); + } + + busdev = realloc(busdev, sizeof(*busdev) * (++busdev_count)); + if (unlikely(!busdev)) + quit(1, "USB failed to realloc busdev"); + + busdev[busdev_count-1].bus_number = bus; + busdev[busdev_count-1].device_address = dev; + + ptr = comma; + } while (ptr); + free(fre); + // Comma list of DRV:limit + } else { + fre = ptr = strdup(opt_usb_select); + do { + comma = strchr(ptr, ','); + if (comma) + *(comma++) = '\0'; + + colon = strchr(ptr, ':'); + if (!colon) + quit(1, "Invalid --usb DRV:limit missing ':'"); + + *(colon++) = '\0'; + + if (!isdigit(*colon)) + quit(1, "Invalid --usb DRV:limit - limit must be a number"); + + lim = atoi(colon); + if (lim < 0) + quit(1, "Invalid --usb DRV:limit - limit must be >= 0"); + + found = false; + /* Use the DRIVER_PARSE_COMMANDS macro to iterate + * over all the drivers. */ + DRIVER_PARSE_COMMANDS(DRIVER_COUNT_FOUND) + if (!found) + quit(1, "Invalid --usb DRV:limit - unknown DRV='%s'", ptr); + + ptr = comma; + } while (ptr); + free(fre); + } + } +} + +#ifndef WIN32 +#include +#include +#include +#include +#include + +#ifndef __APPLE__ +union semun { + int val; + struct semid_ds *buf; + unsigned short *array; + struct seminfo *__buf; +}; +#endif + +#else +static LPSECURITY_ATTRIBUTES unsec(LPSECURITY_ATTRIBUTES sec) +{ + FreeSid(((PSECURITY_DESCRIPTOR)(sec->lpSecurityDescriptor))->Group); + free(sec->lpSecurityDescriptor); + free(sec); + return NULL; +} + +static LPSECURITY_ATTRIBUTES mksec(const char *dname, uint8_t bus_number, uint8_t device_address) +{ + SID_IDENTIFIER_AUTHORITY SIDAuthWorld = {SECURITY_WORLD_SID_AUTHORITY}; + PSID gsid = NULL; + LPSECURITY_ATTRIBUTES sec_att = NULL; + PSECURITY_DESCRIPTOR sec_des = NULL; + + sec_des = malloc(sizeof(*sec_des)); + if (unlikely(!sec_des)) + quit(1, "MTX: Failed to malloc LPSECURITY_DESCRIPTOR"); + + if (!InitializeSecurityDescriptor(sec_des, SECURITY_DESCRIPTOR_REVISION)) { + applog(LOG_ERR, + "MTX: %s (%d:%d) USB failed to init secdes err (%d)", + dname, (int)bus_number, (int)device_address, + (int)GetLastError()); + free(sec_des); + return NULL; + } + + if (!SetSecurityDescriptorDacl(sec_des, TRUE, NULL, FALSE)) { + applog(LOG_ERR, + "MTX: %s (%d:%d) USB failed to secdes dacl err (%d)", + dname, (int)bus_number, (int)device_address, + (int)GetLastError()); + free(sec_des); + return NULL; + } + + if(!AllocateAndInitializeSid(&SIDAuthWorld, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &gsid)) { + applog(LOG_ERR, + "MTX: %s (%d:%d) USB failed to create gsid err (%d)", + dname, (int)bus_number, (int)device_address, + (int)GetLastError()); + free(sec_des); + return NULL; + } + + if (!SetSecurityDescriptorGroup(sec_des, gsid, FALSE)) { + applog(LOG_ERR, + "MTX: %s (%d:%d) USB failed to secdes grp err (%d)", + dname, (int)bus_number, (int)device_address, + (int)GetLastError()); + FreeSid(gsid); + free(sec_des); + return NULL; + } + + sec_att = malloc(sizeof(*sec_att)); + if (unlikely(!sec_att)) + quit(1, "MTX: Failed to malloc LPSECURITY_ATTRIBUTES"); + + sec_att->nLength = sizeof(*sec_att); + sec_att->lpSecurityDescriptor = sec_des; + sec_att->bInheritHandle = FALSE; + + return sec_att; +} +#endif + +// Any errors should always be printed since they will rarely if ever occur +// and thus it is best to always display them +static bool resource_lock(const char *dname, uint8_t bus_number, uint8_t device_address) +{ + applog(LOG_DEBUG, "USB res lock %s %d-%d", dname, (int)bus_number, (int)device_address); +#ifdef WIN32 + struct cgpu_info *cgpu; + LPSECURITY_ATTRIBUTES sec; + HANDLE usbMutex; + char name[64]; + DWORD res; + int i; + + if (is_in_use_bd(bus_number, device_address)) + return false; + + snprintf(name, sizeof(name), "cg-usb-%d-%d", (int)bus_number, (int)device_address); + + sec = mksec(dname, bus_number, device_address); + if (!sec) + return false; + + usbMutex = CreateMutex(sec, FALSE, name); + if (usbMutex == NULL) { + applog(LOG_ERR, + "MTX: %s USB failed to get '%s' err (%d)", + dname, name, (int)GetLastError()); + sec = unsec(sec); + return false; + } + + res = WaitForSingleObject(usbMutex, 0); + + switch(res) { + case WAIT_OBJECT_0: + case WAIT_ABANDONED: + // Am I using it already? + for (i = 0; i < total_devices; i++) { + cgpu = get_devices(i); + if (cgpu->usbinfo.bus_number == bus_number && + cgpu->usbinfo.device_address == device_address && + cgpu->usbinfo.nodev == false) { + if (ReleaseMutex(usbMutex)) { + applog(LOG_WARNING, + "MTX: %s USB can't get '%s' - device in use", + dname, name); + goto fail; + } + applog(LOG_ERR, + "MTX: %s USB can't get '%s' - device in use - failure (%d)", + dname, name, (int)GetLastError()); + goto fail; + } + } + break; + case WAIT_TIMEOUT: + if (!hotplug_mode) + applog(LOG_WARNING, + "MTX: %s USB failed to get '%s' - device in use", + dname, name); + goto fail; + case WAIT_FAILED: + applog(LOG_ERR, + "MTX: %s USB failed to get '%s' err (%d)", + dname, name, (int)GetLastError()); + goto fail; + default: + applog(LOG_ERR, + "MTX: %s USB failed to get '%s' unknown reply (%d)", + dname, name, (int)res); + goto fail; + } + + add_in_use(bus_number, device_address, false); + in_use_store_ress(bus_number, device_address, (void *)usbMutex, (void *)sec); + + return true; +fail: + CloseHandle(usbMutex); + sec = unsec(sec); + return false; +#else + char name[64]; + int fd; + + if (is_in_use_bd(bus_number, device_address)) + return false; + + snprintf(name, sizeof(name), "/tmp/bmminer-usb-%d-%d", (int)bus_number, (int)device_address); + fd = open(name, O_CREAT|O_RDONLY, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); + if (fd == -1) { + applog(LOG_ERR, "%s USB open failed '%s' err (%d) %s", + dname, name, errno, strerror(errno)); + return false; + } + if (flock(fd, LOCK_EX | LOCK_NB)) { + applog(LOG_INFO, "%s USB failed to get '%s' - device in use", + dname, name); + close(fd); + return false; + } + + add_in_use(bus_number, device_address, false); + in_use_store_fd(bus_number, device_address, fd); + return true; +#endif +} + +// Any errors should always be printed since they will rarely if ever occur +// and thus it is best to always display them +static void resource_unlock(const char *dname, uint8_t bus_number, uint8_t device_address) +{ + applog(LOG_DEBUG, "USB res unlock %s %d-%d", dname, (int)bus_number, (int)device_address); + +#ifdef WIN32 + LPSECURITY_ATTRIBUTES sec = NULL; + HANDLE usbMutex = NULL; + char name[64]; + + snprintf(name, sizeof(name), "cg-usb-%d-%d", (int)bus_number, (int)device_address); + + in_use_get_ress(bus_number, device_address, (void **)(&usbMutex), (void **)(&sec)); + + if (!usbMutex || !sec) + goto fila; + + if (!ReleaseMutex(usbMutex)) + applog(LOG_ERR, + "MTX: %s USB failed to release '%s' err (%d)", + dname, name, (int)GetLastError()); + +fila: + + if (usbMutex) + CloseHandle(usbMutex); + if (sec) + unsec(sec); + remove_in_use(bus_number, device_address); + return; +#else + char name[64]; + int fd; + + snprintf(name, sizeof(name), "/tmp/bmminer-usb-%d-%d", (int)bus_number, (int)device_address); + + fd = in_use_get_fd(bus_number, device_address); + if (fd < 0) + return; + remove_in_use(bus_number, device_address); + close(fd); + unlink(name); + return; +#endif +} + +static void resource_process() +{ + struct resource_work *res_work = NULL; + struct resource_reply *res_reply = NULL; + bool ok; + + applog(LOG_DEBUG, "RES: %s (%d:%d) lock=%d", + res_work_head->dname, + (int)res_work_head->bus_number, + (int)res_work_head->device_address, + res_work_head->lock); + + if (res_work_head->lock) { + ok = resource_lock(res_work_head->dname, + res_work_head->bus_number, + res_work_head->device_address); + + applog(LOG_DEBUG, "RES: %s (%d:%d) lock ok=%d", + res_work_head->dname, + (int)res_work_head->bus_number, + (int)res_work_head->device_address, + ok); + + res_reply = calloc(1, sizeof(*res_reply)); + if (unlikely(!res_reply)) + quit(1, "USB failed to calloc res_reply"); + + res_reply->bus_number = res_work_head->bus_number; + res_reply->device_address = res_work_head->device_address; + res_reply->got = ok; + res_reply->next = res_reply_head; + + res_reply_head = res_reply; + } + else + resource_unlock(res_work_head->dname, + res_work_head->bus_number, + res_work_head->device_address); + + res_work = res_work_head; + res_work_head = res_work_head->next; + free(res_work); +} + +void *usb_resource_thread(void __maybe_unused *userdata) +{ + pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); + + RenameThread("USBResource"); + + applog(LOG_DEBUG, "RES: thread starting"); + + while (42) { + /* Wait to be told we have work to do */ + cgsem_wait(&usb_resource_sem); + + mutex_lock(&cgusbres_lock); + while (res_work_head) + resource_process(); + mutex_unlock(&cgusbres_lock); + } + + return NULL; +} + +void initialise_usblocks(void) +{ + mutex_init(&cgusb_lock); + mutex_init(&cgusbres_lock); + cglock_init(&cgusb_fd_lock); +} + +#ifdef USE_BITMAIN + +struct cgpu_info *btm_alloc_cgpu(struct device_drv *drv, int threads) +{ + struct cgpu_info *cgpu = calloc(1, sizeof(*cgpu)); + + if (unlikely(!cgpu)) + quit(1, "Failed to calloc cgpu for %s in usb_alloc_cgpu", drv->dname); + + cgpu->drv = drv; + cgpu->deven = DEV_ENABLED; + cgpu->threads = threads; + + cgpu->usbinfo.nodev = true; + cgpu->device_fd = -1; + + cglock_init(&cgpu->usbinfo.devlock); + + return cgpu; +} + +struct cgpu_info *btm_free_cgpu(struct cgpu_info *cgpu) +{ + if (cgpu->drv->copy) + free(cgpu->drv); + + if(cgpu->device_path) { + free(cgpu->device_path); + } + + free(cgpu); + + return NULL; +} + +bool btm_init(struct cgpu_info *cgpu, const char * devpath) +{ +#ifdef WIN32 + int fd = -1; + signed short timeout = 1; + unsigned long baud = 115200; + bool purge = true; + HANDLE hSerial = NULL; + applog(LOG_DEBUG, "btm_init cgpu->device_fd=%d", cgpu->device_fd); + if(cgpu->device_fd >= 0) { + return false; + } + hSerial = CreateFile(devpath, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); + if (unlikely(hSerial == INVALID_HANDLE_VALUE)) + { + DWORD e = GetLastError(); + switch (e) { + case ERROR_ACCESS_DENIED: + applog(LOG_DEBUG, "Do not have user privileges required to open %s", devpath); + break; + case ERROR_SHARING_VIOLATION: + applog(LOG_DEBUG, "%s is already in use by another process", devpath); + break; + default: + applog(LOG_DEBUG, "Open %s failed, GetLastError:%d", devpath, (int)e); + break; + } + } else { + // thanks to af_newbie for pointers about this + COMMCONFIG comCfg = {0}; + comCfg.dwSize = sizeof(COMMCONFIG); + comCfg.wVersion = 1; + comCfg.dcb.DCBlength = sizeof(DCB); + comCfg.dcb.BaudRate = baud; + comCfg.dcb.fBinary = 1; + comCfg.dcb.fDtrControl = DTR_CONTROL_ENABLE; + comCfg.dcb.fRtsControl = RTS_CONTROL_ENABLE; + comCfg.dcb.ByteSize = 8; + + SetCommConfig(hSerial, &comCfg, sizeof(comCfg)); + + // Code must specify a valid timeout value (0 means don't timeout) + const DWORD ctoms = (timeout * 100); + COMMTIMEOUTS cto = {ctoms, 0, ctoms, 0, ctoms}; + SetCommTimeouts(hSerial, &cto); + + if (purge) { + PurgeComm(hSerial, PURGE_RXABORT); + PurgeComm(hSerial, PURGE_TXABORT); + PurgeComm(hSerial, PURGE_RXCLEAR); + PurgeComm(hSerial, PURGE_TXCLEAR); + } + fd = _open_osfhandle((intptr_t)hSerial, 0); + } +#else + int fd = -1; + if(cgpu->device_fd >= 0) { + return false; + } + fd = open(devpath, O_RDWR|O_EXCL|O_NONBLOCK); +#endif + if(fd == -1) { + applog(LOG_DEBUG, "%s open %s error %d", + cgpu->drv->dname, devpath, errno); + return false; + } + cgpu->device_path = strdup(devpath); + cgpu->device_fd = fd; + cgpu->usbinfo.nodev = false; + applog(LOG_DEBUG, "btm_init open device fd = %d", cgpu->device_fd); + return true; +} + +void btm_uninit(struct cgpu_info *cgpu) +{ + applog(LOG_DEBUG, "BTM uninit %s%i", cgpu->drv->name, cgpu->device_fd); + + // May have happened already during a failed initialisation + // if release_cgpu() was called due to a USB NODEV(err) + close(cgpu->device_fd); + if(cgpu->device_path) { + free(cgpu->device_path); + cgpu->device_path = NULL; + } +} + +void btm_detect(struct device_drv *drv, bool (*device_detect)(const char*)) +{ + ssize_t count, i; + + applog(LOG_DEBUG, "BTM scan devices: checking for %s devices", drv->name); + + if (total_count >= total_limit) { + applog(LOG_DEBUG, "BTM scan devices: total limit %d reached", total_limit); + return; + } + + if (drv_count[drv->drv_id].count >= drv_count[drv->drv_id].limit) { + applog(LOG_DEBUG, + "BTM scan devices: %s limit %d reached", + drv->dname, drv_count[drv->drv_id].limit); + return; + } + device_detect("asic"); +} + +int btm_read(struct cgpu_info *cgpu, char *buf, size_t bufsize) +{ + int err = 0; + //applog(LOG_DEBUG, "btm_read ----- %d -----", bufsize); + err = read(cgpu->device_fd, buf, bufsize); + return err; +} + +int btm_write(struct cgpu_info *cgpu, char *buf, size_t bufsize) +{ + int err = 0; + //applog(LOG_DEBUG, "btm_write ----- %d -----", bufsize); + err = write(cgpu->device_fd, buf, bufsize); + return err; +} + +#endif diff --git a/usbutils.h b/usbutils.h new file mode 100644 index 0000000..b1bd64f --- /dev/null +++ b/usbutils.h @@ -0,0 +1,610 @@ +/* + * Copyright 2012-2013 Andrew Smith + * Copyright 2013-2014 Con Kolivas + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#ifndef USBUTILS_H +#define USBUTILS_H + +#include + +#include "util.h" + +#define EPI(x) (LIBUSB_ENDPOINT_IN | (unsigned char)(x)) +#define EPO(x) (LIBUSB_ENDPOINT_OUT | (unsigned char)(x)) + + +// For 0x0403:0x6014/0x6001 FT232H (and possibly others?) - BFL, BAS, BLT, LLT, AVA +#define FTDI_TYPE_OUT (LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE | LIBUSB_ENDPOINT_OUT) +#define FTDI_TYPE_IN (LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE | LIBUSB_ENDPOINT_IN) + +#define FTDI_REQUEST_RESET ((uint8_t)0) +#define FTDI_REQUEST_MODEM ((uint8_t)1) +#define FTDI_REQUEST_FLOW ((uint8_t)2) +#define FTDI_REQUEST_BAUD ((uint8_t)3) +#define FTDI_REQUEST_DATA ((uint8_t)4) +#define FTDI_REQUEST_LATENCY ((uint8_t)9) + +#define FTDI_VALUE_RESET 0 +#define FTDI_VALUE_PURGE_RX 1 +#define FTDI_VALUE_PURGE_TX 2 +#define FTDI_VALUE_LATENCY 1 + +// Baud +#define FTDI_VALUE_BAUD_BFL 0xc068 +#define FTDI_INDEX_BAUD_BFL 0x0200 +#define FTDI_VALUE_BAUD_BAS FTDI_VALUE_BAUD_BFL +#define FTDI_INDEX_BAUD_BAS FTDI_INDEX_BAUD_BFL +// LLT = BLT (same code) +#define FTDI_VALUE_BAUD_BLT 0x001a +#define FTDI_INDEX_BAUD_BLT 0x0000 + +// Avalon +#define FTDI_VALUE_BAUD_AVA 0x001A +#define FTDI_INDEX_BAUD_AVA 0x0000 + +#define FTDI_VALUE_DATA_AVA 8 + +// Bitmain +#define FTDI_VALUE_BAUD_BTM 0x001A +#define FTDI_INDEX_BAUD_BTM 0x0000 + +#define FTDI_VALUE_DATA_BTM 8 + +// BitBurner +#define BITBURNER_REQUEST ((uint8_t)0x42) +#define BITBURNER_VALUE 0x4242 +#define BITBURNER_INDEX_SET_VOLTAGE 1 +#define BITBURNER_INDEX_GET_VOLTAGE 2 +#define BITBURNER_INDEX_GET_VERSION 4 + +// CMR = 115200 & 57600 +#define FTDI_VALUE_BAUD_CMR_115 0xc068 +#define FTDI_INDEX_BAUD_CMR_115 0x0200 + +#define FTDI_VALUE_BAUD_CMR_57 0x80d0 +#define FTDI_INDEX_BAUD_CMR_57 0x0200 + +// Data control +#define FTDI_VALUE_DATA_BFL 0 +#define FTDI_VALUE_DATA_BAS FTDI_VALUE_DATA_BFL +// LLT = BLT (same code) +#define FTDI_VALUE_DATA_BLT 8 + +#define FTDI_VALUE_FLOW 0 +#define FTDI_VALUE_MODEM 0x0303 + + +// For 0x10c4:0xea60 USB cp210x chip - AMU +#define CP210X_TYPE_OUT 0x41 + +#define CP210X_REQUEST_IFC_ENABLE 0x00 +#define CP210X_REQUEST_DATA 0x07 +#define CP210X_REQUEST_BAUD 0x1e + +#define CP210X_VALUE_UART_ENABLE 0x0001 +#define CP210X_VALUE_DATA 0x0303 +#define CP210X_DATA_BAUD 0x0001c200 + +#define CP210X_SET_LINE_CTL 0x03 +#define CP210X_BITS_DATA_MASK 0x0f00 +#define CP210X_BITS_DATA_8 0x0800 +#define CP210X_BITS_PARITY_MARK 0x0030 +#define CP210X_BITS_PARITY_SPACE 0x0040 + + +// For 0x067b:0x2303 Prolific PL2303 - ICA +#define PL2303_CTRL_DTR 0x01 +#define PL2303_CTRL_RTS 0x02 + +#define PL2303_CTRL_OUT 0x21 +#define PL2303_VENDOR_OUT 0x40 + +#define PL2303_REQUEST_CTRL 0x22 +#define PL2303_REQUEST_LINE 0x20 +#define PL2303_REQUEST_VENDOR 0x01 + +#define PL2303_REPLY_CTRL 0x21 + +#define PL2303_VALUE_CTRL (PL2303_CTRL_DTR | PL2303_CTRL_RTS) +#define PL2303_VALUE_LINE 0 +#define PL2303_VALUE_LINE0 0x0001c200 +#define PL2303_VALUE_LINE1 0x080000 +#define PL2303_VALUE_LINE_SIZE 7 +#define PL2303_VALUE_VENDOR 0 + +// Use the device defined timeout +#define DEVTIMEOUT 0 + +// The default intinfo structure used is the first one +#define DEFAULT_INTINFO 0 + +// For endpoints defined in usb_find_devices.intinfos.epinfos, +// the first two must be the default IN and OUT and both must always exist +#define DEFAULT_EP_IN 0 +#define DEFAULT_EP_OUT 1 + +struct usb_epinfo { + uint8_t att; + uint16_t size; + unsigned char ep; + uint16_t wMaxPacketSize; + bool found; +}; + +struct usb_intinfo { + int interface; + int ctrl_transfer; + int epinfo_count; + struct usb_epinfo *epinfos; +}; + +enum sub_ident { + IDENT_UNK = 0, + IDENT_AMU, + IDENT_ANU, + IDENT_BMM, + IDENT_BMS, + IDENT_AU3, + IDENT_AVA, + IDENT_AV2, + IDENT_AV4, + IDENT_BAJ, + IDENT_BAL, + IDENT_BAM, + IDENT_BAS, + IDENT_BBF, + IDENT_BET, + IDENT_BF1, + IDENT_BFL, + IDENT_BLT, + IDENT_BMA, + IDENT_BTB, + IDENT_BXF, + IDENT_BXM, + IDENT_CMR1, + IDENT_CMR2, + IDENT_CTA, + IDENT_DRB, + IDENT_HFA, + IDENT_HRO, + IDENT_ICA, + IDENT_KLN, + IDENT_LIN, + IDENT_LLT, + IDENT_MMQ, + IDENT_NFU, + IDENT_OSM +}; + +struct usb_find_devices { + int drv; + const char *name; + enum sub_ident ident; + uint16_t idVendor; + uint16_t idProduct; + char *iManufacturer; + char *iProduct; + int config; + unsigned int timeout; + uint16_t latency; + int intinfo_count; + struct usb_intinfo *intinfos; +}; + +/* Latency is set to 32ms to prevent a transfer ever being more than 512 bytes + * +2 bytes of status such as the ftdi chip, when the chips emulate a 115200 + * baud rate, to avoid status bytes being interleaved in larger transfers. */ +#define LATENCY_UNUSED 0 +#define LATENCY_STD 32 + +enum usb_types { + USB_TYPE_STD = 0, + USB_TYPE_FTDI +}; + +#define USB_MAX_READ 8192 +/* + * We add 4: 1 for null, 2 for FTDI status and 1 to round to 4 bytes + * If a single device ever has multiple end points then it will need + * multiple of these + */ +#define USB_READ_BUFSIZE (USB_MAX_READ + 4) + +struct cg_usb_device { + struct usb_find_devices *found; + libusb_device_handle *handle; + pthread_mutex_t *mutex; + struct libusb_device_descriptor *descriptor; + enum usb_types usb_type; + enum sub_ident ident; + uint16_t usbver; + char *prod_string; + char *manuf_string; + char *serial_string; + unsigned char fwVersion; // ?? + unsigned char interfaceVersion; // ?? + char buffer[USB_MAX_READ]; + uint32_t bufsiz; + uint32_t bufamt; + bool usb11; // USB 1.1 flag for convenience + bool tt; // Enable the transaction translator +}; + +#define USB_NOSTAT 0 + +#define USB_TMO_0 50 +#define USB_TMO_1 100 +#define USB_TMO_2 500 +#define USB_TMOS 3 + +struct cg_usb_tmo { + uint32_t count; + uint32_t min_tmo; + uint32_t max_tmo; + uint64_t total_over; + uint64_t total_tmo; +}; + +struct cg_usb_info { + uint8_t bus_number; + uint8_t device_address; + int usbstat; + bool nodev; + bool initialised; + int nodev_count; + struct timeval last_nodev; + uint32_t ioerr_count; + uint32_t continuous_ioerr_count; + + /* + * for nodev and cgusb access (read and write) + * it's a pointer so MMQ can have it in multiple devices + * + * N.B. general mining code doesn't need to use the read + * lock for 'nodev' if it calls a usb_read/write/etc function + * that uses the lock - however, all usbutils code MUST use it + * to avoid devices disappearing while in use by multiple threads + */ + cglock_t devlock; + + time_t last_pipe; + uint64_t pipe_count; + uint64_t clear_err_count; + uint64_t retry_err_count; + uint64_t clear_fail_count; + + uint64_t read_delay_count; + double total_read_delay; + uint64_t write_delay_count; + double total_write_delay; + + uint64_t tmo_count; + struct cg_usb_tmo usb_tmo[USB_TMOS]; +}; + +#define ENUMERATION(a,b) a, +#define JUMPTABLE(a,b) b, + +#define USB_PARSE_COMMANDS(USB_ADD_COMMAND) \ + USB_ADD_COMMAND(C_REJECTED, "RejectedNoDevice") \ + USB_ADD_COMMAND(C_PING, "Ping") \ + USB_ADD_COMMAND(C_CLEAR, "Clear") \ + USB_ADD_COMMAND(C_REQUESTVERSION, "RequestVersion") \ + USB_ADD_COMMAND(C_GETVERSION, "GetVersion") \ + USB_ADD_COMMAND(C_REQUESTFPGACOUNT, "RequestFPGACount") \ + USB_ADD_COMMAND(C_GETFPGACOUNT, "GetFPGACount") \ + USB_ADD_COMMAND(C_STARTPROGRAM, "StartProgram") \ + USB_ADD_COMMAND(C_STARTPROGRAMSTATUS, "StartProgramStatus") \ + USB_ADD_COMMAND(C_PROGRAM, "Program") \ + USB_ADD_COMMAND(C_PROGRAMSTATUS, "ProgramStatus") \ + USB_ADD_COMMAND(C_PROGRAMSTATUS2, "ProgramStatus2") \ + USB_ADD_COMMAND(C_FINALPROGRAMSTATUS, "FinalProgramStatus") \ + USB_ADD_COMMAND(C_SETCLOCK, "SetClock") \ + USB_ADD_COMMAND(C_SETPARITY, "SetParity") \ + USB_ADD_COMMAND(C_REPLYSETCLOCK, "ReplySetClock") \ + USB_ADD_COMMAND(C_SETVOLT, "SetVolt") \ + USB_ADD_COMMAND(C_REPLYSETVOLT, "ReplySetVolt") \ + USB_ADD_COMMAND(C_REQUESTUSERCODE, "RequestUserCode") \ + USB_ADD_COMMAND(C_GETUSERCODE, "GetUserCode") \ + USB_ADD_COMMAND(C_REQUESTTEMPERATURE, "RequestTemperature") \ + USB_ADD_COMMAND(C_GETTEMPERATURE, "GetTemperature") \ + USB_ADD_COMMAND(C_SENDWORK, "SendWork") \ + USB_ADD_COMMAND(C_SENDWORKSTATUS, "SendWorkStatus") \ + USB_ADD_COMMAND(C_REQUESTWORKSTATUS, "RequestWorkStatus") \ + USB_ADD_COMMAND(C_GETWORKSTATUS, "GetWorkStatus") \ + USB_ADD_COMMAND(C_REQUESTIDENTIFY, "RequestIdentify") \ + USB_ADD_COMMAND(C_GETIDENTIFY, "GetIdentify") \ + USB_ADD_COMMAND(C_REQUESTFLASH, "RequestFlash") \ + USB_ADD_COMMAND(C_REQUESTSENDWORK, "RequestSendWork") \ + USB_ADD_COMMAND(C_REQUESTSENDWORKSTATUS, "RequestSendWorkStatus") \ + USB_ADD_COMMAND(C_RESET, "Reset") \ + USB_ADD_COMMAND(C_SETBAUD, "SetBaud") \ + USB_ADD_COMMAND(C_SETDATA, "SetDataCtrl") \ + USB_ADD_COMMAND(C_SETFLOW, "SetFlowCtrl") \ + USB_ADD_COMMAND(C_SETMODEM, "SetModemCtrl") \ + USB_ADD_COMMAND(C_PURGERX, "PurgeRx") \ + USB_ADD_COMMAND(C_PURGETX, "PurgeTx") \ + USB_ADD_COMMAND(C_FLASHREPLY, "FlashReply") \ + USB_ADD_COMMAND(C_REQUESTDETAILS, "RequestDetails") \ + USB_ADD_COMMAND(C_GETDETAILS, "GetDetails") \ + USB_ADD_COMMAND(C_REQUESTRESULTS, "RequestResults") \ + USB_ADD_COMMAND(C_GETRESULTS, "GetResults") \ + USB_ADD_COMMAND(C_REQUESTQUEJOB, "RequestQueJob") \ + USB_ADD_COMMAND(C_REQUESTQUEJOBSTATUS, "RequestQueJobStatus") \ + USB_ADD_COMMAND(C_QUEJOB, "QueJob") \ + USB_ADD_COMMAND(C_QUEJOBSTATUS, "QueJobStatus") \ + USB_ADD_COMMAND(C_QUEFLUSH, "QueFlush") \ + USB_ADD_COMMAND(C_QUEFLUSHREPLY, "QueFlushReply") \ + USB_ADD_COMMAND(C_REQUESTVOLTS, "RequestVolts") \ + USB_ADD_COMMAND(C_GETVOLTS, "GetVolts") \ + USB_ADD_COMMAND(C_SENDTESTWORK, "SendTestWork") \ + USB_ADD_COMMAND(C_LATENCY, "SetLatency") \ + USB_ADD_COMMAND(C_SETLINE, "SetLine") \ + USB_ADD_COMMAND(C_VENDOR, "Vendor") \ + USB_ADD_COMMAND(C_SETFAN, "SetFan") \ + USB_ADD_COMMAND(C_FANREPLY, "GetFan") \ + USB_ADD_COMMAND(C_AVALON_TASK, "AvalonTask") \ + USB_ADD_COMMAND(C_AVALON_READ, "AvalonRead") \ + USB_ADD_COMMAND(C_GET_AVALON_READY, "AvalonReady") \ + USB_ADD_COMMAND(C_AVALON_RESET, "AvalonReset") \ + USB_ADD_COMMAND(C_GET_AVALON_RESET, "GetAvalonReset") \ + USB_ADD_COMMAND(C_FTDI_STATUS, "FTDIStatus") \ + USB_ADD_COMMAND(C_ENABLE_UART, "EnableUART") \ + USB_ADD_COMMAND(C_ANU_SEND_CMD, "ANUSendcmd") \ + USB_ADD_COMMAND(C_ANU_SEND_RDREG, "ANUSendrdreg") \ + USB_ADD_COMMAND(C_ANU_SEND_VOLT, "ANUSendvolt") \ + USB_ADD_COMMAND(C_BB_SET_VOLTAGE, "SetCoreVoltage") \ + USB_ADD_COMMAND(C_BB_GET_VOLTAGE, "GetCoreVoltage") \ + USB_ADD_COMMAND(C_BF_RESET, "BFReset") \ + USB_ADD_COMMAND(C_BF_OPEN, "BFOpen") \ + USB_ADD_COMMAND(C_BF_INIT, "BFInit") \ + USB_ADD_COMMAND(C_BF_CLOSE, "BFClose") \ + USB_ADD_COMMAND(C_BF_REQINFO, "BFRequestInfo") \ + USB_ADD_COMMAND(C_BF_GETINFO, "BFGetInfo") \ + USB_ADD_COMMAND(C_BF_REQRESET, "BFRequestReset") \ + USB_ADD_COMMAND(C_BF_GETRESET, "BFGetReset") \ + USB_ADD_COMMAND(C_BF_REQWORK, "BFRequestWork") \ + USB_ADD_COMMAND(C_BF_GETWORK, "BFGetWork") \ + USB_ADD_COMMAND(C_BF_GETRES, "BFGetResults") \ + USB_ADD_COMMAND(C_BF_FLUSH, "BFFlush") \ + USB_ADD_COMMAND(C_BF_IFLUSH, "BFInterruptFlush") \ + USB_ADD_COMMAND(C_BF_IDENTIFY, "BFIdentify") \ + USB_ADD_COMMAND(C_BF_DETECTCHIPS, "BFDetectChips") \ + USB_ADD_COMMAND(C_BF_CONFIG, "BFConfig") \ + USB_ADD_COMMAND(C_BF_GETTEMP, "BFGetTemp") \ + USB_ADD_COMMAND(C_BF_AUTOTUNE, "BFAutoTune") \ + USB_ADD_COMMAND(C_ATMEL_RESET, "AtmelReset") \ + USB_ADD_COMMAND(C_ATMEL_OPEN, "AtmelOpen") \ + USB_ADD_COMMAND(C_ATMEL_INIT, "AtmelInit") \ + USB_ADD_COMMAND(C_ATMEL_CLOSE, "AtmelClose") \ + USB_ADD_COMMAND(C_AVA2_READ, "Ava2Read") \ + USB_ADD_COMMAND(C_AVA2_WRITE, "Ava2Write") \ + USB_ADD_COMMAND(C_AVA4_READ, "Ava4Read") \ + USB_ADD_COMMAND(C_AVA4_WRITE, "Ava4Write") \ + USB_ADD_COMMAND(C_BET_WRITE, "BlockErupterWrite") \ + USB_ADD_COMMAND(C_BET_READ, "BlockErupterRead") \ + USB_ADD_COMMAND(C_BF1_REQINFO, "BF1RequestInfo") \ + USB_ADD_COMMAND(C_BF1_GETINFO, "BF1GetInfo") \ + USB_ADD_COMMAND(C_BF1_REQRESET, "BF1RequestReset") \ + USB_ADD_COMMAND(C_BF1_GETRESET, "BF1GetReset") \ + USB_ADD_COMMAND(C_BF1_REQWORK, "BF1RequestWork") \ + USB_ADD_COMMAND(C_BF1_GETWORK, "BF1GetWork") \ + USB_ADD_COMMAND(C_BF1_GETRES, "BF1GetResults") \ + USB_ADD_COMMAND(C_BF1_FLUSH, "BF1Flush") \ + USB_ADD_COMMAND(C_BF1_IFLUSH, "BF1InterruptFlush") \ + USB_ADD_COMMAND(C_BF1_IDENTIFY, "BF1Identify") \ + USB_ADD_COMMAND(C_BXF_READ, "BXFRead") \ + USB_ADD_COMMAND(C_BXF_WORK, "BXFWork") \ + USB_ADD_COMMAND(C_BXF_TARGET, "BXFTarget") \ + USB_ADD_COMMAND(C_BXF_VERSION, "BXFVersion") \ + USB_ADD_COMMAND(C_BXF_MAXROLL, "BXFMaxRoll") \ + USB_ADD_COMMAND(C_BXF_FLUSH, "BXFFlush") \ + USB_ADD_COMMAND(C_BXF_CLOCK, "BXFClock") \ + USB_ADD_COMMAND(C_BXF_LEDMODE, "BXFLedMode") \ + USB_ADD_COMMAND(C_BXF_DEBUGMODE, "BXFDebugMode") \ + USB_ADD_COMMAND(C_BXM_FLUSH, "BXMFlush") \ + USB_ADD_COMMAND(C_BXM_SRESET, "BXMSReset") \ + USB_ADD_COMMAND(C_BXM_SETLATENCY, "BXMSetLatency") \ + USB_ADD_COMMAND(C_BXM_SECR, "BXMSetEventCharRequest") \ + USB_ADD_COMMAND(C_BXM_SETBITMODE, "BXMSetBitmodeRequest") \ + USB_ADD_COMMAND(C_BXM_CLOCK, "BXMClock") \ + USB_ADD_COMMAND(C_BXM_CLOCKDIV, "BXMClockDiv") \ + USB_ADD_COMMAND(C_BXM_LOOP, "BXMLoop") \ + USB_ADD_COMMAND(C_BXM_ADBUS, "BXMADBus") \ + USB_ADD_COMMAND(C_BXM_ACBUS, "BXMACBus") \ + USB_ADD_COMMAND(C_BXM_PURGERX, "BXMPurgeRX") \ + USB_ADD_COMMAND(C_BXM_PURGETX, "BXMPurgeTX") \ + USB_ADD_COMMAND(C_BXM_CSLOW, "BXMCSLow") \ + USB_ADD_COMMAND(C_BXM_CSHIGH, "BXMCSHigh") \ + USB_ADD_COMMAND(C_BXM_RESET, "BXMReset") \ + USB_ADD_COMMAND(C_BXM_SPITX, "BXMSPITX") \ + USB_ADD_COMMAND(C_BXM_SPIRX, "BXMSPIRX") \ + USB_ADD_COMMAND(C_HF_RESET, "HFReset") \ + USB_ADD_COMMAND(C_HF_PLL_CONFIG, "HFPLLConfig") \ + USB_ADD_COMMAND(C_HF_ADDRESS, "HFAddress") \ + USB_ADD_COMMAND(C_HF_BAUD, "HFBaud") \ + USB_ADD_COMMAND(C_HF_HASH, "HFHash") \ + USB_ADD_COMMAND(C_HF_NONCE, "HFNonce") \ + USB_ADD_COMMAND(C_HF_ABORT, "HFAbort") \ + USB_ADD_COMMAND(C_HF_STATUS, "HFStatus") \ + USB_ADD_COMMAND(C_HF_CONFIG, "HFConfig") \ + USB_ADD_COMMAND(C_HF_STATISTICS, "HFStatistics") \ + USB_ADD_COMMAND(C_HF_CLOCKGATE, "HFClockGate") \ + USB_ADD_COMMAND(C_HF_USB_INIT, "HFUSBInit") \ + USB_ADD_COMMAND(C_HF_DFU, "HFDFU") \ + USB_ADD_COMMAND(C_HF_DIE_STATUS, "HFDieStatus") \ + USB_ADD_COMMAND(C_HF_GWQ_STATUS, "HFGWQStatus") \ + USB_ADD_COMMAND(C_HF_WORK_RESTART, "HFWorkRestart") \ + USB_ADD_COMMAND(C_HF_GWQSTATS, "HFGWQStats") \ + USB_ADD_COMMAND(C_HF_NOTICE, "HFNotice") \ + USB_ADD_COMMAND(C_HF_PING, "HFPing") \ + USB_ADD_COMMAND(C_HF_FAN, "HFFan") \ + USB_ADD_COMMAND(C_HRO_WRITE, "HROWrite") \ + USB_ADD_COMMAND(C_HRO_READ, "HRORead") \ + USB_ADD_COMMAND(C_OP_NAME, "HFName") \ + USB_ADD_COMMAND(C_HF_GETHEADER, "HFGetHeader") \ + USB_ADD_COMMAND(C_HF_GETDATA, "HFGetData") \ + USB_ADD_COMMAND(C_HF_CLEAR_READ, "HFClearRead") \ + USB_ADD_COMMAND(C_CTA_READ, "CTARead") \ + USB_ADD_COMMAND(C_CTA_WRITE, "CTAWrite") \ + USB_ADD_COMMAND(C_MCP_GETGPIOSETTING, "MCPGetGPIOSetting") \ + USB_ADD_COMMAND(C_MCP_SETGPIOSETTING, "MCPSetGPIOSetting") \ + USB_ADD_COMMAND(C_MCP_GETGPIOPINVAL, "MCPGetGPIOPinVal") \ + USB_ADD_COMMAND(C_MCP_SETGPIOPINVAL, "MCPSetGPIOPinVal") \ + USB_ADD_COMMAND(C_MCP_GETGPIOPINDIR, "MCPGetGPIOPinDir") \ + USB_ADD_COMMAND(C_MCP_SETGPIOPINDIR, "MCPSetGPIOPinDir") \ + USB_ADD_COMMAND(C_MCP_SETSPISETTING, "MCPSetSPISetting") \ + USB_ADD_COMMAND(C_MCP_GETSPISETTING, "MCPGetSPISetting") \ + USB_ADD_COMMAND(C_MCP_SPITRANSFER, "MCPSPITransfer") \ + USB_ADD_COMMAND(C_MCP_SPICANCEL, "MCPSPICancel") \ + USB_ADD_COMMAND(C_BITMAIN_SEND, "BitmainSend") \ + USB_ADD_COMMAND(C_BITMAIN_READ, "BitmainRead") \ + USB_ADD_COMMAND(C_BITMAIN_TOKEN_TXCONFIG, "BitmainTokenTxConfig") \ + USB_ADD_COMMAND(C_BITMAIN_TOKEN_TXTASK, "BitmainTokenTxTask") \ + USB_ADD_COMMAND(C_BITMAIN_TOKEN_RXSTATUS, "BitmainTokenRxStatus") \ + USB_ADD_COMMAND(C_BITMAIN_DATA_RXSTATUS, "BitmainDataRxStatus") \ + USB_ADD_COMMAND(C_BITMAIN_DATA_RXNONCE, "BitmainDataRxNonce") + +/* Create usb_cmds enum from USB_PARSE_COMMANDS macro */ +enum usb_cmds { + USB_PARSE_COMMANDS(ENUMERATION) + C_MAX +}; + +struct device_drv; +struct cgpu_info; + +#ifdef USE_BITMAIN +struct cgpu_info *btm_alloc_cgpu(struct device_drv *drv, int threads); +struct cgpu_info *btm_free_cgpu(struct cgpu_info *cgpu); +void btm_uninit(struct cgpu_info *cgpu); +bool btm_init(struct cgpu_info *cgpu, const char * devpath); +void btm_detect(struct device_drv *drv, bool (*device_detect)(const char*)); +int btm_read(struct cgpu_info *cgpu, char *buf, size_t bufsize); +int btm_write(struct cgpu_info *cgpu, char *buf, size_t bufsize); +#endif + +bool async_usb_transfers(void); +void cancel_usb_transfers(void); +void usb_all(int level); +void usb_list(void); +const char *usb_cmdname(enum usb_cmds cmd); +void usb_applog(struct cgpu_info *cgpu, enum usb_cmds cmd, char *msg, int amount, int err); +void blacklist_cgpu(struct cgpu_info *cgpu); +void whitelist_cgpu(struct cgpu_info *cgpu); +void usb_nodev(struct cgpu_info *cgpu); +struct cgpu_info *usb_copy_cgpu(struct cgpu_info *orig); +struct cgpu_info *usb_alloc_cgpu(struct device_drv *drv, int threads); +struct cgpu_info *usb_free_cgpu(struct cgpu_info *cgpu); +void usb_uninit(struct cgpu_info *cgpu); +bool usb_init(struct cgpu_info *cgpu, struct libusb_device *dev, struct usb_find_devices *found); +void __usb_detect(struct device_drv *drv, struct cgpu_info *(*device_detect)(struct libusb_device *, struct usb_find_devices *), + bool single); +#define usb_detect(drv, cgpu) __usb_detect(drv, cgpu, false) +#define usb_detect_one(drv, cgpu) __usb_detect(drv, cgpu, true) +struct api_data *api_usb_stats(int *count); +void update_usb_stats(struct cgpu_info *cgpu); +void usb_reset(struct cgpu_info *cgpu); +int _usb_read(struct cgpu_info *cgpu, int intinfo, int epinfo, char *buf, size_t bufsiz, int *processed, int timeout, const char *end, enum usb_cmds cmd, bool readonce, bool cancellable); +int _usb_write(struct cgpu_info *cgpu, int intinfo, int epinfo, char *buf, size_t bufsiz, int *processed, int timeout, enum usb_cmds); +int _usb_transfer(struct cgpu_info *cgpu, uint8_t request_type, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, uint32_t *data, int siz, unsigned int timeout, enum usb_cmds cmd); +int _usb_transfer_read(struct cgpu_info *cgpu, uint8_t request_type, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, char *buf, int bufsiz, int *amount, unsigned int timeout, enum usb_cmds cmd); +int usb_ftdi_cts(struct cgpu_info *cgpu); +int _usb_ftdi_set_latency(struct cgpu_info *cgpu, int intinfo); +#define usb_ftdi_set_latency(_cgpu) _usb_ftdi_set_latency(_cgpu, DEFAULT_INTINFO) +void usb_buffer_clear(struct cgpu_info *cgpu); +uint32_t usb_buffer_size(struct cgpu_info *cgpu); +int _usb_interface(struct cgpu_info *cgpu, int intinfo); +#define usb_interface(_cgpu) _usb_interface(_cgpu, DEFAULT_INTINFO) +enum sub_ident usb_ident(struct cgpu_info *cgpu); +void usb_set_dev_start(struct cgpu_info *cgpu); +void usb_cleanup(); +void usb_initialise(); +void *usb_resource_thread(void *userdata); +void initialise_usblocks(void); + +#define usb_read(cgpu, buf, bufsiz, read, cmd) \ + _usb_read(cgpu, DEFAULT_INTINFO, DEFAULT_EP_IN, buf, bufsiz, read, DEVTIMEOUT, NULL, cmd, false, false) + +#define usb_read_cancellable(cgpu, buf, bufsiz, read, cmd) \ + _usb_read(cgpu, DEFAULT_INTINFO, DEFAULT_EP_IN, buf, bufsiz, read, DEVTIMEOUT, NULL, cmd, false, true) + +#define usb_read_ii(cgpu, intinfo, buf, bufsiz, read, cmd) \ + _usb_read(cgpu, intinfo, DEFAULT_EP_IN, buf, bufsiz, read, DEVTIMEOUT, NULL, cmd, false, false) + +#define usb_read_once(cgpu, buf, bufsiz, read, cmd) \ + _usb_read(cgpu, DEFAULT_INTINFO, DEFAULT_EP_IN, buf, bufsiz, read, DEVTIMEOUT, NULL, cmd, true, false) + +#define usb_read_ii_once(cgpu, intinfo, buf, bufsiz, read, cmd) \ + _usb_read(cgpu, intinfo, DEFAULT_EP_IN, buf, bufsiz, read, DEVTIMEOUT, NULL, cmd, true, false) + +#define usb_read_once_timeout(cgpu, buf, bufsiz, read, timeout, cmd) \ + _usb_read(cgpu, DEFAULT_INTINFO, DEFAULT_EP_IN, buf, bufsiz, read, timeout, NULL, cmd, true, false) + +#define usb_read_once_timeout_cancellable(cgpu, buf, bufsiz, read, timeout, cmd) \ + _usb_read(cgpu, DEFAULT_INTINFO, DEFAULT_EP_IN, buf, bufsiz, read, timeout, NULL, cmd, true, true) + +#define usb_read_ii_once_timeout(cgpu, intinfo, buf, bufsiz, read, timeout, cmd) \ + _usb_read(cgpu, intinfo, DEFAULT_EP_IN, buf, bufsiz, read, timeout, NULL, cmd, true, false) + +#define usb_read_nl(cgpu, buf, bufsiz, read, cmd) \ + _usb_read(cgpu, DEFAULT_INTINFO, DEFAULT_EP_IN, buf, bufsiz, read, DEVTIMEOUT, "\n", cmd, false, false) + +#define usb_read_nl_timeout(cgpu, buf, bufsiz, read, timeout, cmd) \ + _usb_read(cgpu, DEFAULT_INTINFO, DEFAULT_EP_IN, buf, bufsiz, read, timeout, "\n", cmd, false, false) + +#define usb_read_ok(cgpu, buf, bufsiz, read, cmd) \ + _usb_read(cgpu, DEFAULT_INTINFO, DEFAULT_EP_IN, buf, bufsiz, read, DEVTIMEOUT, "OK\n", cmd, false, false) + +#define usb_read_ok_timeout(cgpu, buf, bufsiz, read, timeout, cmd) \ + _usb_read(cgpu, DEFAULT_INTINFO, DEFAULT_EP_IN, buf, bufsiz, read, timeout, "OK\n", cmd, false, false) + +#define usb_read_ep(cgpu, ep, buf, bufsiz, read, cmd) \ + _usb_read(cgpu, DEFAULT_INTINFO, ep, buf, bufsiz, read, DEVTIMEOUT, NULL, cmd, false, false) + +#define usb_read_timeout(cgpu, buf, bufsiz, read, timeout, cmd) \ + _usb_read(cgpu, DEFAULT_INTINFO, DEFAULT_EP_IN, buf, bufsiz, read, timeout, NULL, cmd, false, false) + +#define usb_read_timeout_cancellable(cgpu, buf, bufsiz, read, timeout, cmd) \ + _usb_read(cgpu, DEFAULT_INTINFO, DEFAULT_EP_IN, buf, bufsiz, read, timeout, NULL, cmd, false, true) + +#define usb_read_ii_timeout(cgpu, intinfo, buf, bufsiz, read, timeout, cmd) \ + _usb_read(cgpu, intinfo, DEFAULT_EP_IN, buf, bufsiz, read, timeout, NULL, cmd, false, false) + +#define usb_read_ii_timeout_cancellable(cgpu, intinfo, buf, bufsiz, read, timeout, cmd) \ + _usb_read(cgpu, intinfo, DEFAULT_EP_IN, buf, bufsiz, read, timeout, NULL, cmd, false, true) + +#define usb_read_ep_timeout(cgpu, ep, buf, bufsiz, read, timeout, cmd) \ + _usb_read(cgpu, DEFAULT_INTINFO, ep, buf, bufsiz, read, timeout, NULL, cmd, false, false) + +#define usb_write(cgpu, buf, bufsiz, wrote, cmd) \ + _usb_write(cgpu, DEFAULT_INTINFO, DEFAULT_EP_OUT, buf, bufsiz, wrote, DEVTIMEOUT, cmd) + +#define usb_write_ii(cgpu, intinfo, buf, bufsiz, wrote, cmd) \ + _usb_write(cgpu, intinfo, DEFAULT_EP_OUT, buf, bufsiz, wrote, DEVTIMEOUT, cmd) + +#define usb_write_ep(cgpu, ep, buf, bufsiz, wrote, cmd) \ + _usb_write(cgpu, DEFAULT_INTINFO, ep, buf, bufsiz, wrote, DEVTIMEOUT, cmd) + +#define usb_write_timeout(cgpu, buf, bufsiz, wrote, timeout, cmd) \ + _usb_write(cgpu, DEFAULT_INTINFO, DEFAULT_EP_OUT, buf, bufsiz, wrote, timeout, cmd) + +#define usb_write_ep_timeout(cgpu, ep, buf, bufsiz, wrote, timeout, cmd) \ + _usb_write(cgpu, DEFAULT_INTINFO, ep, buf, bufsiz, wrote, timeout, cmd) + +#define usb_transfer(cgpu, typ, req, val, idx, cmd) \ + _usb_transfer(cgpu, typ, req, val, idx, NULL, 0, DEVTIMEOUT, cmd) + +#define usb_transfer_data(cgpu, typ, req, val, idx, data, len, cmd) \ + _usb_transfer(cgpu, typ, req, val, idx, data, len, DEVTIMEOUT, cmd) + +#define usb_transfer_read(cgpu, typ, req, val, idx, buf, bufsiz, read, cmd) \ + _usb_transfer_read(cgpu, typ, req, val, idx, buf, bufsiz, read, DEVTIMEOUT, cmd) + +#endif diff --git a/uthash.h b/uthash.h new file mode 100644 index 0000000..72acf11 --- /dev/null +++ b/uthash.h @@ -0,0 +1,948 @@ +/* +Copyright (c) 2003-2013, Troy D. Hanson http://troydhanson.github.com/uthash/ +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef UTHASH_H +#define UTHASH_H + +#include /* memcmp,strlen */ +#include /* ptrdiff_t */ +#include /* exit() */ + +/* These macros use decltype or the earlier __typeof GNU extension. + As decltype is only available in newer compilers (VS2010 or gcc 4.3+ + when compiling c++ source) this code uses whatever method is needed + or, for VS2008 where neither is available, uses casting workarounds. */ +#ifdef _MSC_VER /* MS compiler */ +#if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */ +#define DECLTYPE(x) (decltype(x)) +#else /* VS2008 or older (or VS2010 in C mode) */ +#define NO_DECLTYPE +#define DECLTYPE(x) +#endif +#else /* GNU, Sun and other compilers */ +#define DECLTYPE(x) (__typeof(x)) +#endif + +#ifdef NO_DECLTYPE +#define DECLTYPE_ASSIGN(dst,src) \ +do { \ + char **_da_dst = (char**)(&(dst)); \ + *_da_dst = (char*)(src); \ +} while(0) +#else +#define DECLTYPE_ASSIGN(dst,src) \ +do { \ + (dst) = DECLTYPE(dst)(src); \ +} while(0) +#endif + +/* a number of the hash function use uint32_t which isn't defined on win32 */ +#ifdef _MSC_VER +typedef unsigned int uint32_t; +typedef unsigned char uint8_t; +#else +#include /* uint32_t */ +#endif + +#define UTHASH_VERSION 1.9.8 + +#ifndef uthash_fatal +#define uthash_fatal(msg) exit(-1) /* fatal error (out of memory,etc) */ +#endif +#ifndef uthash_malloc +#define uthash_malloc(sz) malloc(sz) /* malloc fcn */ +#endif +#ifndef uthash_free +#define uthash_free(ptr,sz) free(ptr) /* free fcn */ +#endif + +#ifndef uthash_noexpand_fyi +#define uthash_noexpand_fyi(tbl) /* can be defined to log noexpand */ +#endif +#ifndef uthash_expand_fyi +#define uthash_expand_fyi(tbl) /* can be defined to log expands */ +#endif + +/* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS 32 /* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS_LOG2 5 /* lg2 of initial number of buckets */ +#define HASH_BKT_CAPACITY_THRESH 10 /* expand when bucket count reaches */ + +/* calculate the element whose hash handle address is hhe */ +#define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho))) + +#define HASH_FIND(hh,head,keyptr,keylen,out) \ +do { \ + unsigned _hf_bkt,_hf_hashv; \ + out=NULL; \ + if (head) { \ + HASH_FCN(keyptr,keylen, (head)->hh.tbl->num_buckets, _hf_hashv, _hf_bkt); \ + if (HASH_BLOOM_TEST((head)->hh.tbl, _hf_hashv)) { \ + HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], \ + keyptr,keylen,out); \ + } \ + } \ +} while (0) + +#ifdef HASH_BLOOM +#define HASH_BLOOM_BITLEN (1ULL << HASH_BLOOM) +#define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8) + ((HASH_BLOOM_BITLEN%8) ? 1:0) +#define HASH_BLOOM_MAKE(tbl) \ +do { \ + (tbl)->bloom_nbits = HASH_BLOOM; \ + (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN); \ + if (!((tbl)->bloom_bv)) { uthash_fatal( "out of memory"); } \ + memset((tbl)->bloom_bv, 0, HASH_BLOOM_BYTELEN); \ + (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE; \ +} while (0) + +#define HASH_BLOOM_FREE(tbl) \ +do { \ + uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ +} while (0) + +#define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8] |= (1U << ((idx)%8))) +#define HASH_BLOOM_BITTEST(bv,idx) (bv[(idx)/8] & (1U << ((idx)%8))) + +#define HASH_BLOOM_ADD(tbl,hashv) \ + HASH_BLOOM_BITSET((tbl)->bloom_bv, (hashv & (uint32_t)((1ULL << (tbl)->bloom_nbits) - 1))) + +#define HASH_BLOOM_TEST(tbl,hashv) \ + HASH_BLOOM_BITTEST((tbl)->bloom_bv, (hashv & (uint32_t)((1ULL << (tbl)->bloom_nbits) - 1))) + +#else +#define HASH_BLOOM_MAKE(tbl) +#define HASH_BLOOM_FREE(tbl) +#define HASH_BLOOM_ADD(tbl,hashv) +#define HASH_BLOOM_TEST(tbl,hashv) (1) +#define HASH_BLOOM_BYTELEN 0 +#endif + +#define HASH_MAKE_TABLE(hh,head) \ +do { \ + (head)->hh.tbl = (UT_hash_table*)uthash_malloc( \ + sizeof(UT_hash_table)); \ + if (!((head)->hh.tbl)) { uthash_fatal( "out of memory"); } \ + memset((head)->hh.tbl, 0, sizeof(UT_hash_table)); \ + (head)->hh.tbl->tail = &((head)->hh); \ + (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS; \ + (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2; \ + (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head); \ + (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc( \ + HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ + if (! (head)->hh.tbl->buckets) { uthash_fatal( "out of memory"); } \ + memset((head)->hh.tbl->buckets, 0, \ + HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ + HASH_BLOOM_MAKE((head)->hh.tbl); \ + (head)->hh.tbl->signature = HASH_SIGNATURE; \ +} while(0) + +#define HASH_ADD(hh,head,fieldname,keylen_in,add) \ + HASH_ADD_KEYPTR(hh,head,&((add)->fieldname),keylen_in,add) + +#define HASH_REPLACE(hh,head,fieldname,keylen_in,add,replaced) \ +do { \ + replaced=NULL; \ + HASH_FIND(hh,head,&((add)->fieldname),keylen_in,replaced); \ + if (replaced!=NULL) { \ + HASH_DELETE(hh,head,replaced); \ + }; \ + HASH_ADD(hh,head,fieldname,keylen_in,add); \ +} while(0) + +#define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add) \ +do { \ + unsigned _ha_bkt; \ + (add)->hh.next = NULL; \ + (add)->hh.key = (char*)(keyptr); \ + (add)->hh.keylen = (unsigned)(keylen_in); \ + if (!(head)) { \ + head = (add); \ + (head)->hh.prev = NULL; \ + HASH_MAKE_TABLE(hh,head); \ + } else { \ + (head)->hh.tbl->tail->next = (add); \ + (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail); \ + (head)->hh.tbl->tail = &((add)->hh); \ + } \ + (head)->hh.tbl->num_items++; \ + (add)->hh.tbl = (head)->hh.tbl; \ + HASH_FCN(keyptr,keylen_in, (head)->hh.tbl->num_buckets, \ + (add)->hh.hashv, _ha_bkt); \ + HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt],&(add)->hh); \ + HASH_BLOOM_ADD((head)->hh.tbl,(add)->hh.hashv); \ + HASH_EMIT_KEY(hh,head,keyptr,keylen_in); \ + HASH_FSCK(hh,head); \ +} while(0) + +#define HASH_TO_BKT( hashv, num_bkts, bkt ) \ +do { \ + bkt = ((hashv) & ((num_bkts) - 1)); \ +} while(0) + +/* delete "delptr" from the hash table. + * "the usual" patch-up process for the app-order doubly-linked-list. + * The use of _hd_hh_del below deserves special explanation. + * These used to be expressed using (delptr) but that led to a bug + * if someone used the same symbol for the head and deletee, like + * HASH_DELETE(hh,users,users); + * We want that to work, but by changing the head (users) below + * we were forfeiting our ability to further refer to the deletee (users) + * in the patch-up process. Solution: use scratch space to + * copy the deletee pointer, then the latter references are via that + * scratch pointer rather than through the repointed (users) symbol. + */ +#define HASH_DELETE(hh,head,delptr) \ +do { \ + unsigned _hd_bkt; \ + struct UT_hash_handle *_hd_hh_del; \ + if ( ((delptr)->hh.prev == NULL) && ((delptr)->hh.next == NULL) ) { \ + uthash_free((head)->hh.tbl->buckets, \ + (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket) ); \ + HASH_BLOOM_FREE((head)->hh.tbl); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + head = NULL; \ + } else { \ + _hd_hh_del = &((delptr)->hh); \ + if ((delptr) == ELMT_FROM_HH((head)->hh.tbl,(head)->hh.tbl->tail)) { \ + (head)->hh.tbl->tail = \ + (UT_hash_handle*)((ptrdiff_t)((delptr)->hh.prev) + \ + (head)->hh.tbl->hho); \ + } \ + if ((delptr)->hh.prev) { \ + ((UT_hash_handle*)((ptrdiff_t)((delptr)->hh.prev) + \ + (head)->hh.tbl->hho))->next = (delptr)->hh.next; \ + } else { \ + DECLTYPE_ASSIGN(head,(delptr)->hh.next); \ + } \ + if (_hd_hh_del->next) { \ + ((UT_hash_handle*)((ptrdiff_t)_hd_hh_del->next + \ + (head)->hh.tbl->hho))->prev = \ + _hd_hh_del->prev; \ + } \ + HASH_TO_BKT( _hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ + HASH_DEL_IN_BKT(hh,(head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del); \ + (head)->hh.tbl->num_items--; \ + } \ + HASH_FSCK(hh,head); \ +} while (0) + + +/* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */ +#define HASH_FIND_STR(head,findstr,out) \ + HASH_FIND(hh,head,findstr,strlen(findstr),out) +#define HASH_ADD_STR(head,strfield,add) \ + HASH_ADD(hh,head,strfield,strlen(add->strfield),add) +#define HASH_REPLACE_STR(head,strfield,add,replaced) \ + HASH_REPLACE(hh,head,strfield,strlen(add->strfield),add,replaced) +#define HASH_FIND_INT(head,findint,out) \ + HASH_FIND(hh,head,findint,sizeof(int),out) +#define HASH_ADD_INT(head,intfield,add) \ + HASH_ADD(hh,head,intfield,sizeof(int),add) +#define HASH_REPLACE_INT(head,intfield,add,replaced) \ + HASH_REPLACE(hh,head,intfield,sizeof(int),add,replaced) +#define HASH_FIND_PTR(head,findptr,out) \ + HASH_FIND(hh,head,findptr,sizeof(void *),out) +#define HASH_ADD_PTR(head,ptrfield,add) \ + HASH_ADD(hh,head,ptrfield,sizeof(void *),add) +#define HASH_REPLACE_PTR(head,ptrfield,add) \ + HASH_REPLACE(hh,head,ptrfield,sizeof(void *),add,replaced) +#define HASH_DEL(head,delptr) \ + HASH_DELETE(hh,head,delptr) + +/* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined. + * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined. + */ +#ifdef HASH_DEBUG +#define HASH_OOPS(...) do { fprintf(stderr,__VA_ARGS__); exit(-1); } while (0) +#define HASH_FSCK(hh,head) \ +do { \ + unsigned _bkt_i; \ + unsigned _count, _bkt_count; \ + char *_prev; \ + struct UT_hash_handle *_thh; \ + if (head) { \ + _count = 0; \ + for( _bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; _bkt_i++) { \ + _bkt_count = 0; \ + _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head; \ + _prev = NULL; \ + while (_thh) { \ + if (_prev != (char*)(_thh->hh_prev)) { \ + HASH_OOPS("invalid hh_prev %p, actual %p\n", \ + _thh->hh_prev, _prev ); \ + } \ + _bkt_count++; \ + _prev = (char*)(_thh); \ + _thh = _thh->hh_next; \ + } \ + _count += _bkt_count; \ + if ((head)->hh.tbl->buckets[_bkt_i].count != _bkt_count) { \ + HASH_OOPS("invalid bucket count %d, actual %d\n", \ + (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count); \ + } \ + } \ + if (_count != (head)->hh.tbl->num_items) { \ + HASH_OOPS("invalid hh item count %d, actual %d\n", \ + (head)->hh.tbl->num_items, _count ); \ + } \ + /* traverse hh in app order; check next/prev integrity, count */ \ + _count = 0; \ + _prev = NULL; \ + _thh = &(head)->hh; \ + while (_thh) { \ + _count++; \ + if (_prev !=(char*)(_thh->prev)) { \ + HASH_OOPS("invalid prev %p, actual %p\n", \ + _thh->prev, _prev ); \ + } \ + _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh); \ + _thh = ( _thh->next ? (UT_hash_handle*)((char*)(_thh->next) + \ + (head)->hh.tbl->hho) : NULL ); \ + } \ + if (_count != (head)->hh.tbl->num_items) { \ + HASH_OOPS("invalid app item count %d, actual %d\n", \ + (head)->hh.tbl->num_items, _count ); \ + } \ + } \ +} while (0) +#else +#define HASH_FSCK(hh,head) +#endif + +/* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to + * the descriptor to which this macro is defined for tuning the hash function. + * The app can #include to get the prototype for write(2). */ +#ifdef HASH_EMIT_KEYS +#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) \ +do { \ + unsigned _klen = fieldlen; \ + write(HASH_EMIT_KEYS, &_klen, sizeof(_klen)); \ + write(HASH_EMIT_KEYS, keyptr, fieldlen); \ +} while (0) +#else +#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) +#endif + +/* default to Jenkin's hash unless overridden e.g. DHASH_FUNCTION=HASH_SAX */ +#ifdef HASH_FUNCTION +#define HASH_FCN HASH_FUNCTION +#else +#define HASH_FCN HASH_JEN +#endif + +/* The Bernstein hash function, used in Perl prior to v5.6 */ +#define HASH_BER(key,keylen,num_bkts,hashv,bkt) \ +do { \ + unsigned _hb_keylen=keylen; \ + char *_hb_key=(char*)(key); \ + (hashv) = 0; \ + while (_hb_keylen--) { (hashv) = ((hashv) * 33) + *_hb_key++; } \ + bkt = (hashv) & (num_bkts-1); \ +} while (0) + + +/* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at + * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx */ +#define HASH_SAX(key,keylen,num_bkts,hashv,bkt) \ +do { \ + unsigned _sx_i; \ + char *_hs_key=(char*)(key); \ + hashv = 0; \ + for(_sx_i=0; _sx_i < keylen; _sx_i++) \ + hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i]; \ + bkt = hashv & (num_bkts-1); \ +} while (0) + +#define HASH_FNV(key,keylen,num_bkts,hashv,bkt) \ +do { \ + unsigned _fn_i; \ + char *_hf_key=(char*)(key); \ + hashv = 2166136261UL; \ + for(_fn_i=0; _fn_i < keylen; _fn_i++) \ + hashv = (hashv * 16777619) ^ _hf_key[_fn_i]; \ + bkt = hashv & (num_bkts-1); \ +} while(0) + +#define HASH_OAT(key,keylen,num_bkts,hashv,bkt) \ +do { \ + unsigned _ho_i; \ + char *_ho_key=(char*)(key); \ + hashv = 0; \ + for(_ho_i=0; _ho_i < keylen; _ho_i++) { \ + hashv += _ho_key[_ho_i]; \ + hashv += (hashv << 10); \ + hashv ^= (hashv >> 6); \ + } \ + hashv += (hashv << 3); \ + hashv ^= (hashv >> 11); \ + hashv += (hashv << 15); \ + bkt = hashv & (num_bkts-1); \ +} while(0) + +#define HASH_JEN_MIX(a,b,c) \ +do { \ + a -= b; a -= c; a ^= ( c >> 13 ); \ + b -= c; b -= a; b ^= ( a << 8 ); \ + c -= a; c -= b; c ^= ( b >> 13 ); \ + a -= b; a -= c; a ^= ( c >> 12 ); \ + b -= c; b -= a; b ^= ( a << 16 ); \ + c -= a; c -= b; c ^= ( b >> 5 ); \ + a -= b; a -= c; a ^= ( c >> 3 ); \ + b -= c; b -= a; b ^= ( a << 10 ); \ + c -= a; c -= b; c ^= ( b >> 15 ); \ +} while (0) + +#define HASH_JEN(key,keylen,num_bkts,hashv,bkt) \ +do { \ + unsigned _hj_i,_hj_j,_hj_k; \ + unsigned char *_hj_key=(unsigned char*)(key); \ + hashv = 0xfeedbeef; \ + _hj_i = _hj_j = 0x9e3779b9; \ + _hj_k = (unsigned)(keylen); \ + while (_hj_k >= 12) { \ + _hj_i += (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 ) \ + + ( (unsigned)_hj_key[2] << 16 ) \ + + ( (unsigned)_hj_key[3] << 24 ) ); \ + _hj_j += (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 ) \ + + ( (unsigned)_hj_key[6] << 16 ) \ + + ( (unsigned)_hj_key[7] << 24 ) ); \ + hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 ) \ + + ( (unsigned)_hj_key[10] << 16 ) \ + + ( (unsigned)_hj_key[11] << 24 ) ); \ + \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ + \ + _hj_key += 12; \ + _hj_k -= 12; \ + } \ + hashv += keylen; \ + switch ( _hj_k ) { \ + case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); \ + case 10: hashv += ( (unsigned)_hj_key[9] << 16 ); \ + case 9: hashv += ( (unsigned)_hj_key[8] << 8 ); \ + case 8: _hj_j += ( (unsigned)_hj_key[7] << 24 ); \ + case 7: _hj_j += ( (unsigned)_hj_key[6] << 16 ); \ + case 6: _hj_j += ( (unsigned)_hj_key[5] << 8 ); \ + case 5: _hj_j += _hj_key[4]; \ + case 4: _hj_i += ( (unsigned)_hj_key[3] << 24 ); \ + case 3: _hj_i += ( (unsigned)_hj_key[2] << 16 ); \ + case 2: _hj_i += ( (unsigned)_hj_key[1] << 8 ); \ + case 1: _hj_i += _hj_key[0]; \ + } \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ + bkt = hashv & (num_bkts-1); \ +} while(0) + +/* The Paul Hsieh hash function */ +#undef get16bits +#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \ + || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__) +#define get16bits(d) (*((const uint16_t *) (d))) +#endif + +#if !defined (get16bits) +#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8) \ + +(uint32_t)(((const uint8_t *)(d))[0]) ) +#endif +#define HASH_SFH(key,keylen,num_bkts,hashv,bkt) \ +do { \ + unsigned char *_sfh_key=(unsigned char*)(key); \ + uint32_t _sfh_tmp, _sfh_len = keylen; \ + \ + int _sfh_rem = _sfh_len & 3; \ + _sfh_len >>= 2; \ + hashv = 0xcafebabe; \ + \ + /* Main loop */ \ + for (;_sfh_len > 0; _sfh_len--) { \ + hashv += get16bits (_sfh_key); \ + _sfh_tmp = (uint32_t)(get16bits (_sfh_key+2)) << 11 ^ hashv; \ + hashv = (hashv << 16) ^ _sfh_tmp; \ + _sfh_key += 2*sizeof (uint16_t); \ + hashv += hashv >> 11; \ + } \ + \ + /* Handle end cases */ \ + switch (_sfh_rem) { \ + case 3: hashv += get16bits (_sfh_key); \ + hashv ^= hashv << 16; \ + hashv ^= (uint32_t)(_sfh_key[sizeof (uint16_t)] << 18); \ + hashv += hashv >> 11; \ + break; \ + case 2: hashv += get16bits (_sfh_key); \ + hashv ^= hashv << 11; \ + hashv += hashv >> 17; \ + break; \ + case 1: hashv += *_sfh_key; \ + hashv ^= hashv << 10; \ + hashv += hashv >> 1; \ + } \ + \ + /* Force "avalanching" of final 127 bits */ \ + hashv ^= hashv << 3; \ + hashv += hashv >> 5; \ + hashv ^= hashv << 4; \ + hashv += hashv >> 17; \ + hashv ^= hashv << 25; \ + hashv += hashv >> 6; \ + bkt = hashv & (num_bkts-1); \ +} while(0) + +#ifdef HASH_USING_NO_STRICT_ALIASING +/* The MurmurHash exploits some CPU's (x86,x86_64) tolerance for unaligned reads. + * For other types of CPU's (e.g. Sparc) an unaligned read causes a bus error. + * MurmurHash uses the faster approach only on CPU's where we know it's safe. + * + * Note the preprocessor built-in defines can be emitted using: + * + * gcc -m64 -dM -E - < /dev/null (on gcc) + * cc -## a.c (where a.c is a simple test file) (Sun Studio) + */ +#if (defined(__i386__) || defined(__x86_64__) || defined(_M_IX86)) +#define MUR_GETBLOCK(p,i) p[i] +#else /* non intel */ +#define MUR_PLUS0_ALIGNED(p) (((unsigned long)p & 0x3) == 0) +#define MUR_PLUS1_ALIGNED(p) (((unsigned long)p & 0x3) == 1) +#define MUR_PLUS2_ALIGNED(p) (((unsigned long)p & 0x3) == 2) +#define MUR_PLUS3_ALIGNED(p) (((unsigned long)p & 0x3) == 3) +#define WP(p) ((uint32_t*)((unsigned long)(p) & ~3UL)) +#if (defined(__BIG_ENDIAN__) || defined(SPARC) || defined(__ppc__) || defined(__ppc64__)) +#define MUR_THREE_ONE(p) ((((*WP(p))&0x00ffffff) << 8) | (((*(WP(p)+1))&0xff000000) >> 24)) +#define MUR_TWO_TWO(p) ((((*WP(p))&0x0000ffff) <<16) | (((*(WP(p)+1))&0xffff0000) >> 16)) +#define MUR_ONE_THREE(p) ((((*WP(p))&0x000000ff) <<24) | (((*(WP(p)+1))&0xffffff00) >> 8)) +#else /* assume little endian non-intel */ +#define MUR_THREE_ONE(p) ((((*WP(p))&0xffffff00) >> 8) | (((*(WP(p)+1))&0x000000ff) << 24)) +#define MUR_TWO_TWO(p) ((((*WP(p))&0xffff0000) >>16) | (((*(WP(p)+1))&0x0000ffff) << 16)) +#define MUR_ONE_THREE(p) ((((*WP(p))&0xff000000) >>24) | (((*(WP(p)+1))&0x00ffffff) << 8)) +#endif +#define MUR_GETBLOCK(p,i) (MUR_PLUS0_ALIGNED(p) ? ((p)[i]) : \ + (MUR_PLUS1_ALIGNED(p) ? MUR_THREE_ONE(p) : \ + (MUR_PLUS2_ALIGNED(p) ? MUR_TWO_TWO(p) : \ + MUR_ONE_THREE(p)))) +#endif +#define MUR_ROTL32(x,r) (((x) << (r)) | ((x) >> (32 - (r)))) +#define MUR_FMIX(_h) \ +do { \ + _h ^= _h >> 16; \ + _h *= 0x85ebca6b; \ + _h ^= _h >> 13; \ + _h *= 0xc2b2ae35l; \ + _h ^= _h >> 16; \ +} while(0) + +#define HASH_MUR(key,keylen,num_bkts,hashv,bkt) \ +do { \ + const uint8_t *_mur_data = (const uint8_t*)(key); \ + const int _mur_nblocks = (keylen) / 4; \ + uint32_t _mur_h1 = 0xf88D5353; \ + uint32_t _mur_c1 = 0xcc9e2d51; \ + uint32_t _mur_c2 = 0x1b873593; \ + uint32_t _mur_k1 = 0; \ + const uint8_t *_mur_tail; \ + const uint32_t *_mur_blocks = (const uint32_t*)(_mur_data+_mur_nblocks*4); \ + int _mur_i; \ + for(_mur_i = -_mur_nblocks; _mur_i; _mur_i++) { \ + _mur_k1 = MUR_GETBLOCK(_mur_blocks,_mur_i); \ + _mur_k1 *= _mur_c1; \ + _mur_k1 = MUR_ROTL32(_mur_k1,15); \ + _mur_k1 *= _mur_c2; \ + \ + _mur_h1 ^= _mur_k1; \ + _mur_h1 = MUR_ROTL32(_mur_h1,13); \ + _mur_h1 = _mur_h1*5+0xe6546b64; \ + } \ + _mur_tail = (const uint8_t*)(_mur_data + _mur_nblocks*4); \ + _mur_k1=0; \ + switch((keylen) & 3) { \ + case 3: _mur_k1 ^= _mur_tail[2] << 16; \ + case 2: _mur_k1 ^= _mur_tail[1] << 8; \ + case 1: _mur_k1 ^= _mur_tail[0]; \ + _mur_k1 *= _mur_c1; \ + _mur_k1 = MUR_ROTL32(_mur_k1,15); \ + _mur_k1 *= _mur_c2; \ + _mur_h1 ^= _mur_k1; \ + } \ + _mur_h1 ^= (keylen); \ + MUR_FMIX(_mur_h1); \ + hashv = _mur_h1; \ + bkt = hashv & (num_bkts-1); \ +} while(0) +#endif /* HASH_USING_NO_STRICT_ALIASING */ + +/* key comparison function; return 0 if keys equal */ +#define HASH_KEYCMP(a,b,len) memcmp(a,b,len) + +/* iterate over items in a known bucket to find desired item */ +#define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,out) \ +do { \ + if (head.hh_head) DECLTYPE_ASSIGN(out,ELMT_FROM_HH(tbl,head.hh_head)); \ + else out=NULL; \ + while (out) { \ + if ((out)->hh.keylen == keylen_in) { \ + if ((HASH_KEYCMP((out)->hh.key,keyptr,keylen_in)) == 0) break; \ + } \ + if ((out)->hh.hh_next) DECLTYPE_ASSIGN(out,ELMT_FROM_HH(tbl,(out)->hh.hh_next)); \ + else out = NULL; \ + } \ +} while(0) + +/* add an item to a bucket */ +#define HASH_ADD_TO_BKT(head,addhh) \ +do { \ + head.count++; \ + (addhh)->hh_next = head.hh_head; \ + (addhh)->hh_prev = NULL; \ + if (head.hh_head) { (head).hh_head->hh_prev = (addhh); } \ + (head).hh_head=addhh; \ + if (head.count >= ((head.expand_mult+1) * HASH_BKT_CAPACITY_THRESH) \ + && (addhh)->tbl->noexpand != 1) { \ + HASH_EXPAND_BUCKETS((addhh)->tbl); \ + } \ +} while(0) + +/* remove an item from a given bucket */ +#define HASH_DEL_IN_BKT(hh,head,hh_del) \ + (head).count--; \ + if ((head).hh_head == hh_del) { \ + (head).hh_head = hh_del->hh_next; \ + } \ + if (hh_del->hh_prev) { \ + hh_del->hh_prev->hh_next = hh_del->hh_next; \ + } \ + if (hh_del->hh_next) { \ + hh_del->hh_next->hh_prev = hh_del->hh_prev; \ + } + +/* Bucket expansion has the effect of doubling the number of buckets + * and redistributing the items into the new buckets. Ideally the + * items will distribute more or less evenly into the new buckets + * (the extent to which this is true is a measure of the quality of + * the hash function as it applies to the key domain). + * + * With the items distributed into more buckets, the chain length + * (item count) in each bucket is reduced. Thus by expanding buckets + * the hash keeps a bound on the chain length. This bounded chain + * length is the essence of how a hash provides constant time lookup. + * + * The calculation of tbl->ideal_chain_maxlen below deserves some + * explanation. First, keep in mind that we're calculating the ideal + * maximum chain length based on the *new* (doubled) bucket count. + * In fractions this is just n/b (n=number of items,b=new num buckets). + * Since the ideal chain length is an integer, we want to calculate + * ceil(n/b). We don't depend on floating point arithmetic in this + * hash, so to calculate ceil(n/b) with integers we could write + * + * ceil(n/b) = (n/b) + ((n%b)?1:0) + * + * and in fact a previous version of this hash did just that. + * But now we have improved things a bit by recognizing that b is + * always a power of two. We keep its base 2 log handy (call it lb), + * so now we can write this with a bit shift and logical AND: + * + * ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0) + * + */ +#define HASH_EXPAND_BUCKETS(tbl) \ +do { \ + unsigned _he_bkt; \ + unsigned _he_bkt_i; \ + struct UT_hash_handle *_he_thh, *_he_hh_nxt; \ + UT_hash_bucket *_he_new_buckets, *_he_newbkt; \ + _he_new_buckets = (UT_hash_bucket*)uthash_malloc( \ + 2 * tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ + if (!_he_new_buckets) { uthash_fatal( "out of memory"); } \ + memset(_he_new_buckets, 0, \ + 2 * tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ + tbl->ideal_chain_maxlen = \ + (tbl->num_items >> (tbl->log2_num_buckets+1)) + \ + ((tbl->num_items & ((tbl->num_buckets*2)-1)) ? 1 : 0); \ + tbl->nonideal_items = 0; \ + for(_he_bkt_i = 0; _he_bkt_i < tbl->num_buckets; _he_bkt_i++) \ + { \ + _he_thh = tbl->buckets[ _he_bkt_i ].hh_head; \ + while (_he_thh) { \ + _he_hh_nxt = _he_thh->hh_next; \ + HASH_TO_BKT( _he_thh->hashv, tbl->num_buckets*2, _he_bkt); \ + _he_newbkt = &(_he_new_buckets[ _he_bkt ]); \ + if (++(_he_newbkt->count) > tbl->ideal_chain_maxlen) { \ + tbl->nonideal_items++; \ + _he_newbkt->expand_mult = _he_newbkt->count / \ + tbl->ideal_chain_maxlen; \ + } \ + _he_thh->hh_prev = NULL; \ + _he_thh->hh_next = _he_newbkt->hh_head; \ + if (_he_newbkt->hh_head) _he_newbkt->hh_head->hh_prev = \ + _he_thh; \ + _he_newbkt->hh_head = _he_thh; \ + _he_thh = _he_hh_nxt; \ + } \ + } \ + uthash_free( tbl->buckets, tbl->num_buckets*sizeof(struct UT_hash_bucket) ); \ + tbl->num_buckets *= 2; \ + tbl->log2_num_buckets++; \ + tbl->buckets = _he_new_buckets; \ + tbl->ineff_expands = (tbl->nonideal_items > (tbl->num_items >> 1)) ? \ + (tbl->ineff_expands+1) : 0; \ + if (tbl->ineff_expands > 1) { \ + tbl->noexpand=1; \ + uthash_noexpand_fyi(tbl); \ + } \ + uthash_expand_fyi(tbl); \ +} while(0) + + +/* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */ +/* Note that HASH_SORT assumes the hash handle name to be hh. + * HASH_SRT was added to allow the hash handle name to be passed in. */ +#define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn) +#define HASH_SRT(hh,head,cmpfcn) \ +do { \ + unsigned _hs_i; \ + unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize; \ + struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail; \ + if (head) { \ + _hs_insize = 1; \ + _hs_looping = 1; \ + _hs_list = &((head)->hh); \ + while (_hs_looping) { \ + _hs_p = _hs_list; \ + _hs_list = NULL; \ + _hs_tail = NULL; \ + _hs_nmerges = 0; \ + while (_hs_p) { \ + _hs_nmerges++; \ + _hs_q = _hs_p; \ + _hs_psize = 0; \ + for ( _hs_i = 0; _hs_i < _hs_insize; _hs_i++ ) { \ + _hs_psize++; \ + _hs_q = (UT_hash_handle*)((_hs_q->next) ? \ + ((void*)((char*)(_hs_q->next) + \ + (head)->hh.tbl->hho)) : NULL); \ + if (! (_hs_q) ) break; \ + } \ + _hs_qsize = _hs_insize; \ + while ((_hs_psize > 0) || ((_hs_qsize > 0) && _hs_q )) { \ + if (_hs_psize == 0) { \ + _hs_e = _hs_q; \ + _hs_q = (UT_hash_handle*)((_hs_q->next) ? \ + ((void*)((char*)(_hs_q->next) + \ + (head)->hh.tbl->hho)) : NULL); \ + _hs_qsize--; \ + } else if ( (_hs_qsize == 0) || !(_hs_q) ) { \ + _hs_e = _hs_p; \ + if (_hs_p){ \ + _hs_p = (UT_hash_handle*)((_hs_p->next) ? \ + ((void*)((char*)(_hs_p->next) + \ + (head)->hh.tbl->hho)) : NULL); \ + } \ + _hs_psize--; \ + } else if (( \ + cmpfcn(DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl,_hs_p)), \ + DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl,_hs_q))) \ + ) <= 0) { \ + _hs_e = _hs_p; \ + if (_hs_p){ \ + _hs_p = (UT_hash_handle*)((_hs_p->next) ? \ + ((void*)((char*)(_hs_p->next) + \ + (head)->hh.tbl->hho)) : NULL); \ + } \ + _hs_psize--; \ + } else { \ + _hs_e = _hs_q; \ + _hs_q = (UT_hash_handle*)((_hs_q->next) ? \ + ((void*)((char*)(_hs_q->next) + \ + (head)->hh.tbl->hho)) : NULL); \ + _hs_qsize--; \ + } \ + if ( _hs_tail ) { \ + _hs_tail->next = ((_hs_e) ? \ + ELMT_FROM_HH((head)->hh.tbl,_hs_e) : NULL); \ + } else { \ + _hs_list = _hs_e; \ + } \ + if (_hs_e) { \ + _hs_e->prev = ((_hs_tail) ? \ + ELMT_FROM_HH((head)->hh.tbl,_hs_tail) : NULL); \ + } \ + _hs_tail = _hs_e; \ + } \ + _hs_p = _hs_q; \ + } \ + if (_hs_tail){ \ + _hs_tail->next = NULL; \ + } \ + if ( _hs_nmerges <= 1 ) { \ + _hs_looping=0; \ + (head)->hh.tbl->tail = _hs_tail; \ + DECLTYPE_ASSIGN(head,ELMT_FROM_HH((head)->hh.tbl, _hs_list)); \ + } \ + _hs_insize *= 2; \ + } \ + HASH_FSCK(hh,head); \ + } \ +} while (0) + +/* This function selects items from one hash into another hash. + * The end result is that the selected items have dual presence + * in both hashes. There is no copy of the items made; rather + * they are added into the new hash through a secondary hash + * hash handle that must be present in the structure. */ +#define HASH_SELECT(hh_dst, dst, hh_src, src, cond) \ +do { \ + unsigned _src_bkt, _dst_bkt; \ + void *_last_elt=NULL, *_elt; \ + UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL; \ + ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst)); \ + if (src) { \ + for(_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) { \ + for(_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head; \ + _src_hh; \ + _src_hh = _src_hh->hh_next) { \ + _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh); \ + if (cond(_elt)) { \ + _dst_hh = (UT_hash_handle*)(((char*)_elt) + _dst_hho); \ + _dst_hh->key = _src_hh->key; \ + _dst_hh->keylen = _src_hh->keylen; \ + _dst_hh->hashv = _src_hh->hashv; \ + _dst_hh->prev = _last_elt; \ + _dst_hh->next = NULL; \ + if (_last_elt_hh) { _last_elt_hh->next = _elt; } \ + if (!dst) { \ + DECLTYPE_ASSIGN(dst,_elt); \ + HASH_MAKE_TABLE(hh_dst,dst); \ + } else { \ + _dst_hh->tbl = (dst)->hh_dst.tbl; \ + } \ + HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt); \ + HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt],_dst_hh); \ + (dst)->hh_dst.tbl->num_items++; \ + _last_elt = _elt; \ + _last_elt_hh = _dst_hh; \ + } \ + } \ + } \ + } \ + HASH_FSCK(hh_dst,dst); \ +} while (0) + +#define HASH_CLEAR(hh,head) \ +do { \ + if (head) { \ + uthash_free((head)->hh.tbl->buckets, \ + (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket)); \ + HASH_BLOOM_FREE((head)->hh.tbl); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head)=NULL; \ + } \ +} while(0) + +#define HASH_OVERHEAD(hh,head) \ + (size_t)((((head)->hh.tbl->num_items * sizeof(UT_hash_handle)) + \ + ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket)) + \ + (sizeof(UT_hash_table)) + \ + (HASH_BLOOM_BYTELEN))) + +#ifdef NO_DECLTYPE +#define HASH_ITER(hh,head,el,tmp) \ +for((el)=(head), (*(char**)(&(tmp)))=(char*)((head)?(head)->hh.next:NULL); \ + el; (el)=(tmp),(*(char**)(&(tmp)))=(char*)((tmp)?(tmp)->hh.next:NULL)) +#else +#define HASH_ITER(hh,head,el,tmp) \ +for((el)=(head),(tmp)=DECLTYPE(el)((head)?(head)->hh.next:NULL); \ + el; (el)=(tmp),(tmp)=DECLTYPE(el)((tmp)?(tmp)->hh.next:NULL)) +#endif + +/* obtain a count of items in the hash */ +#define HASH_COUNT(head) HASH_CNT(hh,head) +#define HASH_CNT(hh,head) ((head)?((head)->hh.tbl->num_items):0) + +typedef struct UT_hash_bucket { + struct UT_hash_handle *hh_head; + unsigned count; + + /* expand_mult is normally set to 0. In this situation, the max chain length + * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If + * the bucket's chain exceeds this length, bucket expansion is triggered). + * However, setting expand_mult to a non-zero value delays bucket expansion + * (that would be triggered by additions to this particular bucket) + * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH. + * (The multiplier is simply expand_mult+1). The whole idea of this + * multiplier is to reduce bucket expansions, since they are expensive, in + * situations where we know that a particular bucket tends to be overused. + * It is better to let its chain length grow to a longer yet-still-bounded + * value, than to do an O(n) bucket expansion too often. + */ + unsigned expand_mult; + +} UT_hash_bucket; + +/* random signature used only to find hash tables in external analysis */ +#define HASH_SIGNATURE 0xa0111fe1 +#define HASH_BLOOM_SIGNATURE 0xb12220f2 + +typedef struct UT_hash_table { + UT_hash_bucket *buckets; + unsigned num_buckets, log2_num_buckets; + unsigned num_items; + struct UT_hash_handle *tail; /* tail hh in app order, for fast append */ + ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */ + + /* in an ideal situation (all buckets used equally), no bucket would have + * more than ceil(#items/#buckets) items. that's the ideal chain length. */ + unsigned ideal_chain_maxlen; + + /* nonideal_items is the number of items in the hash whose chain position + * exceeds the ideal chain maxlen. these items pay the penalty for an uneven + * hash distribution; reaching them in a chain traversal takes >ideal steps */ + unsigned nonideal_items; + + /* ineffective expands occur when a bucket doubling was performed, but + * afterward, more than half the items in the hash had nonideal chain + * positions. If this happens on two consecutive expansions we inhibit any + * further expansion, as it's not helping; this happens when the hash + * function isn't a good fit for the key domain. When expansion is inhibited + * the hash will still work, albeit no longer in constant time. */ + unsigned ineff_expands, noexpand; + + uint32_t signature; /* used only to find hash tables in external analysis */ +#ifdef HASH_BLOOM + uint32_t bloom_sig; /* used only to test bloom exists in external analysis */ + uint8_t *bloom_bv; + char bloom_nbits; +#endif + +} UT_hash_table; + +typedef struct UT_hash_handle { + struct UT_hash_table *tbl; + void *prev; /* prev element in app order */ + void *next; /* next element in app order */ + struct UT_hash_handle *hh_prev; /* previous hh in bucket order */ + struct UT_hash_handle *hh_next; /* next hh in bucket order */ + void *key; /* ptr to enclosing struct's key */ + unsigned keylen; /* enclosing struct's key len */ + unsigned hashv; /* result of hash-fcn(key) */ +} UT_hash_handle; + +#endif /* UTHASH_H */ diff --git a/util.c b/util.c new file mode 100644 index 0000000..452ff56 --- /dev/null +++ b/util.c @@ -0,0 +1,4050 @@ +/* + * Copyright 2011-2014 Con Kolivas + * Copyright 2010 Jeff Garzik + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#ifdef HAVE_LIBCURL +#include +#endif +#include +#include +#include +#include +#ifndef WIN32 +#include +# ifdef __linux +# include +# endif +# include +# include +# include +# include +#else +# include +# include +# include +#endif +#include + +#include "miner.h" +#include "elist.h" +#include "compat.h" +#include "util.h" + +#define DEFAULT_SOCKWAIT 60 + +bool successful_connect = false; + +int no_yield(void) +{ + return 0; +} + +int (*selective_yield)(void) = &no_yield; + +unsigned char bit_swap_table[256] = +{ + 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, + 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0, + 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, + 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, + 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, + 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4, + 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, + 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc, + 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, + 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2, + 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, + 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa, + 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, + 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6, + 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, + 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe, + 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, + 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1, + 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, + 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9, + 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, + 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5, + 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, + 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd, + 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, + 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3, + 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, + 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb, + 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, + 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7, + 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, + 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff, +}; + +static void keep_sockalive(SOCKETTYPE fd) +{ + const int tcp_one = 1; +#ifndef WIN32 + const int tcp_keepidle = 45; + const int tcp_keepintvl = 30; + int flags = fcntl(fd, F_GETFL, 0); + + fcntl(fd, F_SETFL, O_NONBLOCK | flags); +#else + u_long flags = 1; + + ioctlsocket(fd, FIONBIO, &flags); +#endif + + setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (const void *)&tcp_one, sizeof(tcp_one)); + if (!opt_delaynet) +#ifndef __linux + setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (const void *)&tcp_one, sizeof(tcp_one)); +#else /* __linux */ + fcntl(fd, F_SETFD, FD_CLOEXEC); + setsockopt(fd, SOL_TCP, TCP_NODELAY, (const void *)&tcp_one, sizeof(tcp_one)); + setsockopt(fd, SOL_TCP, TCP_KEEPCNT, &tcp_one, sizeof(tcp_one)); + setsockopt(fd, SOL_TCP, TCP_KEEPIDLE, &tcp_keepidle, sizeof(tcp_keepidle)); + setsockopt(fd, SOL_TCP, TCP_KEEPINTVL, &tcp_keepintvl, sizeof(tcp_keepintvl)); +#endif /* __linux */ + +#ifdef __APPLE_CC__ + setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &tcp_keepintvl, sizeof(tcp_keepintvl)); +#endif /* __APPLE_CC__ */ + +} + +#ifdef WIN32 +/* Generic versions of inet_pton for windows, using different names in case + * it is implemented in ming in the future. */ +#define W32NS_INADDRSZ 4 +#define W32NS_IN6ADDRSZ 16 +#define W32NS_INT16SZ 2 + +static int Inet_Pton4(const char *src, char *dst) +{ + uint8_t tmp[W32NS_INADDRSZ], *tp; + + int saw_digit = 0; + int octets = 0; + *(tp = tmp) = 0; + + int ch; + while ((ch = *src++) != '\0') + { + if (ch >= '0' && ch <= '9') + { + uint32_t n = *tp * 10 + (ch - '0'); + + if (saw_digit && *tp == 0) + return 0; + + if (n > 255) + return 0; + + *tp = n; + if (!saw_digit) + { + if (++octets > 4) + return 0; + saw_digit = 1; + } + } + else if (ch == '.' && saw_digit) + { + if (octets == 4) + return 0; + *++tp = 0; + saw_digit = 0; + } + else + return 0; + } + if (octets < 4) + return 0; + + memcpy(dst, tmp, W32NS_INADDRSZ); + + return 1; +} + +static int Inet_Pton6(const char *src, char *dst) +{ + static const char xdigits[] = "0123456789abcdef"; + uint8_t tmp[W32NS_IN6ADDRSZ]; + + uint8_t *tp = (uint8_t*) memset(tmp, '\0', W32NS_IN6ADDRSZ); + uint8_t *endp = tp + W32NS_IN6ADDRSZ; + uint8_t *colonp = NULL; + + /* Leading :: requires some special handling. */ + if (*src == ':') + { + if (*++src != ':') + return 0; + } + + const char *curtok = src; + int saw_xdigit = 0; + uint32_t val = 0; + int ch; + while ((ch = tolower(*src++)) != '\0') + { + const char *pch = strchr(xdigits, ch); + if (pch != NULL) + { + val <<= 4; + val |= (pch - xdigits); + if (val > 0xffff) + return 0; + saw_xdigit = 1; + continue; + } + if (ch == ':') + { + curtok = src; + if (!saw_xdigit) + { + if (colonp) + return 0; + colonp = tp; + continue; + } + else if (*src == '\0') + { + return 0; + } + if (tp + W32NS_INT16SZ > endp) + return 0; + *tp++ = (uint8_t) (val >> 8) & 0xff; + *tp++ = (uint8_t) val & 0xff; + saw_xdigit = 0; + val = 0; + continue; + } + if (ch == '.' && ((tp + W32NS_INADDRSZ) <= endp) && + Inet_Pton4(curtok, (char*) tp) > 0) + { + tp += W32NS_INADDRSZ; + saw_xdigit = 0; + break; /* '\0' was seen by inet_pton4(). */ + } + return 0; + } + if (saw_xdigit) + { + if (tp + W32NS_INT16SZ > endp) + return 0; + *tp++ = (uint8_t) (val >> 8) & 0xff; + *tp++ = (uint8_t) val & 0xff; + } + if (colonp != NULL) + { + int i; + /* + * Since some memmove()'s erroneously fail to handle + * overlapping regions, we'll do the shift by hand. + */ + const int n = tp - colonp; + + if (tp == endp) + return 0; + + for (i = 1; i <= n; i++) + { + endp[-i] = colonp[n - i]; + colonp[n - i] = 0; + } + tp = endp; + } + if (tp != endp) + return 0; + + memcpy(dst, tmp, W32NS_IN6ADDRSZ); + + return 1; +} + +int Inet_Pton(int af, const char *src, void *dst) +{ + switch (af) + { + case AF_INET: + return Inet_Pton4(src, dst); + case AF_INET6: + return Inet_Pton6(src, dst); + default: + return -1; + } +} +#endif + +struct tq_ent +{ + void *data; + struct list_head q_node; +}; + +#ifdef HAVE_LIBCURL +struct timeval nettime; + +struct data_buffer +{ + void *buf; + size_t len; +}; + +struct upload_buffer +{ + const void *buf; + size_t len; +}; + +struct header_info +{ + char *lp_path; + int rolltime; + char *reason; + char *stratum_url; + bool hadrolltime; + bool canroll; + bool hadexpire; +}; + +static void databuf_free(struct data_buffer *db) +{ + if (!db) + return; + + free(db->buf); + + memset(db, 0, sizeof(*db)); +} + +static size_t all_data_cb(const void *ptr, size_t size, size_t nmemb, + void *user_data) +{ + struct data_buffer *db = user_data; + size_t len = size * nmemb; + size_t oldlen, newlen; + void *newmem; + static const unsigned char zero = 0; + + oldlen = db->len; + newlen = oldlen + len; + + newmem = realloc(db->buf, newlen + 1); + if (!newmem) + return 0; + + db->buf = newmem; + db->len = newlen; + memcpy(db->buf + oldlen, ptr, len); + memcpy(db->buf + newlen, &zero, 1); /* null terminate */ + + return len; +} + +static size_t upload_data_cb(void *ptr, size_t size, size_t nmemb, + void *user_data) +{ + struct upload_buffer *ub = user_data; + unsigned int len = size * nmemb; + + if (len > ub->len) + len = ub->len; + + if (len) + { + memcpy(ptr, ub->buf, len); + ub->buf += len; + ub->len -= len; + } + + return len; +} + +static size_t resp_hdr_cb(void *ptr, size_t size, size_t nmemb, void *user_data) +{ + struct header_info *hi = user_data; + size_t remlen, slen, ptrlen = size * nmemb; + char *rem, *val = NULL, *key = NULL; + void *tmp; + + val = calloc(1, ptrlen); + key = calloc(1, ptrlen); + if (!key || !val) + goto out; + + tmp = memchr(ptr, ':', ptrlen); + if (!tmp || (tmp == ptr)) /* skip empty keys / blanks */ + goto out; + slen = tmp - ptr; + if ((slen + 1) == ptrlen) /* skip key w/ no value */ + goto out; + memcpy(key, ptr, slen); /* store & nul term key */ + key[slen] = 0; + + rem = ptr + slen + 1; /* trim value's leading whitespace */ + remlen = ptrlen - slen - 1; + while ((remlen > 0) && (isspace(*rem))) + { + remlen--; + rem++; + } + + memcpy(val, rem, remlen); /* store value, trim trailing ws */ + val[remlen] = 0; + while ((*val) && (isspace(val[strlen(val) - 1]))) + val[strlen(val) - 1] = 0; + + if (!*val) /* skip blank value */ + goto out; + + if (opt_protocol) + applog(LOG_DEBUG, "HTTP hdr(%s): %s", key, val); + + if (!strcasecmp("X-Roll-Ntime", key)) + { + hi->hadrolltime = true; + if (!strncasecmp("N", val, 1)) + applog(LOG_DEBUG, "X-Roll-Ntime: N found"); + else + { + hi->canroll = true; + + /* Check to see if expire= is supported and if not, set + * the rolltime to the default scantime */ + if (strlen(val) > 7 && !strncasecmp("expire=", val, 7)) + { + sscanf(val + 7, "%d", &hi->rolltime); + hi->hadexpire = true; + } + else + hi->rolltime = opt_scantime; + applog(LOG_DEBUG, "X-Roll-Ntime expiry set to %d", hi->rolltime); + } + } + + if (!strcasecmp("X-Long-Polling", key)) + { + hi->lp_path = val; /* steal memory reference */ + val = NULL; + } + + if (!strcasecmp("X-Reject-Reason", key)) + { + hi->reason = val; /* steal memory reference */ + val = NULL; + } + + if (!strcasecmp("X-Stratum", key)) + { + hi->stratum_url = val; + val = NULL; + } + +out: + free(key); + free(val); + return ptrlen; +} + +static void last_nettime(struct timeval *last) +{ + rd_lock(&netacc_lock); + last->tv_sec = nettime.tv_sec; + last->tv_usec = nettime.tv_usec; + rd_unlock(&netacc_lock); +} + +static void set_nettime(void) +{ + wr_lock(&netacc_lock); + cgtime(&nettime); + wr_unlock(&netacc_lock); +} + +#if CURL_HAS_KEEPALIVE +static void keep_curlalive(CURL *curl) +{ + const int tcp_keepidle = 45; + const int tcp_keepintvl = 30; + const long int keepalive = 1; + + curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, keepalive); + curl_easy_setopt(curl, CURLOPT_TCP_KEEPIDLE, tcp_keepidle); + curl_easy_setopt(curl, CURLOPT_TCP_KEEPINTVL, tcp_keepintvl); +} +#else +static void keep_curlalive(CURL *curl) +{ + SOCKETTYPE sock; + + curl_easy_getinfo(curl, CURLINFO_LASTSOCKET, (long *)&sock); + keep_sockalive(sock); +} +#endif + +static int curl_debug_cb(__maybe_unused CURL *handle, curl_infotype type, + __maybe_unused char *data, size_t size, void *userdata) +{ + struct pool *pool = (struct pool *)userdata; + + switch(type) + { + case CURLINFO_HEADER_IN: + case CURLINFO_DATA_IN: + case CURLINFO_SSL_DATA_IN: + pool->cgminer_pool_stats.net_bytes_received += size; + break; + case CURLINFO_HEADER_OUT: + case CURLINFO_DATA_OUT: + case CURLINFO_SSL_DATA_OUT: + pool->cgminer_pool_stats.net_bytes_sent += size; + break; + case CURLINFO_TEXT: + default: + break; + } + return 0; +} + +json_t *json_web_config(const char *url) +{ + struct data_buffer all_data = {NULL, 0}; + char curl_err_str[CURL_ERROR_SIZE]; + long timeout = 60; + json_error_t err; + json_t *val; + CURL *curl; + int rc; + + memset(&err, 0, sizeof(err)); + + curl = curl_easy_init(); + if (unlikely(!curl)) + quithere(1, "CURL initialisation failed"); + + curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout); + + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_ENCODING, ""); + curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1); + + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, all_data_cb); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &all_data); + curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_err_str); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); + curl_easy_setopt(curl, CURLOPT_USE_SSL, CURLUSESSL_TRY); + + val = NULL; + rc = curl_easy_perform(curl); + curl_easy_cleanup(curl); + if (rc) + { + applog(LOG_ERR, "HTTP config request of '%s' failed: %s", url, curl_err_str); + goto c_out; + } + + if (!all_data.buf) + { + applog(LOG_ERR, "Empty config data received from '%s'", url); + goto c_out; + } + + val = JSON_LOADS(all_data.buf, &err); + if (!val) + { + applog(LOG_ERR, "JSON config decode of '%s' failed(%d): %s", url, + err.line, err.text); + } + databuf_free(&all_data); + +c_out: + return val; +} + +json_t *json_rpc_call(CURL *curl, const char *url, + const char *userpass, const char *rpc_req, + bool probe, bool longpoll, int *rolltime, + struct pool *pool, bool share) +{ + long timeout = longpoll ? (60 * 60) : 60; + struct data_buffer all_data = {NULL, 0}; + struct header_info hi = {NULL, 0, NULL, NULL, false, false, false}; + char len_hdr[64], user_agent_hdr[128]; + char curl_err_str[CURL_ERROR_SIZE]; + struct curl_slist *headers = NULL; + struct upload_buffer upload_data; + json_t *val, *err_val, *res_val; + bool probing = false; + double byte_count; + json_error_t err; + int rc; + + memset(&err, 0, sizeof(err)); + + /* it is assumed that 'curl' is freshly [re]initialized at this pt */ + + if (probe) + probing = !pool->probed; + curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout); + + // CURLOPT_VERBOSE won't write to stderr if we use CURLOPT_DEBUGFUNCTION + curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, curl_debug_cb); + curl_easy_setopt(curl, CURLOPT_DEBUGDATA, (void *)pool); + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); + + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_ENCODING, ""); + curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1); + + /* Shares are staggered already and delays in submission can be costly + * so do not delay them */ + if (!opt_delaynet || share) + curl_easy_setopt(curl, CURLOPT_TCP_NODELAY, 1); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, all_data_cb); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &all_data); + curl_easy_setopt(curl, CURLOPT_READFUNCTION, upload_data_cb); + curl_easy_setopt(curl, CURLOPT_READDATA, &upload_data); + curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_err_str); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, resp_hdr_cb); + curl_easy_setopt(curl, CURLOPT_HEADERDATA, &hi); + curl_easy_setopt(curl, CURLOPT_USE_SSL, CURLUSESSL_TRY); + if (pool->rpc_proxy) + { + curl_easy_setopt(curl, CURLOPT_PROXY, pool->rpc_proxy); + curl_easy_setopt(curl, CURLOPT_PROXYTYPE, pool->rpc_proxytype); + } + else if (opt_socks_proxy) + { + curl_easy_setopt(curl, CURLOPT_PROXY, opt_socks_proxy); + curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS4); + } + if (userpass) + { + curl_easy_setopt(curl, CURLOPT_USERPWD, userpass); + curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); + } + if (longpoll) + keep_curlalive(curl); + curl_easy_setopt(curl, CURLOPT_POST, 1); + + if (opt_protocol) + applog(LOG_DEBUG, "JSON protocol request:\n%s", rpc_req); + + upload_data.buf = rpc_req; + upload_data.len = strlen(rpc_req); + sprintf(len_hdr, "Content-Length: %lu", + (unsigned long) upload_data.len); + sprintf(user_agent_hdr, "User-Agent: %s", PACKAGE_STRING); + + headers = curl_slist_append(headers, + "Content-type: application/json"); + headers = curl_slist_append(headers, + "X-Mining-Extensions: longpoll midstate rollntime submitold"); + + if (likely(global_hashrate)) + { + char ghashrate[255]; + + sprintf(ghashrate, "X-Mining-Hashrate: %llu", global_hashrate); + headers = curl_slist_append(headers, ghashrate); + } + + headers = curl_slist_append(headers, len_hdr); + headers = curl_slist_append(headers, user_agent_hdr); + headers = curl_slist_append(headers, "Expect:"); /* disable Expect hdr*/ + + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + + if (opt_delaynet) + { + /* Don't delay share submission, but still track the nettime */ + if (!share) + { + long long now_msecs, last_msecs; + struct timeval now, last; + + cgtime(&now); + last_nettime(&last); + now_msecs = (long long)now.tv_sec * 1000; + now_msecs += now.tv_usec / 1000; + last_msecs = (long long)last.tv_sec * 1000; + last_msecs += last.tv_usec / 1000; + if (now_msecs > last_msecs && now_msecs - last_msecs < 250) + { + struct timespec rgtp; + + rgtp.tv_sec = 0; + rgtp.tv_nsec = (250 - (now_msecs - last_msecs)) * 1000000; + nanosleep(&rgtp, NULL); + } + } + set_nettime(); + } + + rc = curl_easy_perform(curl); + if (rc) + { + applog(LOG_INFO, "HTTP request failed: %s", curl_err_str); + goto err_out; + } + + if (!all_data.buf) + { + applog(LOG_DEBUG, "Empty data received in json_rpc_call."); + goto err_out; + } + + pool->cgminer_pool_stats.times_sent++; + if (curl_easy_getinfo(curl, CURLINFO_SIZE_UPLOAD, &byte_count) == CURLE_OK) + pool->cgminer_pool_stats.bytes_sent += byte_count; + pool->cgminer_pool_stats.times_received++; + if (curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD, &byte_count) == CURLE_OK) + pool->cgminer_pool_stats.bytes_received += byte_count; + + if (probing) + { + pool->probed = true; + /* If X-Long-Polling was found, activate long polling */ + if (hi.lp_path) + { + if (pool->hdr_path != NULL) + free(pool->hdr_path); + pool->hdr_path = hi.lp_path; + } + else + pool->hdr_path = NULL; + if (hi.stratum_url) + { + pool->stratum_url = hi.stratum_url; + hi.stratum_url = NULL; + } + } + else + { + if (hi.lp_path) + { + free(hi.lp_path); + hi.lp_path = NULL; + } + if (hi.stratum_url) + { + free(hi.stratum_url); + hi.stratum_url = NULL; + } + } + + *rolltime = hi.rolltime; + pool->cgminer_pool_stats.rolltime = hi.rolltime; + pool->cgminer_pool_stats.hadrolltime = hi.hadrolltime; + pool->cgminer_pool_stats.canroll = hi.canroll; + pool->cgminer_pool_stats.hadexpire = hi.hadexpire; + + val = JSON_LOADS(all_data.buf, &err); + if (!val) + { + applog(LOG_INFO, "JSON decode failed(%d): %s", err.line, err.text); + + if (opt_protocol) + applog(LOG_DEBUG, "JSON protocol response:\n%s", (char *)(all_data.buf)); + + goto err_out; + } + + if (opt_protocol) + { + char *s = json_dumps(val, JSON_INDENT(3)); + + applog(LOG_DEBUG, "JSON protocol response:\n%s", s); + free(s); + } + + /* JSON-RPC valid response returns a non-null 'result', + * and a null 'error'. + */ + res_val = json_object_get(val, "result"); + err_val = json_object_get(val, "error"); + + if (!res_val ||(err_val && !json_is_null(err_val))) + { + char *s; + + if (err_val) + s = json_dumps(err_val, JSON_INDENT(3)); + else + s = strdup("(unknown reason)"); + + applog(LOG_INFO, "JSON-RPC call failed: %s", s); + + free(s); + + goto err_out; + } + + if (hi.reason) + { + json_object_set_new(val, "reject-reason", json_string(hi.reason)); + free(hi.reason); + hi.reason = NULL; + } + successful_connect = true; + databuf_free(&all_data); + curl_slist_free_all(headers); + curl_easy_reset(curl); + return val; + +err_out: + databuf_free(&all_data); + curl_slist_free_all(headers); + curl_easy_reset(curl); + if (!successful_connect) + applog(LOG_DEBUG, "Failed to connect in json_rpc_call"); + curl_easy_setopt(curl, CURLOPT_FRESH_CONNECT, 1); + return NULL; +} +#define PROXY_HTTP CURLPROXY_HTTP +#define PROXY_HTTP_1_0 CURLPROXY_HTTP_1_0 +#define PROXY_SOCKS4 CURLPROXY_SOCKS4 +#define PROXY_SOCKS5 CURLPROXY_SOCKS5 +#define PROXY_SOCKS4A CURLPROXY_SOCKS4A +#define PROXY_SOCKS5H CURLPROXY_SOCKS5_HOSTNAME +#else /* HAVE_LIBCURL */ +#define PROXY_HTTP 0 +#define PROXY_HTTP_1_0 1 +#define PROXY_SOCKS4 2 +#define PROXY_SOCKS5 3 +#define PROXY_SOCKS4A 4 +#define PROXY_SOCKS5H 5 +#endif /* HAVE_LIBCURL */ + +static struct +{ + const char *name; + proxytypes_t proxytype; +} proxynames[] = +{ + { "http:", PROXY_HTTP }, + { "http0:", PROXY_HTTP_1_0 }, + { "socks4:", PROXY_SOCKS4 }, + { "socks5:", PROXY_SOCKS5 }, + { "socks4a:", PROXY_SOCKS4A }, + { "socks5h:", PROXY_SOCKS5H }, + { NULL, 0 } +}; + +const char *proxytype(proxytypes_t proxytype) +{ + int i; + + for (i = 0; proxynames[i].name; i++) + if (proxynames[i].proxytype == proxytype) + return proxynames[i].name; + + return "invalid"; +} + +char *get_proxy(char *url, struct pool *pool) +{ + pool->rpc_proxy = NULL; + + char *split; + int plen, len, i; + + for (i = 0; proxynames[i].name; i++) + { + plen = strlen(proxynames[i].name); + if (strncmp(url, proxynames[i].name, plen) == 0) + { + if (!(split = strchr(url, '|'))) + return url; + + *split = '\0'; + len = split - url; + pool->rpc_proxy = malloc(1 + len - plen); + if (!(pool->rpc_proxy)) + quithere(1, "Failed to malloc rpc_proxy"); + + strcpy(pool->rpc_proxy, url + plen); + extract_sockaddr(pool->rpc_proxy, &pool->sockaddr_proxy_url, &pool->sockaddr_proxy_port); + pool->rpc_proxytype = proxynames[i].proxytype; + url = split + 1; + break; + } + } + return url; +} + +/* Adequate size s==len*2 + 1 must be alloced to use this variant */ +void __bin2hex(char *s, const unsigned char *p, size_t len) +{ + int i; + static const char hex[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + + for (i = 0; i < (int)len; i++) + { + *s++ = hex[p[i] >> 4]; + *s++ = hex[p[i] & 0xF]; + } + *s++ = '\0'; +} + +/* Returns a malloced array string of a binary value of arbitrary length. The + * array is rounded up to a 4 byte size to appease architectures that need + * aligned array sizes */ +char *bin2hex(const unsigned char *p, size_t len) +{ + ssize_t slen; + char *s; + + slen = len * 2 + 1; + if (slen % 4) + slen += 4 - (slen % 4); + s = calloc(slen, 1); + if (unlikely(!s)) + quithere(1, "Failed to calloc"); + + __bin2hex(s, p, len); + + return s; +} + +static const int hex2bin_tbl[256] = +{ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +}; + +/* Does the reverse of bin2hex but does not allocate any ram */ +bool hex2bin(unsigned char *p, const char *hexstr, size_t len) +{ + int nibble1, nibble2; + unsigned char idx; + bool ret = false; + + while (*hexstr && len) + { + if (unlikely(!hexstr[1])) + { + applog(LOG_ERR, "hex2bin str truncated"); + return ret; + } + + idx = *hexstr++; + nibble1 = hex2bin_tbl[idx]; + idx = *hexstr++; + nibble2 = hex2bin_tbl[idx]; + + if (unlikely((nibble1 < 0) || (nibble2 < 0))) + { + applog(LOG_ERR, "hex2bin scan failed"); + return ret; + } + + *p++ = (((unsigned char)nibble1) << 4) | ((unsigned char)nibble2); + --len; + } + + if (likely(len == 0 && *hexstr == 0)) + ret = true; + return ret; +} + +static bool _valid_hex(char *s, const char *file, const char *func, const int line) +{ + bool ret = false; + int i, len; + + if (unlikely(!s)) + { + applog(LOG_ERR, "Null string passed to valid_hex from"IN_FMT_FFL, file, func, line); + return ret; + } + len = strlen(s); + for (i = 0; i < len; i++) + { + unsigned char idx = s[i]; + + if (unlikely(hex2bin_tbl[idx] < 0)) + { + applog(LOG_ERR, "Invalid char 0x%x passed to valid_hex from"IN_FMT_FFL, idx, file, func, line); + return ret; + } + } + ret = true; + return ret; +} + +#define valid_hex(s) _valid_hex(s, __FILE__, __func__, __LINE__) + +static bool _valid_ascii(char *s, const char *file, const char *func, const int line) +{ + bool ret = false; + int i, len; + + if (unlikely(!s)) + { + applog(LOG_ERR, "Null string passed to valid_ascii from"IN_FMT_FFL, file, func, line); + return ret; + } + len = strlen(s); + if (unlikely(!len)) + { + applog(LOG_ERR, "Zero length string passed to valid_ascii from"IN_FMT_FFL, file, func, line); + return ret; + } + for (i = 0; i < len; i++) + { + unsigned char idx = s[i]; + + if (unlikely(idx < 32 || idx > 126)) + { + applog(LOG_ERR, "Invalid char 0x%x passed to valid_ascii from"IN_FMT_FFL, idx, file, func, line); + return ret; + } + } + ret = true; + return ret; +} + +#define valid_ascii(s) _valid_ascii(s, __FILE__, __func__, __LINE__) + +static const int b58tobin_tbl[] = +{ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, -1, -1, -1, -1, -1, -1, + -1, 9, 10, 11, 12, 13, 14, 15, 16, -1, 17, 18, 19, 20, 21, -1, + 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, -1, -1, -1, -1, -1, + -1, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, -1, 44, 45, 46, + 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57 +}; + +/* b58bin should always be at least 25 bytes long and already checked to be + * valid. */ +void b58tobin(unsigned char *b58bin, const char *b58) +{ + uint32_t c, bin32[7]; + int len, i, j; + uint64_t t; + + memset(bin32, 0, 7 * sizeof(uint32_t)); + len = strlen(b58); + for (i = 0; i < len; i++) + { + c = b58[i]; + c = b58tobin_tbl[c]; + for (j = 6; j >= 0; j--) + { + t = ((uint64_t)bin32[j]) * 58 + c; + c = (t & 0x3f00000000ull) >> 32; + bin32[j] = t & 0xffffffffull; + } + } + *(b58bin++) = bin32[0] & 0xff; + for (i = 1; i < 7; i++) + { + *((uint32_t *)b58bin) = htobe32(bin32[i]); + b58bin += sizeof(uint32_t); + } +} + +void address_to_pubkeyhash(unsigned char *pkh, const char *addr) +{ + unsigned char b58bin[25]; + + memset(b58bin, 0, 25); + b58tobin(b58bin, addr); + pkh[0] = 0x76; + pkh[1] = 0xa9; + pkh[2] = 0x14; + memcpy(&pkh[3], &b58bin[1], 20); + pkh[23] = 0x88; + pkh[24] = 0xac; +} + +/* For encoding nHeight into coinbase, return how many bytes were used */ +int ser_number(unsigned char *s, int32_t val) +{ + int32_t *i32 = (int32_t *)&s[1]; + int len; + + if (val < 128) + len = 1; + else if (val < 16512) + len = 2; + else if (val < 2113664) + len = 3; + else + len = 4; + *i32 = htole32(val); + s[0] = len++; + return len; +} + +/* For encoding variable length strings */ +unsigned char *ser_string(char *s, int *slen) +{ + size_t len = strlen(s); + unsigned char *ret; + + ret = malloc(1 + len + 8); // Leave room for largest size + if (unlikely(!ret)) + quit(1, "Failed to malloc ret in ser_string"); + if (len < 253) + { + ret[0] = len; + memcpy(ret + 1, s, len); + *slen = len + 1; + } + else if (len < 0x10000) + { + uint16_t *u16 = (uint16_t *)&ret[1]; + + ret[0] = 253; + *u16 = htobe16(len); + memcpy(ret + 3, s, len); + *slen = len + 3; + } + else + { + /* size_t is only 32 bit on many platforms anyway */ + uint32_t *u32 = (uint32_t *)&ret[1]; + + ret[0] = 254; + *u32 = htobe32(len); + memcpy(ret + 5, s, len); + *slen = len + 5; + } + return ret; +} + +bool fulltest(const unsigned char *hash, const unsigned char *target) +{ + uint32_t *hash32 = (uint32_t *)hash; + uint32_t *target32 = (uint32_t *)target; + bool rc = true; + int i; + + for (i = 28 / 4; i >= 0; i--) + { + uint32_t h32tmp = le32toh(hash32[i]); + uint32_t t32tmp = le32toh(target32[i]); + + if (h32tmp > t32tmp) + { + rc = false; + break; + } + if (h32tmp < t32tmp) + { + rc = true; + break; + } + } + + if (opt_debug) + { + unsigned char hash_swap[32], target_swap[32]; + char *hash_str, *target_str; + + swab256(hash_swap, hash); + swab256(target_swap, target); + hash_str = bin2hex(hash_swap, 32); + target_str = bin2hex(target_swap, 32); + + applog(LOG_DEBUG, " Proof: %s\nTarget: %s\nTrgVal? %s", + hash_str, + target_str, + rc ? "YES (hash <= target)" : + "no (false positive; hash > target)"); + + free(hash_str); + free(target_str); + } + + return rc; +} + +struct thread_q *tq_new(void) +{ + struct thread_q *tq; + + tq = calloc(1, sizeof(*tq)); + if (!tq) + return NULL; + + INIT_LIST_HEAD(&tq->q); + pthread_mutex_init(&tq->mutex, NULL); + pthread_cond_init(&tq->cond, NULL); + + return tq; +} + +void tq_free(struct thread_q *tq) +{ + struct tq_ent *ent, *iter; + + if (!tq) + return; + + list_for_each_entry_safe(ent, iter, &tq->q, q_node) + { + list_del(&ent->q_node); + free(ent); + } + + pthread_cond_destroy(&tq->cond); + pthread_mutex_destroy(&tq->mutex); + + memset(tq, 0, sizeof(*tq)); /* poison */ + free(tq); +} + +static void tq_freezethaw(struct thread_q *tq, bool frozen) +{ + mutex_lock(&tq->mutex); + tq->frozen = frozen; + pthread_cond_signal(&tq->cond); + mutex_unlock(&tq->mutex); +} + +void tq_freeze(struct thread_q *tq) +{ + tq_freezethaw(tq, true); +} + +void tq_thaw(struct thread_q *tq) +{ + tq_freezethaw(tq, false); +} + +bool tq_push(struct thread_q *tq, void *data) +{ + struct tq_ent *ent; + bool rc = true; + + ent = calloc(1, sizeof(*ent)); + if (!ent) + return false; + + ent->data = data; + INIT_LIST_HEAD(&ent->q_node); + + mutex_lock(&tq->mutex); + if (!tq->frozen) + { + list_add_tail(&ent->q_node, &tq->q); + } + else + { + free(ent); + rc = false; + } + pthread_cond_signal(&tq->cond); + mutex_unlock(&tq->mutex); + + return rc; +} + +void *tq_pop(struct thread_q *tq, const struct timespec *abstime) +{ + struct tq_ent *ent; + void *rval = NULL; + int rc; + + mutex_lock(&tq->mutex); + if (!list_empty(&tq->q)) + goto pop; + + if (abstime) + rc = pthread_cond_timedwait(&tq->cond, &tq->mutex, abstime); + else + rc = pthread_cond_wait(&tq->cond, &tq->mutex); + if (rc) + goto out; + if (list_empty(&tq->q)) + goto out; +pop: + ent = list_entry(tq->q.next, struct tq_ent, q_node); + rval = ent->data; + + list_del(&ent->q_node); + free(ent); +out: + mutex_unlock(&tq->mutex); + + return rval; +} + +int thr_info_create(struct thr_info *thr, pthread_attr_t *attr, void *(*start) (void *), void *arg) +{ + cgsem_init(&thr->sem); + + return pthread_create(&thr->pth, attr, start, arg); +} + +void thr_info_cancel(struct thr_info *thr) +{ + if (!thr) + return; + + if (PTH(thr) != 0L) + { + pthread_cancel(thr->pth); + PTH(thr) = 0L; + } + cgsem_destroy(&thr->sem); +} + +void subtime(struct timeval *a, struct timeval *b) +{ + timersub(a, b, b); +} + +void addtime(struct timeval *a, struct timeval *b) +{ + timeradd(a, b, b); +} + +bool time_more(struct timeval *a, struct timeval *b) +{ + return timercmp(a, b, >); +} + +bool time_less(struct timeval *a, struct timeval *b) +{ + return timercmp(a, b, <); +} + +void copy_time(struct timeval *dest, const struct timeval *src) +{ + memcpy(dest, src, sizeof(struct timeval)); +} + +void timespec_to_val(struct timeval *val, const struct timespec *spec) +{ + val->tv_sec = spec->tv_sec; + val->tv_usec = spec->tv_nsec / 1000; +} + +void timeval_to_spec(struct timespec *spec, const struct timeval *val) +{ + spec->tv_sec = val->tv_sec; + spec->tv_nsec = val->tv_usec * 1000; +} + +void us_to_timeval(struct timeval *val, int64_t us) +{ + lldiv_t tvdiv = lldiv(us, 1000000); + + val->tv_sec = tvdiv.quot; + val->tv_usec = tvdiv.rem; +} + +void us_to_timespec(struct timespec *spec, int64_t us) +{ + lldiv_t tvdiv = lldiv(us, 1000000); + + spec->tv_sec = tvdiv.quot; + spec->tv_nsec = tvdiv.rem * 1000; +} + +void ms_to_timespec(struct timespec *spec, int64_t ms) +{ + lldiv_t tvdiv = lldiv(ms, 1000); + + spec->tv_sec = tvdiv.quot; + spec->tv_nsec = tvdiv.rem * 1000000; +} + +void ms_to_timeval(struct timeval *val, int64_t ms) +{ + lldiv_t tvdiv = lldiv(ms, 1000); + + val->tv_sec = tvdiv.quot; + val->tv_usec = tvdiv.rem * 1000; +} + +static void spec_nscheck(struct timespec *ts) +{ + while (ts->tv_nsec >= 1000000000) + { + ts->tv_nsec -= 1000000000; + ts->tv_sec++; + } + while (ts->tv_nsec < 0) + { + ts->tv_nsec += 1000000000; + ts->tv_sec--; + } +} + +void timeraddspec(struct timespec *a, const struct timespec *b) +{ + a->tv_sec += b->tv_sec; + a->tv_nsec += b->tv_nsec; + spec_nscheck(a); +} + +static int __maybe_unused timespec_to_ms(struct timespec *ts) +{ + return ts->tv_sec * 1000 + ts->tv_nsec / 1000000; +} + +/* Subtract b from a */ +static void __maybe_unused timersubspec(struct timespec *a, const struct timespec *b) +{ + a->tv_sec -= b->tv_sec; + a->tv_nsec -= b->tv_nsec; + spec_nscheck(a); +} + +char *Strcasestr(char *haystack, const char *needle) +{ + char *lowhay, *lowneedle, *ret; + int hlen, nlen, i, ofs; + + if (unlikely(!haystack || !needle)) + return NULL; + hlen = strlen(haystack); + nlen = strlen(needle); + if (!hlen || !nlen) + return NULL; + lowhay = alloca(hlen); + lowneedle = alloca(nlen); + for (i = 0; i < hlen; i++) + lowhay[i] = tolower(haystack[i]); + for (i = 0; i < nlen; i++) + lowneedle[i] = tolower(needle[i]); + ret = strstr(lowhay, lowneedle); + if (!ret) + return ret; + ofs = ret - lowhay; + return haystack + ofs; +} + +char *Strsep(char **stringp, const char *delim) +{ + char *ret = *stringp; + char *p; + + p = (ret != NULL) ? strpbrk(ret, delim) : NULL; + + if (p == NULL) + *stringp = NULL; + else + { + *p = '\0'; + *stringp = p + 1; + } + + return ret; +} + +#ifdef WIN32 +/* Mingw32 has no strsep so create our own custom one */ + +/* Windows start time is since 1601 LOL so convert it to unix epoch 1970. */ +#define EPOCHFILETIME (116444736000000000LL) + +/* These are cgminer specific sleep functions that use an absolute nanosecond + * resolution timer to avoid poor usleep accuracy and overruns. */ + +/* Return the system time as an lldiv_t in decimicroseconds. */ +static void decius_time(lldiv_t *lidiv) +{ + FILETIME ft; + LARGE_INTEGER li; + + GetSystemTimeAsFileTime(&ft); + li.LowPart = ft.dwLowDateTime; + li.HighPart = ft.dwHighDateTime; + li.QuadPart -= EPOCHFILETIME; + + /* SystemTime is in decimicroseconds so divide by an unusual number */ + *lidiv = lldiv(li.QuadPart, 10000000); +} + +/* This is a cgminer gettimeofday wrapper. Since we always call gettimeofday + * with tz set to NULL, and windows' default resolution is only 15ms, this + * gives us higher resolution times on windows. */ +void cgtime(struct timeval *tv) +{ + lldiv_t lidiv; + + decius_time(&lidiv); + tv->tv_sec = lidiv.quot; + tv->tv_usec = lidiv.rem / 10; +} + +#else /* WIN32 */ +void cgtime(struct timeval *tv) +{ + gettimeofday(tv, NULL); +} + +int cgtimer_to_ms(cgtimer_t *cgt) +{ + return timespec_to_ms(cgt); +} + +/* Subtracts b from a and stores it in res. */ +void cgtimer_sub(cgtimer_t *a, cgtimer_t *b, cgtimer_t *res) +{ + res->tv_sec = a->tv_sec - b->tv_sec; + res->tv_nsec = a->tv_nsec - b->tv_nsec; + if (res->tv_nsec < 0) + { + res->tv_nsec += 1000000000; + res->tv_sec--; + } +} +#endif /* WIN32 */ + +#ifdef CLOCK_MONOTONIC /* Essentially just linux */ +void cgtimer_time(cgtimer_t *ts_start) +{ + clock_gettime(CLOCK_MONOTONIC, ts_start); +} + +static void nanosleep_abstime(struct timespec *ts_end) +{ + int ret; + + do + { + ret = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, ts_end, NULL); + } + while (ret == EINTR); +} + +/* Reentrant version of cgsleep functions allow start time to be set separately + * from the beginning of the actual sleep, allowing scheduling delays to be + * counted in the sleep. */ +void cgsleep_ms_r(cgtimer_t *ts_start, int ms) +{ + struct timespec ts_end; + + ms_to_timespec(&ts_end, ms); + timeraddspec(&ts_end, ts_start); + nanosleep_abstime(&ts_end); +} + +void cgsleep_us_r(cgtimer_t *ts_start, int64_t us) +{ + struct timespec ts_end; + + us_to_timespec(&ts_end, us); + timeraddspec(&ts_end, ts_start); + nanosleep_abstime(&ts_end); +} +#else /* CLOCK_MONOTONIC */ +#ifdef __MACH__ +#include +#include +void cgtimer_time(cgtimer_t *ts_start) +{ + clock_serv_t cclock; + mach_timespec_t mts; + + host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock); + clock_get_time(cclock, &mts); + mach_port_deallocate(mach_task_self(), cclock); + ts_start->tv_sec = mts.tv_sec; + ts_start->tv_nsec = mts.tv_nsec; +} +#elif !defined(WIN32) /* __MACH__ - Everything not linux/macosx/win32 */ +void cgtimer_time(cgtimer_t *ts_start) +{ + struct timeval tv; + + cgtime(&tv); + ts_start->tv_sec = tv->tv_sec; + ts_start->tv_nsec = tv->tv_usec * 1000; +} +#endif /* __MACH__ */ + +#ifdef WIN32 +/* For windows we use the SystemTime stored as a LARGE_INTEGER as the cgtimer_t + * typedef, allowing us to have sub-microsecond resolution for times, do simple + * arithmetic for timer calculations, and use windows' own hTimers to get + * accurate absolute timeouts. */ +int cgtimer_to_ms(cgtimer_t *cgt) +{ + return (int)(cgt->QuadPart / 10000LL); +} + +/* Subtracts b from a and stores it in res. */ +void cgtimer_sub(cgtimer_t *a, cgtimer_t *b, cgtimer_t *res) +{ + res->QuadPart = a->QuadPart - b->QuadPart; +} + +/* Note that cgtimer time is NOT offset by the unix epoch since we use absolute + * timeouts with hTimers. */ +void cgtimer_time(cgtimer_t *ts_start) +{ + FILETIME ft; + + GetSystemTimeAsFileTime(&ft); + ts_start->LowPart = ft.dwLowDateTime; + ts_start->HighPart = ft.dwHighDateTime; +} + +static void liSleep(LARGE_INTEGER *li, int timeout) +{ + HANDLE hTimer; + DWORD ret; + + if (unlikely(timeout <= 0)) + return; + + hTimer = CreateWaitableTimer(NULL, TRUE, NULL); + if (unlikely(!hTimer)) + quit(1, "Failed to create hTimer in liSleep"); + ret = SetWaitableTimer(hTimer, li, 0, NULL, NULL, 0); + if (unlikely(!ret)) + quit(1, "Failed to SetWaitableTimer in liSleep"); + /* We still use a timeout as a sanity check in case the system time + * is changed while we're running */ + ret = WaitForSingleObject(hTimer, timeout); + if (unlikely(ret != WAIT_OBJECT_0 && ret != WAIT_TIMEOUT)) + quit(1, "Failed to WaitForSingleObject in liSleep"); + CloseHandle(hTimer); +} + +void cgsleep_ms_r(cgtimer_t *ts_start, int ms) +{ + LARGE_INTEGER li; + + li.QuadPart = ts_start->QuadPart + (int64_t)ms * 10000LL; + liSleep(&li, ms); +} + +void cgsleep_us_r(cgtimer_t *ts_start, int64_t us) +{ + LARGE_INTEGER li; + int ms; + + li.QuadPart = ts_start->QuadPart + us * 10LL; + ms = us / 1000; + if (!ms) + ms = 1; + liSleep(&li, ms); +} +#else /* WIN32 */ +static void cgsleep_spec(struct timespec *ts_diff, const struct timespec *ts_start) +{ + struct timespec now; + + timeraddspec(ts_diff, ts_start); + cgtimer_time(&now); + timersubspec(ts_diff, &now); + if (unlikely(ts_diff->tv_sec < 0)) + return; + nanosleep(ts_diff, NULL); +} + +void cgsleep_ms_r(cgtimer_t *ts_start, int ms) +{ + struct timespec ts_diff; + + ms_to_timespec(&ts_diff, ms); + cgsleep_spec(&ts_diff, ts_start); +} + +void cgsleep_us_r(cgtimer_t *ts_start, int64_t us) +{ + struct timespec ts_diff; + + us_to_timespec(&ts_diff, us); + cgsleep_spec(&ts_diff, ts_start); +} +#endif /* WIN32 */ +#endif /* CLOCK_MONOTONIC */ + +void cgsleep_ms(int ms) +{ + cgtimer_t ts_start; + + cgsleep_prepare_r(&ts_start); + cgsleep_ms_r(&ts_start, ms); +} + +void cgsleep_us(int64_t us) +{ + cgtimer_t ts_start; + + cgsleep_prepare_r(&ts_start); + cgsleep_us_r(&ts_start, us); +} + +/* Returns the microseconds difference between end and start times as a double */ +double us_tdiff(struct timeval *end, struct timeval *start) +{ + /* Sanity check. We should only be using this for small differences so + * limit the max to 60 seconds. */ + if (unlikely(end->tv_sec - start->tv_sec > 60)) + return 60000000; + return (end->tv_sec - start->tv_sec) * 1000000 + (end->tv_usec - start->tv_usec); +} + +/* Returns the milliseconds difference between end and start times */ +int ms_tdiff(struct timeval *end, struct timeval *start) +{ + /* Like us_tdiff, limit to 1 hour. */ + if (unlikely(end->tv_sec - start->tv_sec > 3600)) + return 3600000; + return (end->tv_sec - start->tv_sec) * 1000 + (end->tv_usec - start->tv_usec) / 1000; +} + +/* Returns the seconds difference between end and start times as a double */ +double tdiff(struct timeval *end, struct timeval *start) +{ + return end->tv_sec - start->tv_sec + (end->tv_usec - start->tv_usec) / 1000000.0; +} + +void check_extranonce_option(struct pool *pool, char * url) +{ + char extra_op[16],*extra_op_loc; + extra_op_loc = strstr(url,"#"); + if(extra_op_loc && !pool->extranonce_subscribe) + { + strcpy(extra_op, extra_op_loc); + *extra_op_loc = '\0'; + if(!strcmp(extra_op,"#xnsub")) + { + pool->extranonce_subscribe = true; + applog(LOG_DEBUG, "Pool %d extranonce subscribing enabled.",pool->pool_no); + return; + } + } + return; +} + +bool extract_sockaddr(char *url, char **sockaddr_url, char **sockaddr_port) +{ + char *url_begin, *url_end, *ipv6_begin, *ipv6_end, *port_start = NULL; + char url_address[256], port[6]; + int url_len, port_len = 0; + + *sockaddr_url = url; + url_begin = strstr(url, "//"); + if (!url_begin) + url_begin = url; + else + url_begin += 2; + + /* Look for numeric ipv6 entries */ + ipv6_begin = strstr(url_begin, "["); + ipv6_end = strstr(url_begin, "]"); + if (ipv6_begin && ipv6_end && ipv6_end > ipv6_begin) + url_end = strstr(ipv6_end, ":"); + else + url_end = strstr(url_begin, ":"); + if (url_end) + { + url_len = url_end - url_begin; + port_len = strlen(url_begin) - url_len - 1; + if (port_len < 1) + return false; + port_start = url_end + 1; + } + else + url_len = strlen(url_begin); + + if (url_len < 1) + return false; + + /* Get rid of the [] */ + if (ipv6_begin && ipv6_end && ipv6_end > ipv6_begin) + { + url_len -= 2; + url_begin++; + } + + snprintf(url_address, 254, "%.*s", url_len, url_begin); + + if (port_len) + { + char *slash; + + snprintf(port, 6, "%.*s", port_len, port_start); + slash = strchr(port, '/'); + if (slash) + *slash = '\0'; + } + else + strcpy(port, "80"); + + *sockaddr_port = strdup(port); + *sockaddr_url = strdup(url_address); + + return true; +} + +enum send_ret +{ + SEND_OK, + SEND_SELECTFAIL, + SEND_SENDFAIL, + SEND_INACTIVE +}; + +/* Send a single command across a socket, appending \n to it. This should all + * be done under stratum lock except when first establishing the socket */ +static enum send_ret __stratum_send(struct pool *pool, char *s, ssize_t len) +{ + SOCKETTYPE sock = pool->sock; + ssize_t ssent = 0; + + strcat(s, "\n"); + len++; + + while (len > 0 ) + { + struct timeval timeout = {1, 0}; + ssize_t sent; + fd_set wd; + retry: + FD_ZERO(&wd); + FD_SET(sock, &wd); + if (select(sock + 1, NULL, &wd, NULL, &timeout) < 1) + { + if (interrupted()) + goto retry; + return SEND_SELECTFAIL; + } +#ifdef __APPLE__ + sent = send(pool->sock, s + ssent, len, SO_NOSIGPIPE); +#elif WIN32 + sent = send(pool->sock, s + ssent, len, 0); +#else + sent = send(pool->sock, s + ssent, len, MSG_NOSIGNAL); +#endif + if (sent < 0) + { + if (!sock_blocks()) + return SEND_SENDFAIL; + sent = 0; + } + ssent += sent; + len -= sent; + } + + pool->cgminer_pool_stats.times_sent++; + pool->cgminer_pool_stats.bytes_sent += ssent; + pool->cgminer_pool_stats.net_bytes_sent += ssent; + return SEND_OK; +} + +bool stratum_send(struct pool *pool, char *s, ssize_t len) +{ + enum send_ret ret = SEND_INACTIVE; + + if (opt_protocol) + applog(LOG_DEBUG, "SEND: %s", s); + + mutex_lock(&pool->stratum_lock); + if (pool->stratum_active) + ret = __stratum_send(pool, s, len); + mutex_unlock(&pool->stratum_lock); + + /* This is to avoid doing applog under stratum_lock */ + switch (ret) + { + default: + case SEND_OK: + break; + case SEND_SELECTFAIL: + applog(LOG_DEBUG, "Write select failed on pool %d sock", pool->pool_no); + suspend_stratum(pool); + break; + case SEND_SENDFAIL: + applog(LOG_DEBUG, "Failed to send in stratum_send"); + suspend_stratum(pool); + break; + case SEND_INACTIVE: + applog(LOG_DEBUG, "Stratum send failed due to no pool stratum_active"); + break; + } + return (ret == SEND_OK); +} + +static bool socket_full(struct pool *pool, int wait) +{ + SOCKETTYPE sock = pool->sock; + struct timeval timeout; + fd_set rd; + + if (unlikely(wait < 0)) + wait = 0; + FD_ZERO(&rd); + FD_SET(sock, &rd); + timeout.tv_usec = 0; + timeout.tv_sec = wait; + if (select(sock + 1, &rd, NULL, NULL, &timeout) > 0) + return true; + return false; +} + +/* Check to see if Santa's been good to you */ +bool sock_full(struct pool *pool) +{ + if (strlen(pool->sockbuf)) + return true; + + return (socket_full(pool, 0)); +} + +static void clear_sockbuf(struct pool *pool) +{ + if (likely(pool->sockbuf)) + strcpy(pool->sockbuf, ""); +} + +static void clear_sock(struct pool *pool) +{ + ssize_t n; + + mutex_lock(&pool->stratum_lock); + do + { + if (pool->sock) + n = recv(pool->sock, pool->sockbuf, RECVSIZE, 0); + else + n = 0; + } + while (n > 0); + mutex_unlock(&pool->stratum_lock); + + clear_sockbuf(pool); +} + +/* Realloc memory to new size and zero any extra memory added */ +void _recalloc(void **ptr, size_t old, size_t new, const char *file, const char *func, const int line) +{ + if (new == old) + return; + *ptr = realloc(*ptr, new); + if (unlikely(!*ptr)) + quitfrom(1, file, func, line, "Failed to realloc"); + if (new > old) + memset(*ptr + old, 0, new - old); +} + +/* Make sure the pool sockbuf is large enough to cope with any coinbase size + * by reallocing it to a large enough size rounded up to a multiple of RBUFSIZE + * and zeroing the new memory */ +static void recalloc_sock(struct pool *pool, size_t len) +{ + size_t old, new; + + old = strlen(pool->sockbuf); + new = old + len + 1; + if (new < pool->sockbuf_size) + return; + new = new + (RBUFSIZE - (new % RBUFSIZE)); + // Avoid potentially recursive locking + // applog(LOG_DEBUG, "Recallocing pool sockbuf to %d", new); + pool->sockbuf = realloc(pool->sockbuf, new); + if (!pool->sockbuf) + quithere(1, "Failed to realloc pool sockbuf"); + memset(pool->sockbuf + old, 0, new - old); + pool->sockbuf_size = new; +} + +/* Peeks at a socket to find the first end of line and then reads just that + * from the socket and returns that as a malloced char */ +char *recv_line(struct pool *pool) +{ + char *tok, *sret = NULL; + ssize_t len, buflen; + int waited = 0; + + if (!strstr(pool->sockbuf, "\n")) + { + struct timeval rstart, now; + + cgtime(&rstart); + if (!socket_full(pool, DEFAULT_SOCKWAIT)) + { + applog(LOG_DEBUG, "Timed out waiting for data on socket_full"); + goto out; + } + + do + { + char s[RBUFSIZE]; + size_t slen; + ssize_t n; + + memset(s, 0, RBUFSIZE); + n = recv(pool->sock, s, RECVSIZE, 0); + if (!n) + { + applog(LOG_DEBUG, "Socket closed waiting in recv_line"); + suspend_stratum(pool); + break; + } + cgtime(&now); + waited = tdiff(&now, &rstart); + if (n < 0) + { + if (!sock_blocks() || !socket_full(pool, DEFAULT_SOCKWAIT - waited)) + { + applog(LOG_DEBUG, "Failed to recv sock in recv_line"); + suspend_stratum(pool); + break; + } + } + else + { + slen = strlen(s); + recalloc_sock(pool, slen); + strcat(pool->sockbuf, s); + } + } + while (waited < DEFAULT_SOCKWAIT && !strstr(pool->sockbuf, "\n")); + } + + buflen = strlen(pool->sockbuf); + tok = strtok(pool->sockbuf, "\n"); + if (!tok) + { + applog(LOG_DEBUG, "Failed to parse a \\n terminated string in recv_line"); + goto out; + } + sret = strdup(tok); + len = strlen(sret); + + /* Copy what's left in the buffer after the \n, including the + * terminating \0 */ + if (buflen > len + 1) + memmove(pool->sockbuf, pool->sockbuf + len + 1, buflen - len + 1); + else + strcpy(pool->sockbuf, ""); + + pool->cgminer_pool_stats.times_received++; + pool->cgminer_pool_stats.bytes_received += len; + pool->cgminer_pool_stats.net_bytes_received += len; +out: + if (!sret) + clear_sock(pool); + else if (opt_protocol) + applog(LOG_DEBUG, "RECVD: %s", sret); + return sret; +} + +/* Extracts a string value from a json array with error checking. To be used + * when the value of the string returned is only examined and not to be stored. + * See json_array_string below */ +static char *__json_array_string(json_t *val, unsigned int entry) +{ + json_t *arr_entry; + + if (json_is_null(val)) + return NULL; + if (!json_is_array(val)) + return NULL; + if (entry > json_array_size(val)) + return NULL; + arr_entry = json_array_get(val, entry); + if (!json_is_string(arr_entry)) + return NULL; + + return (char *)json_string_value(arr_entry); +} + +/* Creates a freshly malloced dup of __json_array_string */ +static char *json_array_string(json_t *val, unsigned int entry) +{ + char *buf = __json_array_string(val, entry); + + if (buf) + return strdup(buf); + return NULL; +} + +static char *blank_merkle = "0000000000000000000000000000000000000000000000000000000000000000"; + +static bool parse_notify(struct pool *pool, json_t *val) +{ + char *job_id, *prev_hash, *coinbase1, *coinbase2, *bbversion, *nbit, + *ntime, header[228]; + unsigned char *cb1 = NULL, *cb2 = NULL; + size_t cb1_len, cb2_len, alloc_len; + bool clean, ret = false; + int merkles, i; + json_t *arr; + + arr = json_array_get(val, 4); + if (!arr || !json_is_array(arr)) + goto out; + + merkles = json_array_size(arr); + + job_id = json_array_string(val, 0); + prev_hash = __json_array_string(val, 1); + coinbase1 = json_array_string(val, 2); + coinbase2 = json_array_string(val, 3); + bbversion = __json_array_string(val, 5); + nbit = __json_array_string(val, 6); + ntime = __json_array_string(val, 7); + clean = json_is_true(json_array_get(val, 8)); + + if (!valid_ascii(job_id) || !valid_hex(prev_hash) || !valid_hex(coinbase1) || + !valid_hex(coinbase2) || !valid_hex(bbversion) || !valid_hex(nbit) || + !valid_hex(ntime)) + { + /* Annoying but we must not leak memory */ + free(job_id); + free(coinbase1); + free(coinbase2); + goto out; + } + + cg_wlock(&pool->data_lock); + free(pool->swork.job_id); + pool->swork.job_id = job_id; + snprintf(pool->prev_hash, 65, "%s", prev_hash); + cb1_len = strlen(coinbase1) / 2; + cb2_len = strlen(coinbase2) / 2; + snprintf(pool->bbversion, 9, "%s", bbversion); + snprintf(pool->nbit, 9, "%s", nbit); + snprintf(pool->ntime, 9, "%s", ntime); + pool->swork.clean = clean; + alloc_len = pool->coinbase_len = cb1_len + pool->n1_len + pool->n2size + cb2_len; + pool->nonce2_offset = cb1_len + pool->n1_len; + + for (i = 0; i < pool->merkles; i++) + free(pool->swork.merkle_bin[i]); + if (merkles) + { + pool->swork.merkle_bin = realloc(pool->swork.merkle_bin, + sizeof(char *) * merkles + 1); + for (i = 0; i < merkles; i++) + { + char *merkle = json_array_string(arr, i); + + pool->swork.merkle_bin[i] = malloc(32); + if (unlikely(!pool->swork.merkle_bin[i])) + quit(1, "Failed to malloc pool swork merkle_bin"); + if (opt_protocol) + applog(LOG_DEBUG, "merkle %d: %s", i, merkle); + ret = hex2bin(pool->swork.merkle_bin[i], merkle, 32); + free(merkle); + if (unlikely(!ret)) + { + applog(LOG_ERR, "Failed to convert merkle to merkle_bin in parse_notify"); + goto out_unlock; + } + } + } + pool->merkles = merkles; + if (clean) + pool->nonce2 = 0; +#if 0 + header_len = strlen(pool->bbversion) + + strlen(pool->prev_hash); + /* merkle_hash */ 32 + + strlen(pool->ntime) + + strlen(pool->nbit) + + /* nonce */ 8 + + /* workpadding */ 96; +#endif + snprintf(header, 225, + "%s%s%s%s%s%s%s", + pool->bbversion, + pool->prev_hash, + blank_merkle, + pool->ntime, + pool->nbit, + "00000000", /* nonce */ + workpadding); + ret = hex2bin(pool->header_bin, header, 112); + if (unlikely(!ret)) + { + applog(LOG_ERR, "Failed to convert header to header_bin in parse_notify"); + goto out_unlock; + } + + cb1 = alloca(cb1_len); + ret = hex2bin(cb1, coinbase1, cb1_len); + if (unlikely(!ret)) + { + applog(LOG_ERR, "Failed to convert cb1 to cb1_bin in parse_notify"); + goto out_unlock; + } + cb2 = alloca(cb2_len); + ret = hex2bin(cb2, coinbase2, cb2_len); + if (unlikely(!ret)) + { + applog(LOG_ERR, "Failed to convert cb2 to cb2_bin in parse_notify"); + goto out_unlock; + } + free(pool->coinbase); + align_len(&alloc_len); + pool->coinbase = calloc(alloc_len, 1); + if (unlikely(!pool->coinbase)) + quit(1, "Failed to calloc pool coinbase in parse_notify"); + memcpy(pool->coinbase, cb1, cb1_len); + memcpy(pool->coinbase + cb1_len, pool->nonce1bin, pool->n1_len); + memcpy(pool->coinbase + cb1_len + pool->n1_len + pool->n2size, cb2, cb2_len); + if (opt_debug) + { + char *cb = bin2hex(pool->coinbase, pool->coinbase_len); + + applog(LOG_DEBUG, "Pool %d coinbase %s", pool->pool_no, cb); + free(cb); + } +out_unlock: + cg_wunlock(&pool->data_lock); + + if (opt_protocol) + { + applog(LOG_DEBUG, "job_id: %s", job_id); + applog(LOG_DEBUG, "prev_hash: %s", prev_hash); + applog(LOG_DEBUG, "coinbase1: %s", coinbase1); + applog(LOG_DEBUG, "coinbase2: %s", coinbase2); + applog(LOG_DEBUG, "bbversion: %s", bbversion); + applog(LOG_DEBUG, "nbit: %s", nbit); + applog(LOG_DEBUG, "ntime: %s", ntime); + applog(LOG_DEBUG, "clean: %s", clean ? "yes" : "no"); + } + free(coinbase1); + free(coinbase2); + + /* A notify message is the closest stratum gets to a getwork */ + pool->getwork_requested++; + total_getworks++; + if (pool == current_pool()) + opt_work_update = true; +out: + return ret; +} + +static bool parse_version(struct pool *pool, json_t *val) +{ + int i; + for(i = 0; i < json_array_size(val); i++) + { + pool->version[i] = json_integer_value(json_array_get(val, i)); + } +} + +static bool parse_diff(struct pool *pool, json_t *val) +{ + double old_diff, diff; + + diff = json_number_value(json_array_get(val, 0)); + if (diff == 0) + return false; + + cg_wlock(&pool->data_lock); + old_diff = pool->sdiff; + pool->sdiff = diff; + cg_wunlock(&pool->data_lock); + + if (old_diff != diff) + { + int idiff = diff; + + if ((double)idiff == diff) + applog(LOG_NOTICE, "Pool %d difficulty changed to %d", + pool->pool_no, idiff); + else + applog(LOG_NOTICE, "Pool %d difficulty changed to %.1f", + pool->pool_no, diff); + } + else + applog(LOG_DEBUG, "Pool %d difficulty set to %f", pool->pool_no, + diff); + + return true; +} + +static bool parse_extranonce(struct pool *pool, json_t *val) +{ + int n2size; + char* nonce1; + + nonce1 = json_array_string(val, 0); + if (!valid_hex(nonce1)) + { + applog(LOG_INFO, "Failed to get valid nonce1 in parse_extranonce"); + goto out; + } + n2size = json_integer_value(json_array_get(val, 1)); + if (n2size < 2 || n2size > 16) + { + applog(LOG_INFO, "Failed to get valid n2size in parse_extranonce"); + free(nonce1); + goto out; + } + + cg_wlock(&pool->data_lock); + pool->nonce1 = nonce1; + pool->n1_len = strlen(nonce1) / 2; + free(pool->nonce1bin); + pool->nonce1bin = calloc(pool->n1_len, 1); + if (unlikely(!pool->nonce1bin)) + quithere(1, "Failed to calloc pool->nonce1bin"); + hex2bin(pool->nonce1bin, pool->nonce1, pool->n1_len); + pool->n2size = n2size; + applog(LOG_NOTICE, "Pool %d confirmed mining.extranonce.subscribe with extranonce1 %s extran2size %d", + pool->pool_no, pool->nonce1, pool->n2size); + cg_wunlock(&pool->data_lock); + return true; +out: + return false; +} + +static void __suspend_stratum(struct pool *pool) +{ + clear_sockbuf(pool); + pool->stratum_active = pool->stratum_notify = false; + if (pool->sock) + CLOSESOCKET(pool->sock); + pool->sock = 0; +} + +static bool parse_reconnect(struct pool *pool, json_t *val) +{ + char *sockaddr_url, *stratum_port, *tmp; + char *url, *port, address[256]; + + memset(address, 0, 255); + url = (char *)json_string_value(json_array_get(val, 0)); + if (!url) + url = pool->sockaddr_url; + else + { + char *dot_pool, *dot_reconnect; + dot_pool = strchr(pool->sockaddr_url, '.'); + if (!dot_pool) + { + applog(LOG_ERR, "Denied stratum reconnect request for pool without domain '%s'", + pool->sockaddr_url); + return false; + } + dot_reconnect = strchr(url, '.'); + if (!dot_reconnect) + { + applog(LOG_ERR, "Denied stratum reconnect request to url without domain '%s'", + url); + return false; + } + if (strcmp(dot_pool, dot_reconnect)) + { + applog(LOG_ERR, "Denied stratum reconnect request to non-matching domain url '%s'", + pool->sockaddr_url); + return false; + } + } + + port = (char *)json_string_value(json_array_get(val, 1)); + if (!port) + port = pool->stratum_port; + + snprintf(address, 254, "%s:%s", url, port); + + if (!extract_sockaddr(address, &sockaddr_url, &stratum_port)) + return false; + + applog(LOG_WARNING, "Stratum reconnect requested from pool %d to %s", pool->pool_no, address); + + clear_pool_work(pool); + + mutex_lock(&pool->stratum_lock); + __suspend_stratum(pool); + tmp = pool->sockaddr_url; + pool->sockaddr_url = sockaddr_url; + pool->stratum_url = pool->sockaddr_url; + free(tmp); + tmp = pool->stratum_port; + pool->stratum_port = stratum_port; + free(tmp); + mutex_unlock(&pool->stratum_lock); + + return restart_stratum(pool); +} + +static bool send_version(struct pool *pool, json_t *val) +{ + json_t *id_val = json_object_get(val, "id"); + char s[RBUFSIZE]; + int id; + + if (!id_val) + return false; + id = json_integer_value(json_object_get(val, "id")); + + sprintf(s, "{\"id\": %d, \"result\": \""PACKAGE"/"VERSION"\", \"error\": null}", id); + if (!stratum_send(pool, s, strlen(s))) + return false; + + return true; +} + +static bool send_pong(struct pool *pool, json_t *val) +{ + json_t *id_val = json_object_get(val, "id"); + char s[RBUFSIZE]; + int id; + + if (!id_val) + return false; + id = json_integer_value(json_object_get(val, "id")); + + sprintf(s, "{\"id\": %d, \"result\": \"pong\", \"error\": null}", id); + if (!stratum_send(pool, s, strlen(s))) + return false; + + return true; +} + +static bool show_message(struct pool *pool, json_t *val) +{ + char *msg; + + if (!json_is_array(val)) + return false; + msg = (char *)json_string_value(json_array_get(val, 0)); + if (!msg) + return false; + applog(LOG_NOTICE, "Pool %d message: %s", pool->pool_no, msg); + return true; +} + +bool parse_method(struct pool *pool, char *s) +{ + json_t *val = NULL, *method, *err_val, *params; + json_error_t err; + bool ret = false; + char *buf; + + if (!s) + goto out; + + val = JSON_LOADS(s, &err); + if (!val) + { + applog(LOG_INFO, "JSON decode failed(%d): %s", err.line, err.text); + goto out; + } + + method = json_object_get(val, "method"); + if (!method) + goto out_decref; + err_val = json_object_get(val, "error"); + params = json_object_get(val, "params"); + + if (err_val && !json_is_null(err_val)) + { + char *ss; + + if (err_val) + ss = json_dumps(err_val, JSON_INDENT(3)); + else + ss = strdup("(unknown reason)"); + + applog(LOG_INFO, "JSON-RPC method decode failed: %s", ss); + free(ss); + goto out_decref; + } + + buf = (char *)json_string_value(method); + if (!buf) + goto out_decref; + + if (!strncasecmp(buf, "mining.multi_version", 20)) + { + pool->support_vil = true; + applog(LOG_INFO,"Pool support multi version"); + ret = parse_version(pool, params); + goto out_decref; + } + + if (!strncasecmp(buf, "mining.notify", 13)) + { + if (parse_notify(pool, params)) + pool->stratum_notify = ret = true; + else + pool->stratum_notify = ret = false; + goto out_decref; + } + + + if(!strncasecmp(buf, "mining.set_extranonce", 21)) + { + ret = parse_extranonce(pool, params); + goto out_decref; + } + + if (!strncasecmp(buf, "mining.set_difficulty", 21)) + { + ret = parse_diff(pool, params); + goto out_decref; + } + + if (!strncasecmp(buf, "client.reconnect", 16)) + { + ret = parse_reconnect(pool, params); + goto out_decref; + } + + if (!strncasecmp(buf, "client.get_version", 18)) + { + ret = send_version(pool, val); + goto out_decref; + } + + if (!strncasecmp(buf, "client.show_message", 19)) + { + ret = show_message(pool, params); + goto out_decref; + } + + if (!strncasecmp(buf, "mining.ping", 11)) + { + applog(LOG_INFO, "Pool %d ping", pool->pool_no); + ret = send_pong(pool, val); + goto out_decref; + } +out_decref: + json_decref(val); +out: + return ret; +} + +bool auth_stratum(struct pool *pool) +{ + json_t *val = NULL, *res_val, *err_val; + char s[RBUFSIZE], *sret = NULL; + json_error_t err; + bool ret = false; + + sprintf(s, "{\"id\": %d, \"method\": \"mining.authorize\", \"params\": [\"%s\", \"%s\"]}", + swork_id++, pool->rpc_user, pool->rpc_pass); + + if (!stratum_send(pool, s, strlen(s))) + return ret; + + /* Parse all data in the queue and anything left should be auth */ + while (42) + { + sret = recv_line(pool); + if (!sret) + return ret; + if (parse_method(pool, sret)) + free(sret); + else + break; + } + + val = JSON_LOADS(sret, &err); + free(sret); + res_val = json_object_get(val, "result"); + err_val = json_object_get(val, "error"); + + if (!res_val || json_is_false(res_val) || (err_val && !json_is_null(err_val))) + { + char *ss; + + if (err_val) + ss = json_dumps(err_val, JSON_INDENT(3)); + else + ss = strdup("(unknown reason)"); + applog(LOG_INFO, "pool %d JSON stratum auth failed: %s", pool->pool_no, ss); + free(ss); + + suspend_stratum(pool); + + goto out; + } + + ret = true; + applog(LOG_INFO, "Stratum authorisation success for pool %d", pool->pool_no); + pool->probed = true; + successful_connect = true; + if (opt_suggest_diff) + { + sprintf(s, "{\"id\": %d, \"method\": \"mining.suggest_difficulty\", \"params\": [%d]}", + swork_id++, opt_suggest_diff); + stratum_send(pool, s, strlen(s)); + } + if (opt_multi_version) + { + sprintf(s, "{\"id\": %d, \"method\": \"mining.multi_version\", \"params\": [%d]}", + swork_id++, opt_multi_version); + stratum_send(pool, s, strlen(s)); + } +out: + json_decref(val); + return ret; +} + +static int recv_byte(int sockd) +{ + char c; + + if (recv(sockd, &c, 1, 0) != -1) + return c; + + return -1; +} + +static bool http_negotiate(struct pool *pool, int sockd, bool http0) +{ + char buf[1024]; + int i, len; + + if (http0) + { + snprintf(buf, 1024, "CONNECT %s:%s HTTP/1.0\r\n\r\n", + pool->sockaddr_url, pool->stratum_port); + } + else + { + snprintf(buf, 1024, "CONNECT %s:%s HTTP/1.1\r\nHost: %s:%s\r\n\r\n", + pool->sockaddr_url, pool->stratum_port, pool->sockaddr_url, + pool->stratum_port); + } + applog(LOG_DEBUG, "Sending proxy %s:%s - %s", + pool->sockaddr_proxy_url, pool->sockaddr_proxy_port, buf); + send(sockd, buf, strlen(buf), 0); + len = recv(sockd, buf, 12, 0); + if (len <= 0) + { + applog(LOG_WARNING, "Couldn't read from proxy %s:%s after sending CONNECT", + pool->sockaddr_proxy_url, pool->sockaddr_proxy_port); + return false; + } + buf[len] = '\0'; + applog(LOG_DEBUG, "Received from proxy %s:%s - %s", + pool->sockaddr_proxy_url, pool->sockaddr_proxy_port, buf); + if (strcmp(buf, "HTTP/1.1 200") && strcmp(buf, "HTTP/1.0 200")) + { + applog(LOG_WARNING, "HTTP Error from proxy %s:%s - %s", + pool->sockaddr_proxy_url, pool->sockaddr_proxy_port, buf); + return false; + } + + /* Ignore unwanted headers till we get desired response */ + for (i = 0; i < 4; i++) + { + buf[i] = recv_byte(sockd); + if (buf[i] == (char)-1) + { + applog(LOG_WARNING, "Couldn't read HTTP byte from proxy %s:%s", + pool->sockaddr_proxy_url, pool->sockaddr_proxy_port); + return false; + } + } + while (strncmp(buf, "\r\n\r\n", 4)) + { + for (i = 0; i < 3; i++) + buf[i] = buf[i + 1]; + buf[3] = recv_byte(sockd); + if (buf[3] == (char)-1) + { + applog(LOG_WARNING, "Couldn't read HTTP byte from proxy %s:%s", + pool->sockaddr_proxy_url, pool->sockaddr_proxy_port); + return false; + } + } + + applog(LOG_DEBUG, "Success negotiating with %s:%s HTTP proxy", + pool->sockaddr_proxy_url, pool->sockaddr_proxy_port); + return true; +} + +static bool socks5_negotiate(struct pool *pool, int sockd) +{ + unsigned char atyp, uclen; + unsigned short port; + char buf[515]; + int i, len; + + buf[0] = 0x05; + buf[1] = 0x01; + buf[2] = 0x00; + applog(LOG_DEBUG, "Attempting to negotiate with %s:%s SOCKS5 proxy", + pool->sockaddr_proxy_url, pool->sockaddr_proxy_port ); + send(sockd, buf, 3, 0); + if (recv_byte(sockd) != 0x05 || recv_byte(sockd) != buf[2]) + { + applog(LOG_WARNING, "Bad response from %s:%s SOCKS5 server", + pool->sockaddr_proxy_url, pool->sockaddr_proxy_port ); + return false; + } + + buf[0] = 0x05; + buf[1] = 0x01; + buf[2] = 0x00; + buf[3] = 0x03; + len = (strlen(pool->sockaddr_url)); + if (len > 255) + len = 255; + uclen = len; + buf[4] = (uclen & 0xff); + memcpy(buf + 5, pool->sockaddr_url, len); + port = atoi(pool->stratum_port); + buf[5 + len] = (port >> 8); + buf[6 + len] = (port & 0xff); + send(sockd, buf, (7 + len), 0); + if (recv_byte(sockd) != 0x05 || recv_byte(sockd) != 0x00) + { + applog(LOG_WARNING, "Bad response from %s:%s SOCKS5 server", + pool->sockaddr_proxy_url, pool->sockaddr_proxy_port ); + return false; + } + + recv_byte(sockd); + atyp = recv_byte(sockd); + if (atyp == 0x01) + { + for (i = 0; i < 4; i++) + recv_byte(sockd); + } + else if (atyp == 0x03) + { + len = recv_byte(sockd); + for (i = 0; i < len; i++) + recv_byte(sockd); + } + else + { + applog(LOG_WARNING, "Bad response from %s:%s SOCKS5 server", + pool->sockaddr_proxy_url, pool->sockaddr_proxy_port ); + return false; + } + for (i = 0; i < 2; i++) + recv_byte(sockd); + + applog(LOG_DEBUG, "Success negotiating with %s:%s SOCKS5 proxy", + pool->sockaddr_proxy_url, pool->sockaddr_proxy_port); + return true; +} + +static bool socks4_negotiate(struct pool *pool, int sockd, bool socks4a) +{ + unsigned short port; + in_addr_t inp; + char buf[515]; + int i, len; + + buf[0] = 0x04; + buf[1] = 0x01; + port = atoi(pool->stratum_port); + buf[2] = port >> 8; + buf[3] = port & 0xff; + sprintf(&buf[8], "CGMINER"); + + /* See if we've been given an IP address directly to avoid needing to + * resolve it. */ + inp = inet_addr(pool->sockaddr_url); + inp = ntohl(inp); + if ((int)inp != -1) + socks4a = false; + else + { + /* Try to extract the IP address ourselves first */ + struct addrinfo servinfobase, *servinfo, hints; + + servinfo = &servinfobase; + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_INET; /* IPV4 only */ + if (!getaddrinfo(pool->sockaddr_url, NULL, &hints, &servinfo)) + { + struct sockaddr_in *saddr_in = (struct sockaddr_in *)servinfo->ai_addr; + + inp = ntohl(saddr_in->sin_addr.s_addr); + socks4a = false; + freeaddrinfo(servinfo); + } + } + + if (!socks4a) + { + if ((int)inp == -1) + { + applog(LOG_WARNING, "Invalid IP address specified for socks4 proxy: %s", + pool->sockaddr_url); + return false; + } + buf[4] = (inp >> 24) & 0xFF; + buf[5] = (inp >> 16) & 0xFF; + buf[6] = (inp >> 8) & 0xFF; + buf[7] = (inp >> 0) & 0xFF; + send(sockd, buf, 16, 0); + } + else + { + /* This appears to not be working but hopefully most will be + * able to resolve IP addresses themselves. */ + buf[4] = 0; + buf[5] = 0; + buf[6] = 0; + buf[7] = 1; + len = strlen(pool->sockaddr_url); + if (len > 255) + len = 255; + memcpy(&buf[16], pool->sockaddr_url, len); + len += 16; + buf[len++] = '\0'; + send(sockd, buf, len, 0); + } + + if (recv_byte(sockd) != 0x00 || recv_byte(sockd) != 0x5a) + { + applog(LOG_WARNING, "Bad response from %s:%s SOCKS4 server", + pool->sockaddr_proxy_url, pool->sockaddr_proxy_port); + return false; + } + + for (i = 0; i < 6; i++) + recv_byte(sockd); + + return true; +} + +static void noblock_socket(SOCKETTYPE fd) +{ +#ifndef WIN32 + int flags = fcntl(fd, F_GETFL, 0); + + fcntl(fd, F_SETFL, O_NONBLOCK | flags); +#else + u_long flags = 1; + + ioctlsocket(fd, FIONBIO, &flags); +#endif +} + +static void block_socket(SOCKETTYPE fd) +{ +#ifndef WIN32 + int flags = fcntl(fd, F_GETFL, 0); + + fcntl(fd, F_SETFL, flags & ~O_NONBLOCK); +#else + u_long flags = 0; + + ioctlsocket(fd, FIONBIO, &flags); +#endif +} + +static bool sock_connecting(void) +{ +#ifndef WIN32 + return errno == EINPROGRESS; +#else + return WSAGetLastError() == WSAEWOULDBLOCK; +#endif +} +static bool setup_stratum_socket(struct pool *pool) +{ + struct addrinfo *servinfo, hints, *p; + char *sockaddr_url, *sockaddr_port; + int sockd; + + mutex_lock(&pool->stratum_lock); + pool->stratum_active = false; + if (pool->sock) + CLOSESOCKET(pool->sock); + pool->sock = 0; + mutex_unlock(&pool->stratum_lock); + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + if (!pool->rpc_proxy && opt_socks_proxy) + { + pool->rpc_proxy = opt_socks_proxy; + extract_sockaddr(pool->rpc_proxy, &pool->sockaddr_proxy_url, &pool->sockaddr_proxy_port); + pool->rpc_proxytype = PROXY_SOCKS5; + } + + if (pool->rpc_proxy) + { + sockaddr_url = pool->sockaddr_proxy_url; + sockaddr_port = pool->sockaddr_proxy_port; + } + else + { + sockaddr_url = pool->sockaddr_url; + sockaddr_port = pool->stratum_port; + } + if (getaddrinfo(sockaddr_url, sockaddr_port, &hints, &servinfo) != 0) + { + if (!pool->probed) + { + applog(LOG_WARNING, "Failed to resolve (?wrong URL) %s:%s", + sockaddr_url, sockaddr_port); + pool->probed = true; + } + else + { + applog(LOG_INFO, "Failed to getaddrinfo for %s:%s", + sockaddr_url, sockaddr_port); + } + return false; + } + + for (p = servinfo; p != NULL; p = p->ai_next) + { + sockd = socket(p->ai_family, p->ai_socktype, p->ai_protocol); + if (sockd == -1) + { + applog(LOG_DEBUG, "Failed socket"); + continue; + } + + /* Iterate non blocking over entries returned by getaddrinfo + * to cope with round robin DNS entries, finding the first one + * we can connect to quickly. */ + noblock_socket(sockd); + if (connect(sockd, p->ai_addr, p->ai_addrlen) == -1) + { + struct timeval tv_timeout = {1, 0}; + int selret; + fd_set rw; + + if (!sock_connecting()) + { + CLOSESOCKET(sockd); + applog(LOG_DEBUG, "Failed sock connect"); + continue; + } + retry: + FD_ZERO(&rw); + FD_SET(sockd, &rw); + selret = select(sockd + 1, NULL, &rw, NULL, &tv_timeout); + if (selret > 0 && FD_ISSET(sockd, &rw)) + { + socklen_t len; + int err, n; + + len = sizeof(err); + n = getsockopt(sockd, SOL_SOCKET, SO_ERROR, (void *)&err, &len); + if (!n && !err) + { + applog(LOG_DEBUG, "Succeeded delayed connect"); + block_socket(sockd); + break; + } + } + if (selret < 0 && interrupted()) + goto retry; + CLOSESOCKET(sockd); + applog(LOG_DEBUG, "Select timeout/failed connect"); + continue; + } + applog(LOG_WARNING, "Succeeded immediate connect"); + block_socket(sockd); + + break; + } + if (p == NULL) + { + applog(LOG_INFO, "Failed to connect to stratum on %s:%s", + sockaddr_url, sockaddr_port); + freeaddrinfo(servinfo); + return false; + } + freeaddrinfo(servinfo); + + if (pool->rpc_proxy) + { + switch (pool->rpc_proxytype) + { + case PROXY_HTTP_1_0: + if (!http_negotiate(pool, sockd, true)) + return false; + break; + case PROXY_HTTP: + if (!http_negotiate(pool, sockd, false)) + return false; + break; + case PROXY_SOCKS5: + case PROXY_SOCKS5H: + if (!socks5_negotiate(pool, sockd)) + return false; + break; + case PROXY_SOCKS4: + if (!socks4_negotiate(pool, sockd, false)) + return false; + break; + case PROXY_SOCKS4A: + if (!socks4_negotiate(pool, sockd, true)) + return false; + break; + default: + applog(LOG_WARNING, "Unsupported proxy type for %s:%s", + pool->sockaddr_proxy_url, pool->sockaddr_proxy_port); + return false; + break; + } + } + + if (!pool->sockbuf) + { + pool->sockbuf = calloc(RBUFSIZE, 1); + if (!pool->sockbuf) + quithere(1, "Failed to calloc pool sockbuf"); + pool->sockbuf_size = RBUFSIZE; + } + + pool->sock = sockd; + keep_sockalive(sockd); + return true; +} + +static char *get_sessionid(json_t *val) +{ + char *ret = NULL; + json_t *arr_val; + int arrsize, i; + + arr_val = json_array_get(val, 0); + if (!arr_val || !json_is_array(arr_val)) + goto out; + arrsize = json_array_size(arr_val); + for (i = 0; i < arrsize; i++) + { + json_t *arr = json_array_get(arr_val, i); + char *notify; + + if (!arr | !json_is_array(arr)) + break; + notify = __json_array_string(arr, 0); + if (!notify) + continue; + if (!strncasecmp(notify, "mining.notify", 13)) + { + ret = json_array_string(arr, 1); + break; + } + } +out: + return ret; +} + +void suspend_stratum(struct pool *pool) +{ + applog(LOG_INFO, "Closing socket for stratum pool %d", pool->pool_no); + + mutex_lock(&pool->stratum_lock); + __suspend_stratum(pool); + mutex_unlock(&pool->stratum_lock); +} + +void extranonce_subscribe_stratum(struct pool *pool) +{ + if(pool->extranonce_subscribe) + { + char s[RBUFSIZE]; + sprintf(s,"{\"id\": %d, \"method\": \"mining.extranonce.subscribe\", \"params\": []}", swork_id++); + applog(LOG_INFO, "Send extranonce.subscribe for stratum pool %d", pool->pool_no); + stratum_send(pool, s, strlen(s)); + } +} + + +bool initiate_stratum(struct pool *pool) +{ + bool ret = false, recvd = false, noresume = false, sockd = false; + char s[RBUFSIZE], *sret = NULL, *nonce1, *sessionid; + json_t *val = NULL, *res_val, *err_val; + json_error_t err; + int n2size; + +resend: + if (!setup_stratum_socket(pool)) + { + sockd = false; + goto out; + } + + sockd = true; + + if (recvd) + { + /* Get rid of any crap lying around if we're resending */ + clear_sock(pool); + sprintf(s, "{\"id\": %d, \"method\": \"mining.subscribe\", \"params\": []}", swork_id++); + } + else + { + if (pool->sessionid) + sprintf(s, "{\"id\": %d, \"method\": \"mining.subscribe\", \"params\": [\""PACKAGE"/"VERSION"\", \"%s\"]}", swork_id++, pool->sessionid); + else + sprintf(s, "{\"id\": %d, \"method\": \"mining.subscribe\", \"params\": [\""PACKAGE"/"VERSION"\"]}", swork_id++); + } + + if (__stratum_send(pool, s, strlen(s)) != SEND_OK) + { + applog(LOG_DEBUG, "Failed to send s in initiate_stratum"); + goto out; + } + + if (!socket_full(pool, DEFAULT_SOCKWAIT)) + { + applog(LOG_DEBUG, "Timed out waiting for response in initiate_stratum"); + goto out; + } + + sret = recv_line(pool); + if (!sret) + goto out; + + recvd = true; + + val = JSON_LOADS(sret, &err); + free(sret); + if (!val) + { + applog(LOG_INFO, "JSON decode failed(%d): %s", err.line, err.text); + goto out; + } + + res_val = json_object_get(val, "result"); + err_val = json_object_get(val, "error"); + + if (!res_val || json_is_null(res_val) || + (err_val && !json_is_null(err_val))) + { + char *ss; + + if (err_val) + ss = json_dumps(err_val, JSON_INDENT(3)); + else + ss = strdup("(unknown reason)"); + + applog(LOG_INFO, "JSON-RPC decode failed: %s", ss); + + free(ss); + + goto out; + } + + sessionid = get_sessionid(res_val); + if (!sessionid) + applog(LOG_DEBUG, "Failed to get sessionid in initiate_stratum"); + nonce1 = json_array_string(res_val, 1); + if (!valid_hex(nonce1)) + { + applog(LOG_INFO, "Failed to get valid nonce1 in initiate_stratum"); + free(sessionid); + goto out; + } + n2size = json_integer_value(json_array_get(res_val, 2)); + if (n2size < 2 || n2size > 16) + { + applog(LOG_INFO, "Failed to get valid n2size in initiate_stratum"); + free(sessionid); + free(nonce1); + goto out; + } + + cg_wlock(&pool->data_lock); + pool->sessionid = sessionid; + pool->nonce1 = nonce1; + pool->n1_len = strlen(nonce1) / 2; + free(pool->nonce1bin); + pool->nonce1bin = calloc(pool->n1_len, 1); + if (unlikely(!pool->nonce1bin)) + quithere(1, "Failed to calloc pool->nonce1bin"); + hex2bin(pool->nonce1bin, pool->nonce1, pool->n1_len); + pool->n2size = n2size; + cg_wunlock(&pool->data_lock); + + if (sessionid) + applog(LOG_DEBUG, "Pool %d stratum session id: %s", pool->pool_no, pool->sessionid); + + ret = true; +out: + if (ret) + { + if (!pool->stratum_url) + pool->stratum_url = pool->sockaddr_url; + pool->stratum_active = true; + pool->sdiff = 1; + if (opt_protocol) + { + applog(LOG_DEBUG, "Pool %d confirmed mining.subscribe with extranonce1 %s extran2size %d", + pool->pool_no, pool->nonce1, pool->n2size); + } + if(pool->extranonce_subscribe) + { + sprintf(s,"{\"id\": %d, \"method\": \"mining.extranonce.subscribe\", \"params\": []}", swork_id++); + stratum_send(pool, s, strlen(s)); + } + } + else + { + if (recvd && !noresume) + { + /* Reset the sessionid used for stratum resuming in case the pool + * does not support it, or does not know how to respond to the + * presence of the sessionid parameter. */ + cg_wlock(&pool->data_lock); + free(pool->sessionid); + free(pool->nonce1); + pool->sessionid = pool->nonce1 = NULL; + cg_wunlock(&pool->data_lock); + + applog(LOG_DEBUG, "Failed to resume stratum, trying afresh"); + noresume = true; + json_decref(val); + goto resend; + } + applog(LOG_DEBUG, "Initiate stratum failed"); + if (sockd) + suspend_stratum(pool); + } + + json_decref(val); + return ret; +} + +bool restart_stratum(struct pool *pool) +{ + bool ret = false; + + if (pool->stratum_active) + suspend_stratum(pool); + if (!initiate_stratum(pool)) + goto out; + if (!auth_stratum(pool)) + goto out; + extranonce_subscribe_stratum(pool); + ret = true; +out: + if (!ret) + pool_died(pool); + else + stratum_resumed(pool); + return ret; +} + +void dev_error(struct cgpu_info *dev, enum dev_reason reason) +{ + dev->device_last_not_well = time(NULL); + dev->device_not_well_reason = reason; + + switch (reason) + { + case REASON_THREAD_FAIL_INIT: + dev->thread_fail_init_count++; + break; + case REASON_THREAD_ZERO_HASH: + dev->thread_zero_hash_count++; + break; + case REASON_THREAD_FAIL_QUEUE: + dev->thread_fail_queue_count++; + break; + case REASON_DEV_SICK_IDLE_60: + dev->dev_sick_idle_60_count++; + break; + case REASON_DEV_DEAD_IDLE_600: + dev->dev_dead_idle_600_count++; + break; + case REASON_DEV_NOSTART: + dev->dev_nostart_count++; + break; + case REASON_DEV_OVER_HEAT: + dev->dev_over_heat_count++; + break; + case REASON_DEV_THERMAL_CUTOFF: + dev->dev_thermal_cutoff_count++; + break; + case REASON_DEV_COMMS_ERROR: + dev->dev_comms_error_count++; + break; + case REASON_DEV_THROTTLE: + dev->dev_throttle_count++; + break; + } +} + +/* Realloc an existing string to fit an extra string s, appending s to it. */ +void *realloc_strcat(char *ptr, char *s) +{ + size_t old = 0, len = strlen(s); + char *ret; + + if (!len) + return ptr; + if (ptr) + old = strlen(ptr); + + len += old + 1; + align_len(&len); + + ret = malloc(len); + if (unlikely(!ret)) + quithere(1, "Failed to malloc"); + + if (ptr) + { + sprintf(ret, "%s%s", ptr, s); + free(ptr); + } + else + sprintf(ret, "%s", s); + return ret; +} + +/* Make a text readable version of a string using 0xNN for < ' ' or > '~' + * Including 0x00 at the end + * You must free the result yourself */ +void *str_text(char *ptr) +{ + unsigned char *uptr; + char *ret, *txt; + + if (ptr == NULL) + { + ret = strdup("(null)"); + + if (unlikely(!ret)) + quithere(1, "Failed to malloc null"); + } + + uptr = (unsigned char *)ptr; + + ret = txt = malloc(strlen(ptr)*4+5); // Guaranteed >= needed + if (unlikely(!txt)) + quithere(1, "Failed to malloc txt"); + + do + { + if (*uptr < ' ' || *uptr > '~') + { + sprintf(txt, "0x%02x", *uptr); + txt += 4; + } + else + *(txt++) = *uptr; + } + while (*(uptr++)); + + *txt = '\0'; + + return ret; +} + +void RenameThread(const char* name) +{ + char buf[16]; + + snprintf(buf, sizeof(buf), "cg@%s", name); +#if defined(PR_SET_NAME) + // Only the first 15 characters are used (16 - NUL terminator) + prctl(PR_SET_NAME, buf, 0, 0, 0); +#elif (defined(__FreeBSD__) || defined(__OpenBSD__)) + pthread_set_name_np(pthread_self(), buf); +#elif defined(MAC_OSX) + pthread_setname_np(buf); +#else + // Prevent warnings + (void)buf; +#endif +} + +/* cgminer specific wrappers for true unnamed semaphore usage on platforms + * that support them and for apple which does not. We use a single byte across + * a pipe to emulate semaphore behaviour there. */ +#ifdef __APPLE__ +void _cgsem_init(cgsem_t *cgsem, const char *file, const char *func, const int line) +{ + int flags, fd, i; + + if (pipe(cgsem->pipefd) == -1) + quitfrom(1, file, func, line, "Failed pipe errno=%d", errno); + + /* Make the pipes FD_CLOEXEC to allow them to close should we call + * execv on restart. */ + for (i = 0; i < 2; i++) + { + fd = cgsem->pipefd[i]; + flags = fcntl(fd, F_GETFD, 0); + flags |= FD_CLOEXEC; + if (fcntl(fd, F_SETFD, flags) == -1) + quitfrom(1, file, func, line, "Failed to fcntl errno=%d", errno); + } +} + +void _cgsem_post(cgsem_t *cgsem, const char *file, const char *func, const int line) +{ + const char buf = 1; + int ret; + +retry: + ret = write(cgsem->pipefd[1], &buf, 1); + if (unlikely(ret == 0)) + applog(LOG_WARNING, "Failed to write errno=%d" IN_FMT_FFL, errno, file, func, line); + else if (unlikely(ret < 0 && interrupted)) + goto retry; +} + +void _cgsem_wait(cgsem_t *cgsem, const char *file, const char *func, const int line) +{ + char buf; + int ret; +retry: + ret = read(cgsem->pipefd[0], &buf, 1); + if (unlikely(ret == 0)) + applog(LOG_WARNING, "Failed to read errno=%d" IN_FMT_FFL, errno, file, func, line); + else if (unlikely(ret < 0 && interrupted)) + goto retry; +} + +void cgsem_destroy(cgsem_t *cgsem) +{ + close(cgsem->pipefd[1]); + close(cgsem->pipefd[0]); +} + +/* This is similar to sem_timedwait but takes a millisecond value */ +int _cgsem_mswait(cgsem_t *cgsem, int ms, const char *file, const char *func, const int line) +{ + struct timeval timeout; + int ret, fd; + fd_set rd; + char buf; + +retry: + fd = cgsem->pipefd[0]; + FD_ZERO(&rd); + FD_SET(fd, &rd); + ms_to_timeval(&timeout, ms); + ret = select(fd + 1, &rd, NULL, NULL, &timeout); + + if (ret > 0) + { + ret = read(fd, &buf, 1); + return 0; + } + if (likely(!ret)) + return ETIMEDOUT; + if (interrupted()) + goto retry; + quitfrom(1, file, func, line, "Failed to sem_timedwait errno=%d cgsem=0x%p", errno, cgsem); + /* We don't reach here */ + return 0; +} + +/* Reset semaphore count back to zero */ +void cgsem_reset(cgsem_t *cgsem) +{ + int ret, fd; + fd_set rd; + char buf; + + fd = cgsem->pipefd[0]; + FD_ZERO(&rd); + FD_SET(fd, &rd); + do + { + struct timeval timeout = {0, 0}; + + ret = select(fd + 1, &rd, NULL, NULL, &timeout); + if (ret > 0) + ret = read(fd, &buf, 1); + else if (unlikely(ret < 0 && interrupted())) + ret = 1; + } + while (ret > 0); +} +#else +void _cgsem_init(cgsem_t *cgsem, const char *file, const char *func, const int line) +{ + int ret; + if ((ret = sem_init(cgsem, 0, 0))) + quitfrom(1, file, func, line, "Failed to sem_init ret=%d errno=%d", ret, errno); +} + +void _cgsem_post(cgsem_t *cgsem, const char *file, const char *func, const int line) +{ + if (unlikely(sem_post(cgsem))) + quitfrom(1, file, func, line, "Failed to sem_post errno=%d cgsem=0x%p", errno, cgsem); +} + +void _cgsem_wait(cgsem_t *cgsem, const char *file, const char *func, const int line) +{ +retry: + if (unlikely(sem_wait(cgsem))) + { + if (interrupted()) + goto retry; + quitfrom(1, file, func, line, "Failed to sem_wait errno=%d cgsem=0x%p", errno, cgsem); + } +} + +int _cgsem_mswait(cgsem_t *cgsem, int ms, const char *file, const char *func, const int line) +{ + struct timespec abs_timeout, ts_now; + struct timeval tv_now; + int ret; + + cgtime(&tv_now); + timeval_to_spec(&ts_now, &tv_now); + ms_to_timespec(&abs_timeout, ms); +retry: + timeraddspec(&abs_timeout, &ts_now); + ret = sem_timedwait(cgsem, &abs_timeout); + + if (ret) + { + if (likely(sock_timeout())) + return ETIMEDOUT; + if (interrupted()) + goto retry; + quitfrom(1, file, func, line, "Failed to sem_timedwait errno=%d cgsem=0x%p", errno, cgsem); + } + return 0; +} + +void cgsem_reset(cgsem_t *cgsem) +{ + int ret; + + do + { + ret = sem_trywait(cgsem); + if (unlikely(ret < 0 && interrupted())) + ret = 0; + } + while (!ret); +} + +void cgsem_destroy(cgsem_t *cgsem) +{ + sem_destroy(cgsem); +} +#endif + +/* Provide a completion_timeout helper function for unreliable functions that + * may die due to driver issues etc that time out if the function fails and + * can then reliably return. */ +struct cg_completion +{ + cgsem_t cgsem; + void (*fn)(void *fnarg); + void *fnarg; +}; + +void *completion_thread(void *arg) +{ + struct cg_completion *cgc = (struct cg_completion *)arg; + + pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); + cgc->fn(cgc->fnarg); + cgsem_post(&cgc->cgsem); + + return NULL; +} + +bool cg_completion_timeout(void *fn, void *fnarg, int timeout) +{ + struct cg_completion *cgc; + pthread_t pthread; + bool ret = false; + + cgc = malloc(sizeof(struct cg_completion)); + if (unlikely(!cgc)) + return ret; + cgsem_init(&cgc->cgsem); + cgc->fn = fn; + cgc->fnarg = fnarg; + + pthread_create(&pthread, NULL, completion_thread, (void *)cgc); + + ret = cgsem_mswait(&cgc->cgsem, timeout); + if (!ret) + { + pthread_join(pthread, NULL); + free(cgc); + } + else + pthread_cancel(pthread); + return !ret; +} + +void _cg_memcpy(void *dest, const void *src, unsigned int n, const char *file, const char *func, const int line) +{ + if (unlikely(n < 1 || n > (1ul << 31))) + { + applog(LOG_ERR, "ERR: Asked to memcpy %u bytes from %s %s():%d", + n, file, func, line); + return; + } + memcpy(dest, src, n); +} + +int cg_timeval_subtract(struct timeval* result, struct timeval* x, struct timeval* y) +{ + int nsec = 0; + if(x->tv_sec > y->tv_sec) + return -1; + + if((x->tv_sec == y->tv_sec) && (x->tv_usec > y->tv_usec)) + return -1; + + result->tv_sec = (y->tv_sec - x->tv_sec); + result->tv_usec = (y->tv_usec - x->tv_usec); + + if(result->tv_usec < 0) + { + result->tv_sec--; + result->tv_usec += 1000000; + } + return 0; +} + +void rev(unsigned char *s, size_t l) +{ + size_t i, j; + unsigned char t; + + for (i = 0, j = l - 1; i < j; i++, j--) + { + t = s[i]; + s[i] = s[j]; + s[j] = t; + } +} + +int check_asicnum(int asic_num, unsigned char nonce) +{ + switch(asic_num) + { + case 1: + return 1; + case 2: + switch(nonce & 0x80) + { + case 0x80: + return 2; + default: + return 1; + } + case 4: + switch(nonce & 0xC0) + { + case 0xC0: + return 4; + case 0x80: + return 3; + case 0x40: + return 2; + default: + return 1; + } + case 8: + switch(nonce & 0xE0) + { + case 0xE0: + return 8; + case 0xC0: + return 7; + case 0xA0: + return 6; + case 0x80: + return 5; + case 0x60: + return 4; + case 0x40: + return 3; + case 0x20: + return 2; + default : + return 1; + } + case 16: + switch(nonce & 0xF0) + { + case 0xF0: + return 16; + case 0xE0: + return 15; + case 0xD0: + return 14; + case 0xC0: + return 13; + case 0xB0: + return 12; + case 0xA0: + return 11; + case 0x90: + return 10; + case 0x80: + return 9; + case 0x70: + return 8; + case 0x60: + return 7; + case 0x50: + return 6; + case 0x40: + return 5; + case 0x30: + return 4; + case 0x20: + return 3; + case 0x10: + return 2; + default : + return 1; + } + case 32: + switch(nonce & 0xF8) + { + case 0xF8: + return 32; + case 0xF0: + return 31; + case 0xE8: + return 30; + case 0xE0: + return 29; + case 0xD8: + return 28; + case 0xD0: + return 27; + case 0xC8: + return 26; + case 0xC0: + return 25; + case 0xB8: + return 24; + case 0xB0: + return 23; + case 0xA8: + return 22; + case 0xA0: + return 21; + case 0x98: + return 20; + case 0x90: + return 19; + case 0x88: + return 18; + case 0x80: + return 17; + case 0x78: + return 16; + case 0x70: + return 15; + case 0x68: + return 14; + case 0x60: + return 13; + case 0x58: + return 12; + case 0x50: + return 11; + case 0x48: + return 10; + case 0x40: + return 9; + case 0x38: + return 8; + case 0x30: + return 7; + case 0x28: + return 6; + case 0x20: + return 5; + case 0x18: + return 4; + case 0x10: + return 3; + case 0x08: + return 2; + default : + return 1; + } + case 64: + switch(nonce & 0xFC) + { + case 0xFC: + return 64; + case 0xF8: + return 63; + case 0xF4: + return 62; + case 0xF0: + return 61; + case 0xEC: + return 60; + case 0xE8: + return 59; + case 0xE4: + return 58; + case 0xE0: + return 57; + case 0xDC: + return 56; + case 0xD8: + return 55; + case 0xD4: + return 54; + case 0xD0: + return 53; + case 0xCC: + return 52; + case 0xC8: + return 51; + case 0xC4: + return 50; + case 0xC0: + return 49; + case 0xBC: + return 48; + case 0xB8: + return 47; + case 0xB4: + return 46; + case 0xB0: + return 45; + case 0xAC: + return 44; + case 0xA8: + return 43; + case 0xA4: + return 42; + case 0xA0: + return 41; + case 0x9C: + return 40; + case 0x98: + return 39; + case 0x94: + return 38; + case 0x90: + return 37; + case 0x8C: + return 36; + case 0x88: + return 35; + case 0x84: + return 34; + case 0x80: + return 33; + case 0x7C: + return 32; + case 0x78: + return 31; + case 0x74: + return 30; + case 0x70: + return 29; + case 0x6C: + return 28; + case 0x68: + return 27; + case 0x64: + return 26; + case 0x60: + return 25; + case 0x5C: + return 24; + case 0x58: + return 23; + case 0x54: + return 22; + case 0x50: + return 21; + case 0x4C: + return 20; + case 0x48: + return 19; + case 0x44: + return 18; + case 0x40: + return 17; + case 0x3C: + return 16; + case 0x38: + return 15; + case 0x34: + return 14; + case 0x30: + return 13; + case 0x2C: + return 12; + case 0x28: + return 11; + case 0x24: + return 10; + case 0x20: + return 9; + case 0x1C: + return 8; + case 0x18: + return 7; + case 0x14: + return 6; + case 0x10: + return 5; + case 0x0C: + return 4; + case 0x08: + return 3; + case 0x04: + return 2; + default : + return 1; + } + default: + return 0; + } +} + +void cg_logwork(struct work *work, unsigned char *nonce_bin, bool ok) +{ + if(opt_logwork_path) + { + char szmsg[1024] = {0}; + unsigned char midstate_tmp[32] = {0}; + unsigned char data_tmp[32] = {0}; + unsigned char hash_tmp[32] = {0}; + char * szworkdata = NULL; + char * szmidstate = NULL; + char * szdata = NULL; + char * sznonce4 = NULL; + char * sznonce5 = NULL; + char * szhash = NULL; + int asicnum = 0; + uint64_t worksharediff = 0; + memcpy(midstate_tmp, work->midstate, 32); + memcpy(data_tmp, work->data+64, 12); + memcpy(hash_tmp, work->hash, 32); + rev((void *)midstate_tmp, 32); + rev((void *)data_tmp, 12); + rev((void *)hash_tmp, 32); + szworkdata = bin2hex((void *)work->data, 128); + szmidstate = bin2hex((void *)midstate_tmp, 32); + szdata = bin2hex((void *)data_tmp, 12); + sznonce4 = bin2hex((void *)nonce_bin, 4); + sznonce5 = bin2hex((void *)nonce_bin, 5); + szhash = bin2hex((void *)hash_tmp, 32); + worksharediff = share_ndiff(work); + sprintf(szmsg, "%s %08x midstate %s data %s nonce %s hash %s diff %I64d", ok?"o":"x", work->id, szmidstate, szdata, sznonce5, szhash, worksharediff); + if(strcmp(opt_logwork_path, "screen") == 0) + { + applog(LOG_ERR, szmsg); + } + else + { + applog(LOG_ERR, szmsg); + if(g_logwork_file) + { + sprintf(szmsg, "%s %08x work %s midstate %s data %s nonce %s hash %s diff %I64d", ok?"o":"x", work->id, szworkdata, szmidstate, szdata, sznonce5, szhash, worksharediff); + + fwrite(szmsg, strlen(szmsg), 1, g_logwork_file); + fwrite("\n", 1, 1, g_logwork_file); + fflush(g_logwork_file); + + if(ok) + { + if(g_logwork_asicnum == 1) + { + sprintf(szmsg, "midstate %s data %s nonce %s hash %s", szmidstate, szdata, sznonce4, szhash); + fwrite(szmsg, strlen(szmsg), 1, g_logwork_files[0]); + fwrite("\n", 1, 1, g_logwork_files[0]); + fflush(g_logwork_files[0]); + } + else if(g_logwork_asicnum == 32 || g_logwork_asicnum == 64) + { + sprintf(szmsg, "midstate %s data %s nonce %s hash %s", szmidstate, szdata, sznonce4, szhash); + asicnum = check_asicnum(g_logwork_asicnum, nonce_bin[0]); + fwrite(szmsg, strlen(szmsg), 1, g_logwork_files[asicnum]); + fwrite("\n", 1, 1, g_logwork_files[asicnum]); + fflush(g_logwork_files[asicnum]); + } + + if(opt_logwork_diff) + { + int diffnum = 0; + uint64_t difftmp = worksharediff; + while(1) + { + difftmp = difftmp >> 1; + if(difftmp > 0) + { + diffnum++; + if(diffnum >= 64) + { + break; + } + } + else + { + break; + } + } + applog(LOG_DEBUG, "work diff %I64d diffnum %d", worksharediff, diffnum); + sprintf(szmsg, "midstate %s data %s nonce %s hash %s", szmidstate, szdata, sznonce4, szhash); + fwrite(szmsg, strlen(szmsg), 1, g_logwork_diffs[diffnum]); + fwrite("\n", 1, 1, g_logwork_diffs[diffnum]); + fflush(g_logwork_diffs[diffnum]); + } + } + } + } + if(szworkdata) free(szworkdata); + if(szmidstate) free(szmidstate); + if(szdata) free(szdata); + if(sznonce4) free(sznonce4); + if(sznonce5) free(sznonce5); + if(szhash) free(szhash); + } +} + +void cg_logwork_uint32(struct work *work, uint32_t nonce, bool ok) +{ + if(opt_logwork_path) + { + unsigned char nonce_bin[5] = {0}; + memcpy(nonce_bin, &nonce, 4); + cg_logwork(work, nonce_bin, ok); + } +} diff --git a/util.h b/util.h new file mode 100644 index 0000000..14336ac --- /dev/null +++ b/util.h @@ -0,0 +1,181 @@ +#ifndef __UTIL_H__ +#define __UTIL_H__ + +#include + +#if defined(unix) || defined(__APPLE__) + #include + #include + #include + #include + + #define SOCKETTYPE long + #define SOCKETFAIL(a) ((a) < 0) + #define INVSOCK -1 + #define INVINETADDR -1 + #define CLOSESOCKET close + #define INET_PTON inet_pton + + #define SOCKERRMSG strerror(errno) + static inline bool sock_blocks(void) + { + return (errno == EAGAIN || errno == EWOULDBLOCK); + } + static inline bool sock_timeout(void) + { + return (errno == ETIMEDOUT); + } + static inline bool interrupted(void) + { + return (errno == EINTR); + } +#elif defined WIN32 + #include + #include + + #define SOCKETTYPE SOCKET + #define SOCKETFAIL(a) ((int)(a) == SOCKET_ERROR) + #define INVSOCK INVALID_SOCKET + #define INVINETADDR INADDR_NONE + #define CLOSESOCKET closesocket + + int Inet_Pton(int af, const char *src, void *dst); + #define INET_PTON Inet_Pton + + extern char *WSAErrorMsg(void); + #define SOCKERRMSG WSAErrorMsg() + + /* Check for windows variants of the errors as well as when ming + * decides to wrap the error into the errno equivalent. */ + static inline bool sock_blocks(void) + { + return (WSAGetLastError() == WSAEWOULDBLOCK || errno == EAGAIN); + } + static inline bool sock_timeout(void) + { + return (WSAGetLastError() == WSAETIMEDOUT || errno == ETIMEDOUT); + } + static inline bool interrupted(void) + { + return (WSAGetLastError() == WSAEINTR || errno == EINTR); + } + #ifndef SHUT_RDWR + #define SHUT_RDWR SD_BOTH + #endif + + #ifndef in_addr_t + #define in_addr_t uint32_t + #endif +#endif + +#define JSON_LOADS(str, err_ptr) json_loads((str), 0, (err_ptr)) + +#ifdef HAVE_LIBCURL +#include +typedef curl_proxytype proxytypes_t; +#else +typedef int proxytypes_t; +#endif /* HAVE_LIBCURL */ + +/* cgminer locks, a write biased variant of rwlocks */ +struct cglock { + pthread_mutex_t mutex; + pthread_rwlock_t rwlock; +}; + +typedef struct cglock cglock_t; + +/* cgminer specific unnamed semaphore implementations to cope with osx not + * implementing them. */ +#ifdef __APPLE__ +struct cgsem { + int pipefd[2]; +}; + +typedef struct cgsem cgsem_t; +#else +typedef sem_t cgsem_t; +#endif +#ifdef WIN32 +typedef LARGE_INTEGER cgtimer_t; +#else +typedef struct timespec cgtimer_t; +#endif + +int no_yield(void); +int (*selective_yield)(void); +struct thr_info; +struct pool; +enum dev_reason; +struct cgpu_info; +void b58tobin(unsigned char *b58bin, const char *b58); +void address_to_pubkeyhash(unsigned char *pkh, const char *addr); +int ser_number(unsigned char *s, int32_t val); +unsigned char *ser_string(char *s, int *slen); +int thr_info_create(struct thr_info *thr, pthread_attr_t *attr, void *(*start) (void *), void *arg); +void thr_info_cancel(struct thr_info *thr); +void cgtime(struct timeval *tv); +void subtime(struct timeval *a, struct timeval *b); +void addtime(struct timeval *a, struct timeval *b); +bool time_more(struct timeval *a, struct timeval *b); +bool time_less(struct timeval *a, struct timeval *b); +void copy_time(struct timeval *dest, const struct timeval *src); +void timespec_to_val(struct timeval *val, const struct timespec *spec); +void timeval_to_spec(struct timespec *spec, const struct timeval *val); +void us_to_timeval(struct timeval *val, int64_t us); +void us_to_timespec(struct timespec *spec, int64_t us); +void ms_to_timespec(struct timespec *spec, int64_t ms); +void timeraddspec(struct timespec *a, const struct timespec *b); +char *Strcasestr(char *haystack, const char *needle); +char *Strsep(char **stringp, const char *delim); +void cgsleep_ms(int ms); +void cgsleep_us(int64_t us); +void cgtimer_time(cgtimer_t *ts_start); +#define cgsleep_prepare_r(ts_start) cgtimer_time(ts_start) +void cgsleep_ms_r(cgtimer_t *ts_start, int ms); +void cgsleep_us_r(cgtimer_t *ts_start, int64_t us); +int cgtimer_to_ms(cgtimer_t *cgt); +void cgtimer_sub(cgtimer_t *a, cgtimer_t *b, cgtimer_t *res); +double us_tdiff(struct timeval *end, struct timeval *start); +int ms_tdiff(struct timeval *end, struct timeval *start); +double tdiff(struct timeval *end, struct timeval *start); +bool stratum_send(struct pool *pool, char *s, ssize_t len); +bool sock_full(struct pool *pool); +void _recalloc(void **ptr, size_t old, size_t new, const char *file, const char *func, const int line); +#define recalloc(ptr, old, new) _recalloc((void *)&(ptr), old, new, __FILE__, __func__, __LINE__) +char *recv_line(struct pool *pool); +bool parse_method(struct pool *pool, char *s); +void check_extranonce_option(struct pool *pool, char * url); +bool extract_sockaddr(char *url, char **sockaddr_url, char **sockaddr_port); +void extranonce_subscribe_stratum(struct pool *pool); +bool auth_stratum(struct pool *pool); +bool initiate_stratum(struct pool *pool); +bool restart_stratum(struct pool *pool); +void suspend_stratum(struct pool *pool); +void dev_error(struct cgpu_info *dev, enum dev_reason reason); +void *realloc_strcat(char *ptr, char *s); +void *str_text(char *ptr); +void RenameThread(const char* name); +void _cgsem_init(cgsem_t *cgsem, const char *file, const char *func, const int line); +void _cgsem_post(cgsem_t *cgsem, const char *file, const char *func, const int line); +void _cgsem_wait(cgsem_t *cgsem, const char *file, const char *func, const int line); +int _cgsem_mswait(cgsem_t *cgsem, int ms, const char *file, const char *func, const int line); +void cgsem_reset(cgsem_t *cgsem); +void cgsem_destroy(cgsem_t *cgsem); +bool cg_completion_timeout(void *fn, void *fnarg, int timeout); +void _cg_memcpy(void *dest, const void *src, unsigned int n, const char *file, const char *func, const int line); + +#define cgsem_init(_sem) _cgsem_init(_sem, __FILE__, __func__, __LINE__) +#define cgsem_post(_sem) _cgsem_post(_sem, __FILE__, __func__, __LINE__) +#define cgsem_wait(_sem) _cgsem_wait(_sem, __FILE__, __func__, __LINE__) +#define cgsem_mswait(_sem, _timeout) _cgsem_mswait(_sem, _timeout, __FILE__, __func__, __LINE__) +#define cg_memcpy(dest, src, n) _cg_memcpy(dest, src, n, __FILE__, __func__, __LINE__) + +/* Align a size_t to 4 byte boundaries for fussy arches */ +static inline void align_len(size_t *len) +{ + if (*len % 4) + *len += 4 - (*len % 4); +} + +#endif /* __UTIL_H__ */ diff --git a/warn-on-use.h b/warn-on-use.h new file mode 100644 index 0000000..2cdeec3 --- /dev/null +++ b/warn-on-use.h @@ -0,0 +1,109 @@ +/* A C macro for emitting warnings if a function is used. + Copyright (C) 2010-2011 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +/* _GL_WARN_ON_USE (function, "literal string") issues a declaration + for FUNCTION which will then trigger a compiler warning containing + the text of "literal string" anywhere that function is called, if + supported by the compiler. If the compiler does not support this + feature, the macro expands to an unused extern declaration. + + This macro is useful for marking a function as a potential + portability trap, with the intent that "literal string" include + instructions on the replacement function that should be used + instead. However, one of the reasons that a function is a + portability trap is if it has the wrong signature. Declaring + FUNCTION with a different signature in C is a compilation error, so + this macro must use the same type as any existing declaration so + that programs that avoid the problematic FUNCTION do not fail to + compile merely because they included a header that poisoned the + function. But this implies that _GL_WARN_ON_USE is only safe to + use if FUNCTION is known to already have a declaration. Use of + this macro implies that there must not be any other macro hiding + the declaration of FUNCTION; but undefining FUNCTION first is part + of the poisoning process anyway (although for symbols that are + provided only via a macro, the result is a compilation error rather + than a warning containing "literal string"). Also note that in + C++, it is only safe to use if FUNCTION has no overloads. + + For an example, it is possible to poison 'getline' by: + - adding a call to gl_WARN_ON_USE_PREPARE([[#include ]], + [getline]) in configure.ac, which potentially defines + HAVE_RAW_DECL_GETLINE + - adding this code to a header that wraps the system : + #undef getline + #if HAVE_RAW_DECL_GETLINE + _GL_WARN_ON_USE (getline, "getline is required by POSIX 2008, but" + "not universally present; use the gnulib module getline"); + #endif + + It is not possible to directly poison global variables. But it is + possible to write a wrapper accessor function, and poison that + (less common usage, like &environ, will cause a compilation error + rather than issue the nice warning, but the end result of informing + the developer about their portability problem is still achieved): + #if HAVE_RAW_DECL_ENVIRON + static inline char ***rpl_environ (void) { return &environ; } + _GL_WARN_ON_USE (rpl_environ, "environ is not always properly declared"); + # undef environ + # define environ (*rpl_environ ()) + #endif + */ +#ifndef _GL_WARN_ON_USE + +# if 4 < __GNUC__ || (__GNUC__ == 4 && 3 <= __GNUC_MINOR__) +/* A compiler attribute is available in gcc versions 4.3.0 and later. */ +# define _GL_WARN_ON_USE(function, message) \ +extern __typeof__ (function) function __attribute__ ((__warning__ (message))) +# elif __GNUC__ >= 3 && GNULIB_STRICT_CHECKING +/* Verify the existence of the function. */ +# define _GL_WARN_ON_USE(function, message) \ +extern __typeof__ (function) function +# else /* Unsupported. */ +# define _GL_WARN_ON_USE(function, message) \ +_GL_WARN_EXTERN_C int _gl_warn_on_use +# endif +#endif + +/* _GL_WARN_ON_USE_CXX (function, rettype, parameters_and_attributes, "string") + is like _GL_WARN_ON_USE (function, "string"), except that the function is + declared with the given prototype, consisting of return type, parameters, + and attributes. + This variant is useful for overloaded functions in C++. _GL_WARN_ON_USE does + not work in this case. */ +#ifndef _GL_WARN_ON_USE_CXX +# if 4 < __GNUC__ || (__GNUC__ == 4 && 3 <= __GNUC_MINOR__) +# define _GL_WARN_ON_USE_CXX(function,rettype,parameters_and_attributes,msg) \ +extern rettype function parameters_and_attributes \ + __attribute__ ((__warning__ (msg))) +# elif __GNUC__ >= 3 && GNULIB_STRICT_CHECKING +/* Verify the existence of the function. */ +# define _GL_WARN_ON_USE_CXX(function,rettype,parameters_and_attributes,msg) \ +extern rettype function parameters_and_attributes +# else /* Unsupported. */ +# define _GL_WARN_ON_USE_CXX(function,rettype,parameters_and_attributes,msg) \ +_GL_WARN_EXTERN_C int _gl_warn_on_use +# endif +#endif + +/* _GL_WARN_EXTERN_C declaration; + performs the declaration with C linkage. */ +#ifndef _GL_WARN_EXTERN_C +# if defined __cplusplus +# define _GL_WARN_EXTERN_C extern "C" +# else +# define _GL_WARN_EXTERN_C extern +# endif +#endif diff --git a/windows-build.txt b/windows-build.txt new file mode 100644 index 0000000..2e814d2 --- /dev/null +++ b/windows-build.txt @@ -0,0 +1,348 @@ +###################################################################################### +# # +# Native WIN32 setup and build instructions (on mingw32/Windows): # +# # +###################################################################################### + +(See bottom of file for steps to cross-build for Win32 from Linux.) + +************************************************************************************** +* Introduction * +************************************************************************************** +The following instructions have been tested on both Windows 7 and Windows XP. +Most of what is described below (copying files, downloading files, etc.) can be done +directly in the MinGW MSYS shell; these instructions do not do so because package +versions and links change over time. The best way is to use your browser, go to the +links directly, and see for yourself which versions you want to install. +Winrar was used to do the extracting of archive files in the making of this guide. + +If you think that this documentation was helpful and you wish to donate, you can +do so at the following address. 12KaKtrK52iQjPdtsJq7fJ7smC32tXWbWr + +************************************************************************************** +* A tip that might help you along the way * +************************************************************************************** +Enable "QuickEdit Mode" in your Command Prompt Window or MinGW Command Prompt +Window (No need to go into the context menu to choose edit-mark/copy/paste): +Right-click on the title bar and click Properties. Under the Options tab, check +the box for "QuickEdit Mode". Alternately, if you want this change to be +permanent on all of your Command Prompt Windows; you can click Defaults instead +of Properties as described above. Now you can drag and select text you want to +copy, right-click to copy the text to the clipboard and right-click once again to +paste it at the desired location. You could for example, copy some text from this +document to the clipboard and right click in your Command Prompt Window to paste +what you copied. + +************************************************************************************** +* Install mingw32 * +************************************************************************************** +Go to this url ==> http://www.mingw.org/wiki/Getting_Started +Click the link that says "Download and run the latest mingw-get-inst version." +Download and run the latest file. Install MinGW in the default directory. +(I downloaded the one labeled "mingw-get-inst-20120426" - note that this could +be a different version later.) +Make sure to check the option for "Download latest repository catalogs". +I just selected all the check boxes (excluding "Fortran Compiler") so that everything +was installed. + +************************************************************************************** +* Run the MSYS shell for the first time to create your user directory * +************************************************************************************** +(Start Icon/keyboard key ==> All Programs ==> MinGW ==> MinGW Shell). +This will create your user directory for you. + +************************************************************************************** +* Install libpdcurses * +************************************************************************************** +Type the lines below to install libpdcurses. +mingw-get install mingw32-libpdcurses +mingw-get install mingw32-pdcurses +Ctrl-D or typing "logout" and pressing the enter key should get you out of the +window. + +************************************************************************************** +* Copy CGMiner source to your MSYS working directory * +************************************************************************************** +Copy CGMiner source code directory into: +\MinGW\msys\1.0\home\(folder with your user name) + +************************************************************************************** +* Install AMD APP SDK, latest version (only if you want GPU mining) * +************************************************************************************** +Note: You do not need to install the AMD APP SDK if you are only using Nvidia GPU's +Go to this url for the latest AMD APP SDK: + http://developer.amd.com/tools/heterogeneous-computing/amd-accelerated-parallel-processing-app-sdk/downloads/ +Go to this url for legacy AMD APP SDK's: + http://developer.amd.com/tools/heterogeneous-computing/amd-accelerated-parallel-processing-app-sdk/downloads/download-archive/ +Download and install whichever version you like best. +Copy the folders in \Program Files (x86)\AMD APP\include to \MinGW\include +Copy \Program Files (x86)\AMD APP\lib\x86\libOpenCL.a to \MinGW\lib +Note: If you are on a 32 bit version of windows "Program Files (x86)" will be +"Program Files". +Note2: If you update your APP SDK later you might want to recopy the above files + +************************************************************************************** +* Install AMD ADL SDK, latest version (only if you want GPU monitoring) * +************************************************************************************** +Note: You do not need to install the AMD ADL SDK if you are only using Nvidia GPU's +Go to this url ==> http://developer.amd.com/tools/graphics-development/display-library-adl-sdk/ +Download and unzip the file you downloaded. +Pull adl_defines.h, adl_sdk.h, and adl_structures.h out of the include folder +Put those files into the ADL_SDK folder in your source tree as shown below. +\MinGW\msys\1.0\home\(folder with your user name)\cgminer-x.x.x\ADL_SDK + +************************************************************************************** +* Install GTK-WIN, required for Pkg-config in the next step * +************************************************************************************** +Go to this url ==> http://sourceforge.net/projects/gtk-win/ +Download the file. +After you have downloaded the file Double click/run it and this will install GTK+ +I chose all the selection boxes when I installed. +Copy libglib-2.0-0.dll and intl.dll from \Program Files (x86)\gtk2-runtime\bin to +\MinGW\bin +Note: If you are on a 32 bit version of windows "Program Files (x86)" will be +"Program Files". + +************************************************************************************** +* Install pkg-config * +************************************************************************************** +Go to this url ==> http://www.gtk.org/download/win32.php +Scroll down to where it shows pkg-cfg. +Download the file from the tool link. Extract "pkg-config.exe" from bin and place in +your \MinGW\bin directory. +Download the file from the "Dev" link. Extract "pkg.m4" from share\aclocal and place +in your \MingW\share\aclocal directory. + +************************************************************************************** +* Install libcurl * +************************************************************************************** +Go to this url ==> http://curl.haxx.se/download.html#Win32 +At the section where it says "Win32 - Generic", Click on the link that indicates +Win32 2000.XP 7.27.0 libcurl SSL and download it. +The one I downloaded may not be current for you. Choose the latest. +Extract the files that are in the zip (bin, include, and lib) to their respective +locations in MinGW (\MinGW\bin, \MinGW\include, and \MinGW\lib). +Edit the file \MinGW\lib\pkgconfig\libcurl.pc and change "-lcurl" to +"-lcurl -lcurldll". +Ref. http://old.nabble.com/gcc-working-with-libcurl-td20506927.html + +************************************************************************************** +* Build cgminer.exe * +************************************************************************************** +Run the MinGW MSYS shell +(Start Icon/keyboard key ==> All Programs ==> MinGW ==> MinGW Shell). +Change the working directory to your CGMiner project folder. +Example: cd cgminer-2.1.2 [Enter Key] if you are unsure then type "ls -la" +Another way is to type "cd cg" and then press the tab key; It will auto fill. +Type the lines below one at a time. Look for problems after each one before going on +to the next. + + adl.sh (optional - see below) + autoreconf -fvi + CFLAGS="-O2 -msse2" ./configure (additional config options, see below) + make + strip cgminer.exe <== only do this if you are not compiling for debugging + +************************************************************************************** +* Copy files to a build directory/folder * +************************************************************************************** +Make a directory and copy the following files into it. This will be your CGMiner +Folder that you use for mining. Remember the .cl filenames could change on later +releases. If you installed a different version of libcurl then some of those dll's +may be different as well. + cgminer.exe from \MinGW\msys\1.0\home\(username)\cgminer-x.x.x + *.cl from \MinGW\msys\1.0\home\(username)\cgminer-x.x.x + README from \MinGW\msys\1.0\home\(username)\cgminer-x.x.x + libcurl.dll from \MinGW\bin + libidn-11.dll from \MinGW\bin + libeay32.dll from \MinGW\bin + ssleay32.dll from \MinGW\bin + libpdcurses.dll from \MinGW\bin + pthreadGC2.dll from \MinGW\bin + +************************************************************************************** +* Optional - Install Git into MinGW/MSYS * +************************************************************************************** +Go to this url ==> http://code.google.com/p/msysgit/ +Click on the Downloads tab. +Download the latest "Portable" git archive. +Extract the git*.exe files from the bin folder and put them into \MinGW\bin. +Extract the share\git-core folder and place it into \MinGW\share. +After the previous step you should have a folder called \MinGW\share\git-core. +To test if it is working, open a MinGW shell and type the following: + git config -–global core.autocrlf false (note: one time run only) + git clone git://github.com/ckolivas/cgminer.git + +If you simply just want to update the source after you have already cloned, type: + git pull +"git pull" did not work for me. Try the following which does the same thing: + git fetch && git merge FETCH_HEAD + +Now you can get the latest source directly from github. + +************************************************************************************** +* Optional - Make a .sh file to automate copying over ADL files * +************************************************************************************** +Make a folder/directory in your home folder and name it ADL_SDK. + (ref: \MinGW\msys\1.0\home\(folder with your user name)\ADL_SDK) +Copy the ADL .h files into that folder/directory. +Open your favorite text editor and type the following into it. + cp -av ../ADL_SDK/*.h ADL_SDK +Save the file as "adl.sh" and then place the file into "\MinGW\msys\1.0\bin". +From now on when your current working directory is the cgminer source directory +You can simply type "adl.sh" and it will place the ADL header files into place +For you. Make sure you never remove the ADL_SDK folder from your home folder. + +************************************************************************************** +* Optional - Install libusb if you need auto USB device detection; required for Ztex * +************************************************************************************** +Go to this url ==> http://git.libusb.org/?p=libusb.git;a=snapshot;h=master;sf=zip +save the file to your local storage. Open the file and copy the libusb* folder to +\MinGW\msys\1.0\home\(your user directory/folder). +Or if you do not want to download the file directly and would like to use git then +Type the following from the MSYS shell in your home folder. +git clone git://git.libusb.org/libusb.git + +Run the MinGW MSYS shell +(Start Icon/keyboard key ==> All Programs ==> MinGW ==> MinGW Shell). +Change the working directory to your libusb project folder. +Example: cd libusb-something [Enter Key] if you are unsure then type "ls -la" +Another way is to type "cd libusb" and then press the tab key; It will auto fill. +Type the lines below one at a time. Look for problems after each one before going on +to the next. + +./autogen.sh --disable-debug-log --prefix=/MinGW +make +make install + +You may now exit the MSYS shell. +Ctrl-D or typing "logout" and pressing the enter key should get you out of the +window. + +You will have to copy "libusb-1.0.dll" to your working cgminer binary directory. +You will find "libusb-1.0.dll" in the \MinGW\bin directory/folder. + +Use this method if libusb does not work for you on Ztex. Once someone lets us know +Libusb works instead of libusbx then we will remove the section below this line. +Run the MSYS shell and change into the libusb folder as above. +Type ==> make uninstall +Go to this url ==> http://libusbx.org/ +Click on the "Downloads" tab. +Click on "releases". +Click on the latest version. I downloaded 1.0.14; yours may be newer. +Do not download from the link that says "Looking for the latest version?". +Click on "Windows" +Click on the file and download it. I downloaded libusbx-1.0.12-win.7z. +Extract the the following from the file and place in where directed. +Copy libusb.h from include\libusbx-1.0 to \MinGW\include\libusb-1.0\libusb.h +Copy contents of MinGW32\static \MinGW\lib +Copy contents of MinGW32\dll to \MinGW\lib +You will have to copy "libusb-1.0.dll" to your working cgminer binary directory. + +************************************************************************************** +* Some ./configure options * +************************************************************************************** +--enable-cpumining Build with cpu mining support(default disabled) +--disable-opencl Override detection and disable building with opencl +--disable-adl Override detection and disable building with adl +--enable-bitforce Compile support for BitForce FPGAs(default disabled) +--enable-icarus Compile support for Icarus Board(default disabled) +--enable-bmsc Compile support for BitMain Single Chain(default disabled) +--enable-bitmain Compile support for BitMain Multi Chain(default disabled) +--enable-modminer Compile support for ModMiner FPGAs(default disabled) +--enable-ztex Compile support for Ztex Board(default disabled) +--enable-scrypt Compile support for scrypt litecoin mining (default disabled) +--without-curses Compile support for curses TUI (default enabled) +--without-libudev Autodetect FPGAs using libudev (default enabled) + + + +###################################################################################### +# # +# Cross-compiling for Windows from Linux # +# # +###################################################################################### + +It is possible to cross-compile Windows binaries from Linux. The +process is a bit different to the native steps shown above (it is also +possible to use wine and the native steps, but this is more messing +around, very slow, and not advisable.) + +** Install mingw cross compiler + +On Ubuntu/Debian: + +sudo apt-get install mingw32 + +** create a directory to hold our cross-library dependencies + +We'll create a directory outside the source tree to hold non-system +libraries we depend on. We could put these in +/usr/i586-mingw32msvc/lib or anywhere else, instead (though keeping it +outside /usr means we can set it up without root privileges.) + +IMPORTANT: If you put this directory inside your cgminer directory, +remember 'make distclean' may delete it! + +mkdir -p ../cgminer-win32-deps/lib +cd ../cgminer-win32-deps +mkdir include +mkdir bin + +NB: All following steps assume you are in the "cgminer-win32-deps" directory. Adjust as necessary. + +** pdcurses + +wget http://internode.dl.sourceforge.net/project/pdcurses/pdcurses/3.4/pdc34dllw.zip +unzip /home/gus/Downloads/pdc34dllw.zip +mv *.h include/ +mv pdcurses.lib lib/ +mv pdcurses.dll bin/ + +** pthreads-w32 + +(NB: I found pthreads-w32 2.9.1 doesn't seem to work properly, transfers time out early due to sem_timedwait exiting immediately(?)) + +wget -O lib/libpthread.a ftp://sourceware.org/pub/pthreads-win32/prebuilt-dll-2-8-0-release/lib/libpthreadGC2.a +wget -O include/pthread.h ftp://sourceware.org/pub/pthreads-win32/prebuilt-dll-2-8-0-release/include/pthread.h +wget -O include/sched.h ftp://sourceware.org/pub/pthreads-win32/prebuilt-dll-2-8-0-release/include/sched.h +wget -O include/semaphore.h ftp://sourceware.org/pub/pthreads-win32/prebuilt-dll-2-8-0-release/include/semaphore.h +wget -O lib/libpthread.a ftp://sourceware.org/pub/pthreads-win32/prebuilt-dll-2-8-0-release/lib/libpthreadGC2.a +wget -O bin/pthreadGC2.dll ftp://sourceware.org/pub/pthreads-win32/prebuilt-dll-2-8-0-release/lib/pthreadGC2.dll + +** libcurl + +wget http://curl.haxx.se/gknw.net/7.33.0/dist-w32/curl-7.33.0-devel-mingw32.zip +unzip curl-7.33.0-devel-mingw32.zip +mv curl-7.33.0-devel-mingw32/include/* include/ +mv curl-7.33.0-devel-mingw32/lib/* lib/ +mv curl-7.33.0-devel-mingw32/bin/* bin/ +rm -rf curl-7.33.0-devel-mingw32 + + +** clean up + +rm *.zip + + +** Building cgminer + +Below assumes you're building in a "build-win32" or similar directory +inside the cgminer directory. Fix up the -I and -L paths appropriately +if you're building in-tree or someplace else. + +Configure command: + +CPPFLAGS="-I`pwd`/../../cgminer-win32-deps/include" LDFLAGS="-L`pwd`/../../cgminer-win32-deps/lib -lcurldll" ../autogen.sh --prefix=/usr/local/i586-mingw32 --host=i586-mingw32msvc --build=i686-linux + +^^^ Plus whatever configure arguments you want to add. Note the paths + to cgminer-win32-deps that you may need to change. + +And make: + +make + +After cgminer builds, the next steps are the same as for native +building as given under "Copy files to a build directory/folder" +(DLLs can all be found in the cgminer-win32-deps/bin directory.) +