mirror of
https://github.com/GOSTSec/sgminer
synced 2025-01-11 15:27:53 +00:00
Merge branch 'master' of git@github.com:pshep/cgminer.git
Conflicts: driver-bitforce.c
This commit is contained in:
commit
4889d7ecb2
459
API-README
Normal file
459
API-README
Normal file
@ -0,0 +1,459 @@
|
||||
|
||||
This README contains details about the cgminer RPC API
|
||||
|
||||
|
||||
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.
|
||||
Without it those commands return an access denied status.
|
||||
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
|
||||
|
||||
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 CPU/GPU number if required.
|
||||
|
||||
An example request in both formats to set GPU 0 fan to 80%:
|
||||
gpufan|0,80
|
||||
{"command":"gpufan","parameter":"0,80"}
|
||||
|
||||
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 unigue 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.
|
||||
|
||||
For API version 1.9:
|
||||
|
||||
The list of requests - a (*) means it requires privileged access - and replies are:
|
||||
|
||||
Request Reply Section Details
|
||||
------- ------------- -------
|
||||
version VERSION CGMiner=cgminer version
|
||||
API=API version
|
||||
|
||||
config CONFIG Some miner configuration information:
|
||||
GPU Count=N, <- the number of GPUs
|
||||
PGA Count=N, <- the number of PGAs
|
||||
CPU Count=N, <- the number of CPUs
|
||||
Pool Count=N, <- the number of Pools
|
||||
ADL=X, <- Y or N if ADL is compiled in the code
|
||||
ADL in use=X, <- Y or N if any GPU has ADL
|
||||
Strategy=Name, <- the current pool strategy
|
||||
Log Interval=N, <- log interval (--log N)
|
||||
Device Code=GPU ICA | <- spaced list of compiled devices
|
||||
|
||||
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 GPU, PGA and CPU with their details
|
||||
e.g. GPU=0,Accepted=NN,MHS av=NNN,...,Intensity=D|
|
||||
Last Share Time=NNN, <- standand long time in seconds
|
||||
(or 0 if none) of last accepted share
|
||||
Last Share Pool=N, <- pool number (or -1 if none)
|
||||
Will not report PGAs if PGA mining is disabled
|
||||
Will not report CPUs if CPU mining is disabled
|
||||
|
||||
gpu|N GPU The details of a single GPU number N in the same
|
||||
format and details as for DEVS
|
||||
|
||||
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
|
||||
|
||||
cpu|N CPU The details of a single CPU number N in the same
|
||||
format and details as for DEVS
|
||||
This is only available if CPU mining is enabled
|
||||
Use 'cpucount' or 'config' first to see if there are any
|
||||
|
||||
gpucount GPUS Count=N| <- the number of GPUs
|
||||
|
||||
pgacount PGAS Count=N| <- the number of PGAs
|
||||
Always returns 0 if PGA mining is disabled
|
||||
|
||||
cpucount CPUS Count=N| <- the number of CPUs
|
||||
Always returns 0 if CPU 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 URL
|
||||
Use '\\' to get a '\' and '\,' to include a comma
|
||||
inside URL, USR or PASS
|
||||
|
||||
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
|
||||
|
||||
gpuenable|N (*)
|
||||
none There is no reply section just the STATUS section
|
||||
stating the results of the enable request
|
||||
|
||||
gpudisable|N (*)
|
||||
none There is no reply section just the STATUS section
|
||||
stating the results of the disable request
|
||||
|
||||
gpurestart|N (*)
|
||||
none There is no reply section just the STATUS section
|
||||
stating the results of the restart request
|
||||
|
||||
gpuintensity|N,I (*)
|
||||
none There is no reply section just the STATUS section
|
||||
stating the results of setting GPU N intensity to I
|
||||
|
||||
gpumem|N,V (*)
|
||||
none There is no reply section just the STATUS section
|
||||
stating the results of setting GPU N memoryclock to V MHz
|
||||
|
||||
gpuengine|N,V (*)
|
||||
none There is no reply section just the STATUS section
|
||||
stating the results of setting GPU N clock to V MHz
|
||||
|
||||
gpufan|N,V (*)
|
||||
none There is no reply section just the STATUS section
|
||||
stating the results of setting GPU N fan speed to V%
|
||||
|
||||
gpuvddc|N,V (*)
|
||||
none There is no reply section just the STATUS section
|
||||
stating the results of setting GPU N vddc to V
|
||||
|
||||
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 There is no status section but just 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=GPU,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
|
||||
|
||||
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=GPU,ID=0,Driver=opencl,...|
|
||||
|
||||
restart (*) none There is no status section but just 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
|
||||
|
||||
When you enable, disable or restart a GPU or PGA, you will also get Thread messages
|
||||
in the cgminer status window
|
||||
|
||||
When you switch to a different pool to the current one, 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) you will also get messages showing
|
||||
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.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
|
||||
Read the top of the file (miner.php) 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 V1.11
|
||||
|
||||
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'
|
||||
|
56
FPGA-README
Normal file
56
FPGA-README
Normal file
@ -0,0 +1,56 @@
|
||||
|
||||
This README contains extended details about FPGA mining with cgminer
|
||||
|
||||
|
||||
Icarus
|
||||
|
||||
There is a hidden option in cgminer when Icarus support is compiled in:
|
||||
|
||||
--icarus-timing <arg> 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 Calculate the hash time and stop adjusting it at ~315 difficulty 1 shares (~1hr)
|
||||
long 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)
|
||||
|
||||
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
|
||||
|
||||
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 1/10ths of a second, 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 a dual FPGA device that supports the same commands as
|
||||
Icarus Rev3 requires and also is less than ~840MH/s and greater than 2MH/s
|
||||
If a dual FPGA device does hash faster than ~840MH/s it should work correctly if you supply the
|
||||
correct hash time nanoseconds value
|
||||
|
||||
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
|
@ -10,7 +10,7 @@ endif
|
||||
EXTRA_DIST = example.conf m4/gnulib-cache.m4 linux-usb-cgminer \
|
||||
ADL_SDK/readme.txt api-example.php miner.php \
|
||||
API.class API.java api-example.c windows-build.txt \
|
||||
bitstreams/*
|
||||
bitstreams/* API-README FPGA-README
|
||||
|
||||
SUBDIRS = lib compat ccan
|
||||
|
||||
|
28
NEWS
28
NEWS
@ -1,3 +1,31 @@
|
||||
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
|
||||
|
278
README
278
README
@ -209,6 +209,8 @@ FPGA mining boards(BitForce, Icarus, Ztex) only options:
|
||||
On windows <arg> is usually of the format \\.\COMn
|
||||
(where n = the correct device number for the FPGA device)
|
||||
|
||||
For other FPGA details see the FPGA-README
|
||||
|
||||
|
||||
CPU only options (deprecated, not included in binaries!):
|
||||
|
||||
@ -569,281 +571,7 @@ cgminer shuts down because of this.
|
||||
|
||||
RPC API
|
||||
|
||||
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.
|
||||
Without it those commands return an access denied status.
|
||||
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
|
||||
|
||||
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 CPU/GPU number if required.
|
||||
|
||||
An example request in both formats to set GPU 0 fan to 80%:
|
||||
gpufan|0,80
|
||||
{"command":"gpufan","parameter":"0,80"}
|
||||
|
||||
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 unigue 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.
|
||||
|
||||
For API version 1.9:
|
||||
|
||||
The list of requests - a (*) means it requires privileged access - and replies are:
|
||||
|
||||
Request Reply Section Details
|
||||
------- ------------- -------
|
||||
version VERSION CGMiner=cgminer version
|
||||
API=API version
|
||||
|
||||
config CONFIG Some miner configuration information:
|
||||
GPU Count=N, <- the number of GPUs
|
||||
PGA Count=N, <- the number of PGAs
|
||||
CPU Count=N, <- the number of CPUs
|
||||
Pool Count=N, <- the number of Pools
|
||||
ADL=X, <- Y or N if ADL is compiled in the code
|
||||
ADL in use=X, <- Y or N if any GPU has ADL
|
||||
Strategy=Name, <- the current pool strategy
|
||||
Log Interval=N, <- log interval (--log N)
|
||||
Device Code=GPU ICA | <- spaced list of compiled devices
|
||||
|
||||
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 GPU, PGA and CPU with their details
|
||||
e.g. GPU=0,Accepted=NN,MHS av=NNN,...,Intensity=D|
|
||||
Last Share Time=NNN, <- standand long time in seconds
|
||||
(or 0 if none) of last accepted share
|
||||
Last Share Pool=N, <- pool number (or -1 if none)
|
||||
Will not report PGAs if PGA mining is disabled
|
||||
Will not report CPUs if CPU mining is disabled
|
||||
|
||||
gpu|N GPU The details of a single GPU number N in the same
|
||||
format and details as for DEVS
|
||||
|
||||
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
|
||||
|
||||
cpu|N CPU The details of a single CPU number N in the same
|
||||
format and details as for DEVS
|
||||
This is only available if CPU mining is enabled
|
||||
Use 'cpucount' or 'config' first to see if there are any
|
||||
|
||||
gpucount GPUS Count=N| <- the number of GPUs
|
||||
|
||||
pgacount PGAS Count=N| <- the number of PGAs
|
||||
Always returns 0 if PGA mining is disabled
|
||||
|
||||
cpucount CPUS Count=N| <- the number of CPUs
|
||||
Always returns 0 if CPU 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 URL
|
||||
Use '\\' to get a '\' and '\,' to include a comma
|
||||
inside URL, USR or PASS
|
||||
|
||||
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
|
||||
|
||||
gpuenable|N (*)
|
||||
none There is no reply section just the STATUS section
|
||||
stating the results of the enable request
|
||||
|
||||
gpudisable|N (*)
|
||||
none There is no reply section just the STATUS section
|
||||
stating the results of the disable request
|
||||
|
||||
gpurestart|N (*)
|
||||
none There is no reply section just the STATUS section
|
||||
stating the results of the restart request
|
||||
|
||||
gpuintensity|N,I (*)
|
||||
none There is no reply section just the STATUS section
|
||||
stating the results of setting GPU N intensity to I
|
||||
|
||||
gpumem|N,V (*)
|
||||
none There is no reply section just the STATUS section
|
||||
stating the results of setting GPU N memoryclock to V MHz
|
||||
|
||||
gpuengine|N,V (*)
|
||||
none There is no reply section just the STATUS section
|
||||
stating the results of setting GPU N clock to V MHz
|
||||
|
||||
gpufan|N,V (*)
|
||||
none There is no reply section just the STATUS section
|
||||
stating the results of setting GPU N fan speed to V%
|
||||
|
||||
gpuvddc|N,V (*)
|
||||
none There is no reply section just the STATUS section
|
||||
stating the results of setting GPU N vddc to V
|
||||
|
||||
save|filename (*)
|
||||
none There is no reply section just the STATUS section
|
||||
stating success or failure saving the cgminer config
|
||||
to filename
|
||||
|
||||
quit (*) none There is no status section but just 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=GPU,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
|
||||
|
||||
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=GPU,ID=0,Driver=opencl,...|
|
||||
|
||||
restart (*) none There is no status section but just 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
|
||||
|
||||
When you enable, disable or restart a GPU or PGA, you will also get Thread messages
|
||||
in the cgminer status window
|
||||
|
||||
When you switch to a different pool to the current one, 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) you will also get messages showing
|
||||
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.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
|
||||
Read the top of the file (miner.php) for details of how to tune the display
|
||||
and also to use the option to display a multi-rig summary
|
||||
For RPC API details see the API-README file
|
||||
|
||||
---
|
||||
|
||||
|
6
adl.c
6
adl.c
@ -692,11 +692,7 @@ int gpu_fanpercent(int gpu)
|
||||
unlock_adl();
|
||||
if (unlikely(ga->has_fanspeed && ret == -1)) {
|
||||
applog(LOG_WARNING, "GPU %d stopped reporting fanspeed due to driver corruption", gpu);
|
||||
if (opt_restart) {
|
||||
applog(LOG_WARNING, "Restart enabled, will restart cgminer");
|
||||
applog(LOG_WARNING, "You can disable this with the --no-restart option");
|
||||
app_restart();
|
||||
}
|
||||
applog(LOG_WARNING, "You will need to start cgminer from scratch to correct this");
|
||||
applog(LOG_WARNING, "Disabling fanspeed monitoring on this device");
|
||||
ga->has_fanspeed = false;
|
||||
}
|
||||
|
34
api.c
34
api.c
@ -142,6 +142,9 @@
|
||||
// Current code assumes it can socket send this size also
|
||||
#define MYBUFSIZ 65432 // TODO: intercept before it's exceeded
|
||||
|
||||
// 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
|
||||
@ -763,7 +766,7 @@ static void apiversion(__maybe_unused SOCKETTYPE c, __maybe_unused char *param,
|
||||
|
||||
static void minerconfig(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson)
|
||||
{
|
||||
char buf[BUFSIZ];
|
||||
char buf[TMPBUFSIZ];
|
||||
int pgacount = 0;
|
||||
int cpucount = 0;
|
||||
char *adlinuse = (char *)NO;
|
||||
@ -804,7 +807,7 @@ static void minerconfig(__maybe_unused SOCKETTYPE c, __maybe_unused char *param,
|
||||
static void gpustatus(int gpu, bool isjson)
|
||||
{
|
||||
char intensity[20];
|
||||
char buf[BUFSIZ];
|
||||
char buf[TMPBUFSIZ];
|
||||
char *enabled;
|
||||
char *status;
|
||||
float gt, gv;
|
||||
@ -856,7 +859,7 @@ static void gpustatus(int gpu, bool isjson)
|
||||
#if defined(USE_BITFORCE) || defined(USE_ICARUS) || defined(USE_ZTEX)
|
||||
static void pgastatus(int pga, bool isjson)
|
||||
{
|
||||
char buf[BUFSIZ];
|
||||
char buf[TMPBUFSIZ];
|
||||
char *enabled;
|
||||
char *status;
|
||||
int numpga = numpgas();
|
||||
@ -908,7 +911,7 @@ static void pgastatus(int pga, bool isjson)
|
||||
#ifdef WANT_CPUMINE
|
||||
static void cpustatus(int cpu, bool isjson)
|
||||
{
|
||||
char buf[BUFSIZ];
|
||||
char buf[TMPBUFSIZ];
|
||||
|
||||
if (opt_n_threads > 0 && cpu >= 0 && cpu < num_processors) {
|
||||
struct cgpu_info *cgpu = &cpus[cpu];
|
||||
@ -1188,7 +1191,7 @@ static void cpudev(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
|
||||
|
||||
static void poolstatus(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson)
|
||||
{
|
||||
char buf[BUFSIZ];
|
||||
char buf[TMPBUFSIZ];
|
||||
char *status, *lp;
|
||||
char *rpc_url;
|
||||
char *rpc_user;
|
||||
@ -1400,7 +1403,7 @@ static void gpurestart(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
|
||||
|
||||
static void gpucount(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson)
|
||||
{
|
||||
char buf[BUFSIZ];
|
||||
char buf[TMPBUFSIZ];
|
||||
|
||||
strcpy(io_buffer, message(MSG_NUMGPU, 0, NULL, isjson));
|
||||
|
||||
@ -1414,7 +1417,7 @@ static void gpucount(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bo
|
||||
|
||||
static void pgacount(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson)
|
||||
{
|
||||
char buf[BUFSIZ];
|
||||
char buf[TMPBUFSIZ];
|
||||
int count = 0;
|
||||
|
||||
#if defined(USE_BITFORCE) || defined(USE_ICARUS) || defined(USE_ZTEX)
|
||||
@ -1433,7 +1436,7 @@ static void pgacount(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bo
|
||||
|
||||
static void cpucount(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson)
|
||||
{
|
||||
char buf[BUFSIZ];
|
||||
char buf[TMPBUFSIZ];
|
||||
int count = 0;
|
||||
|
||||
#ifdef WANT_CPUMINE
|
||||
@ -1863,7 +1866,7 @@ void privileged(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool is
|
||||
|
||||
void notifystatus(int device, struct cgpu_info *cgpu, bool isjson)
|
||||
{
|
||||
char buf[BUFSIZ];
|
||||
char buf[TMPBUFSIZ];
|
||||
char *reason;
|
||||
|
||||
if (cgpu->device_last_not_well == 0)
|
||||
@ -1940,7 +1943,7 @@ static void notify(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool
|
||||
|
||||
static void devdetails(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson)
|
||||
{
|
||||
char buf[BUFSIZ];
|
||||
char buf[TMPBUFSIZ];
|
||||
struct cgpu_info *cgpu;
|
||||
int i;
|
||||
|
||||
@ -2006,7 +2009,7 @@ void dosave(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
|
||||
|
||||
static int itemstats(int i, char *id, struct cgminer_stats *stats, char *extra, bool isjson)
|
||||
{
|
||||
char buf[BUFSIZ];
|
||||
char buf[TMPBUFSIZ];
|
||||
|
||||
if (stats->getwork_calls || (extra != NULL && *extra))
|
||||
{
|
||||
@ -2032,7 +2035,7 @@ static int itemstats(int i, char *id, struct cgminer_stats *stats, char *extra,
|
||||
}
|
||||
static void minerstats(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson)
|
||||
{
|
||||
char extra[BUFSIZ];
|
||||
char extra[TMPBUFSIZ];
|
||||
char id[20];
|
||||
int i, j;
|
||||
|
||||
@ -2300,8 +2303,8 @@ static void *restart_thread(__maybe_unused void *userdata)
|
||||
void api(int api_thr_id)
|
||||
{
|
||||
struct thr_info bye_thr;
|
||||
char buf[BUFSIZ];
|
||||
char param_buf[BUFSIZ];
|
||||
char buf[TMPBUFSIZ];
|
||||
char param_buf[TMPBUFSIZ];
|
||||
const char *localaddr = "127.0.0.1";
|
||||
SOCKETTYPE c;
|
||||
int n, bound;
|
||||
@ -2434,7 +2437,7 @@ void api(int api_thr_id)
|
||||
applog(LOG_DEBUG, "API: connection from %s - %s", connectaddr, addrok ? "Accepted" : "Ignored");
|
||||
|
||||
if (addrok) {
|
||||
n = recv(c, &buf[0], BUFSIZ-1, 0);
|
||||
n = recv(c, &buf[0], TMPBUFSIZ-1, 0);
|
||||
if (SOCKETFAIL(n))
|
||||
buf[0] = '\0';
|
||||
else
|
||||
@ -2563,3 +2566,4 @@ die:
|
||||
|
||||
mutex_unlock(&quit_restart_lock);
|
||||
}
|
||||
|
||||
|
116
cgminer.c
116
cgminer.c
@ -134,6 +134,7 @@ bool opt_api_listen;
|
||||
bool opt_api_network;
|
||||
bool opt_delaynet;
|
||||
bool opt_disable_pool = true;
|
||||
char *opt_icarus_timing = NULL;
|
||||
|
||||
char *opt_kernel_path;
|
||||
char *cgminer_path;
|
||||
@ -165,6 +166,9 @@ static pthread_rwlock_t blk_lock;
|
||||
|
||||
pthread_rwlock_t netacc_lock;
|
||||
|
||||
static pthread_mutex_t lp_lock;
|
||||
static pthread_cond_t lp_cond;
|
||||
|
||||
double total_mhashes_done;
|
||||
static struct timeval total_tv_start, total_tv_end;
|
||||
|
||||
@ -675,6 +679,15 @@ static char *set_api_description(const char *arg)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#ifdef USE_ICARUS
|
||||
static char *set_icarus_timing(const char *arg)
|
||||
{
|
||||
opt_set_charp(arg, &opt_icarus_timing);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* These options are available from config file or commandline */
|
||||
static struct opt_table opt_config_table[] = {
|
||||
#ifdef WANT_CPUMINE
|
||||
@ -811,6 +824,11 @@ static struct opt_table opt_config_table[] = {
|
||||
OPT_WITH_ARG("--kernel|-k",
|
||||
set_kernel, NULL, NULL,
|
||||
"Override kernel to use (diablo, poclbm, phatk or diakgcn) - one value or comma separated"),
|
||||
#endif
|
||||
#ifdef USE_ICARUS
|
||||
OPT_WITH_ARG("--icarus-timing",
|
||||
set_icarus_timing, NULL, NULL,
|
||||
opt_hidden),
|
||||
#endif
|
||||
OPT_WITHOUT_ARG("--load-balance",
|
||||
set_loadbalance, &pool_strategy,
|
||||
@ -1718,8 +1736,10 @@ static bool submit_upstream_work(const struct work *work, CURL *curl)
|
||||
/* 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. */
|
||||
if (pool->seq_rejects > 10 && opt_disable_pool && total_pools > 1) {
|
||||
* 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 && total_pools > 1) {
|
||||
double utility = total_accepted / ( total_secs ? total_secs : 1 ) * 60;
|
||||
|
||||
if (pool->seq_rejects > utility * 3) {
|
||||
@ -2154,6 +2174,7 @@ static void *submit_work_thread(void *userdata)
|
||||
pool->stale_shares++;
|
||||
goto out;
|
||||
}
|
||||
work->stale = true;
|
||||
}
|
||||
|
||||
ce = pop_curl_entry(pool);
|
||||
@ -2280,6 +2301,10 @@ void switch_pools(struct pool *selected)
|
||||
mutex_lock(&qd_lock);
|
||||
total_queued = 0;
|
||||
mutex_unlock(&qd_lock);
|
||||
|
||||
mutex_lock(&lp_lock);
|
||||
pthread_cond_broadcast(&lp_cond);
|
||||
mutex_unlock(&lp_lock);
|
||||
}
|
||||
|
||||
static void discard_work(struct work *work)
|
||||
@ -2618,22 +2643,15 @@ void remove_pool(struct pool *pool)
|
||||
|
||||
void write_config(FILE *fcfg)
|
||||
{
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
int i;
|
||||
|
||||
/* Write pool values in priority order */
|
||||
/* Write pool values */
|
||||
fputs("{\n\"pools\" : [", fcfg);
|
||||
while((j < total_pools) && (i < total_pools)) {
|
||||
if(pools[i]->prio == j) {
|
||||
fprintf(fcfg, "%s\n\t{\n\t\t\"url\" : \"%s\",", i > 0 ? "," : "", pools[i]->rpc_url);
|
||||
fprintf(fcfg, "\n\t\t\"user\" : \"%s\",", pools[i]->rpc_user);
|
||||
fprintf(fcfg, "\n\t\t\"pass\" : \"%s\"\n\t}", pools[i]->rpc_pass);
|
||||
j++;
|
||||
i=0;
|
||||
}
|
||||
else
|
||||
i++;
|
||||
}
|
||||
for(i = 0; i < total_pools; i++) {
|
||||
fprintf(fcfg, "%s\n\t{\n\t\t\"url\" : \"%s\",", i > 0 ? "," : "", pools[i]->rpc_url);
|
||||
fprintf(fcfg, "\n\t\t\"user\" : \"%s\",", pools[i]->rpc_user);
|
||||
fprintf(fcfg, "\n\t\t\"pass\" : \"%s\"\n\t}", pools[i]->rpc_pass);
|
||||
}
|
||||
fputs("\n]\n", fcfg);
|
||||
|
||||
if (nDevs) {
|
||||
@ -2766,6 +2784,8 @@ void write_config(FILE *fcfg)
|
||||
fprintf(fcfg, ",\n\"api-allow\" : \"%s\"", opt_api_allow);
|
||||
if (strcmp(opt_api_description, PACKAGE_STRING) != 0)
|
||||
fprintf(fcfg, ",\n\"api-description\" : \"%s\"", opt_api_description);
|
||||
if (opt_icarus_timing)
|
||||
fprintf(fcfg, ",\n\"icarus-timing\" : \"%s\"", opt_icarus_timing);
|
||||
fputs("\n}", fcfg);
|
||||
}
|
||||
|
||||
@ -2986,6 +3006,23 @@ retry:
|
||||
}
|
||||
#endif
|
||||
|
||||
void default_save_file(char *filename)
|
||||
{
|
||||
#if defined(unix)
|
||||
if (getenv("HOME") && *getenv("HOME")) {
|
||||
strcpy(filename, getenv("HOME"));
|
||||
strcat(filename, "/");
|
||||
}
|
||||
else
|
||||
strcpy(filename, "");
|
||||
strcat(filename, ".cgminer/");
|
||||
mkdir(filename, 0777);
|
||||
#else
|
||||
strcpy(filename, "");
|
||||
#endif
|
||||
strcat(filename, def_conf);
|
||||
}
|
||||
|
||||
#ifdef HAVE_CURSES
|
||||
static void set_options(void)
|
||||
{
|
||||
@ -3046,19 +3083,7 @@ retry:
|
||||
FILE *fcfg;
|
||||
char *str, filename[PATH_MAX], prompt[PATH_MAX + 50];
|
||||
|
||||
#if defined(unix)
|
||||
if (getenv("HOME") && *getenv("HOME")) {
|
||||
strcpy(filename, getenv("HOME"));
|
||||
strcat(filename, "/");
|
||||
}
|
||||
else
|
||||
strcpy(filename, "");
|
||||
strcat(filename, ".cgminer/");
|
||||
mkdir(filename, 0777);
|
||||
#else
|
||||
strcpy(filename, "");
|
||||
#endif
|
||||
strcat(filename, def_conf);
|
||||
default_save_file(filename);
|
||||
sprintf(prompt, "Config filename to write (Enter for default) [%s]", filename);
|
||||
str = curses_input(prompt);
|
||||
if (strcmp(str, "-1")) {
|
||||
@ -3997,6 +4022,21 @@ static struct pool *select_longpoll_pool(struct pool *cp)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* 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)
|
||||
{
|
||||
if (pool->enabled == POOL_REJECTING)
|
||||
return;
|
||||
|
||||
while (pool != current_pool()) {
|
||||
mutex_lock(&lp_lock);
|
||||
pthread_cond_wait(&lp_cond, &lp_lock);
|
||||
mutex_unlock(&lp_lock);
|
||||
}
|
||||
}
|
||||
|
||||
static void *longpoll_thread(void *userdata)
|
||||
{
|
||||
struct pool *cp = (struct pool *)userdata;
|
||||
@ -4027,6 +4067,8 @@ retry_pool:
|
||||
/* Any longpoll from any pool is enough for this to be true */
|
||||
have_longpoll = true;
|
||||
|
||||
wait_lpcurrent(cp);
|
||||
|
||||
if (cp == pool)
|
||||
applog(LOG_WARNING, "Long-polling activated for %s", pool->lp_url);
|
||||
else
|
||||
@ -4035,6 +4077,8 @@ retry_pool:
|
||||
while (42) {
|
||||
json_t *val, *soval;
|
||||
|
||||
wait_lpcurrent(cp);
|
||||
|
||||
gettimeofday(&start, NULL);
|
||||
|
||||
/* Longpoll connections can be persistent for a very long time
|
||||
@ -4230,13 +4274,17 @@ static void *watchdog_thread(void __maybe_unused *userdata)
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_OPENCL
|
||||
for (i = 0; i < total_devices; ++i) {
|
||||
struct cgpu_info *cgpu = devices[i];
|
||||
struct thr_info *thr = cgpu->thread;
|
||||
enum dev_enable *denable;
|
||||
int gpu;
|
||||
|
||||
if (cgpu->api->get_stats) {
|
||||
cgpu->api->get_stats(cgpu);
|
||||
}
|
||||
|
||||
#ifdef HAVE_OPENCL
|
||||
if (cgpu->api != &opencl_api)
|
||||
continue;
|
||||
/* Use only one thread per device to determine if the GPU is healthy */
|
||||
@ -4256,6 +4304,7 @@ static void *watchdog_thread(void __maybe_unused *userdata)
|
||||
temp, fanpercent, fanspeed, engineclock, memclock, vddc, activity, powertune);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Thread is waiting on getwork or disabled */
|
||||
if (thr->getwork || *denable == DEV_DISABLED)
|
||||
continue;
|
||||
@ -4303,8 +4352,9 @@ static void *watchdog_thread(void __maybe_unused *userdata)
|
||||
if (opt_restart)
|
||||
reinit_device(thr->cgpu);
|
||||
}
|
||||
#endif /* HAVE_OPENCL */
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
return NULL;
|
||||
@ -4730,6 +4780,10 @@ int main(int argc, char *argv[])
|
||||
rwlock_init(&blk_lock);
|
||||
rwlock_init(&netacc_lock);
|
||||
|
||||
mutex_init(&lp_lock);
|
||||
if (unlikely(pthread_cond_init(&lp_cond, NULL)))
|
||||
quit(1, "Failed to pthread_cond_init lp_cond");
|
||||
|
||||
sprintf(packagename, "%s %s", PACKAGE, VERSION);
|
||||
|
||||
#ifdef WANT_CPUMINE
|
||||
|
@ -2,7 +2,7 @@
|
||||
##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--##
|
||||
m4_define([v_maj], [2])
|
||||
m4_define([v_min], [4])
|
||||
m4_define([v_mic], [1])
|
||||
m4_define([v_mic], [2])
|
||||
##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--##
|
||||
m4_define([v_ver], [v_maj.v_min.v_mic])
|
||||
m4_define([lt_rev], m4_eval(v_maj + v_min))
|
||||
|
30
debian/changelog
vendored
30
debian/changelog
vendored
@ -1,3 +1,33 @@
|
||||
cgminer (2.4.2-1) stable; urgency=medium
|
||||
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
|
||||
|
||||
-- nushor <nushor11@gmail.com> Sun, 03 Jun 2012 22:02:03 -0500
|
||||
|
||||
cgminer (2.4.1-1) stable; urgency=low
|
||||
Version 2.4.1-1 - May 6, 2012
|
||||
- In the unlikely event of finding a block, display the block solved count with
|
||||
|
1
debian/patches/series
vendored
1
debian/patches/series
vendored
@ -1 +0,0 @@
|
||||
v2.4.1
|
64422
debian/patches/v2.4.2
vendored
Normal file
64422
debian/patches/v2.4.2
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@ -37,6 +37,9 @@
|
||||
#include "elist.h"
|
||||
#include "miner.h"
|
||||
|
||||
#define BITFORCE_SLEEP_US 4500000
|
||||
#define BITFORCE_SLEEP_MS (BITFORCE_SLEEP_US/1000)
|
||||
#define BITFORCE_TIMEOUT_MS 30000
|
||||
|
||||
struct device_api bitforce_api;
|
||||
|
||||
@ -103,7 +106,7 @@ static bool bitforce_detect_one(const char *devpath)
|
||||
|
||||
int fdDev = BFopen(devpath);
|
||||
if (unlikely(fdDev == -1)) {
|
||||
applog(LOG_DEBUG, "BitForce Detect: Failed to open %s", devpath);
|
||||
applog(LOG_ERR, "BitForce Detect: Failed to open %s", devpath);
|
||||
return false;
|
||||
}
|
||||
BFwrite(fdDev, "ZGX", 3);
|
||||
@ -114,7 +117,7 @@ static bool bitforce_detect_one(const char *devpath)
|
||||
}
|
||||
BFclose(fdDev);
|
||||
if (unlikely(!strstr(pdevbuf, "SHA256"))) {
|
||||
applog(LOG_DEBUG, "BitForce Detect: Didn't recognise BitForce on %s", devpath);
|
||||
applog(LOG_ERR, "BitForce Detect: Didn't recognise BitForce on %s", devpath);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -130,6 +133,8 @@ static bool bitforce_detect_one(const char *devpath)
|
||||
s[0] = '\0';
|
||||
bitforce->name = strdup(pdevbuf + 7);
|
||||
}
|
||||
|
||||
mutex_init(&bitforce->dev_lock);
|
||||
|
||||
return add_cgpu(bitforce);
|
||||
}
|
||||
@ -264,11 +269,57 @@ static bool bitforce_thread_prepare(struct thr_info *thr)
|
||||
return true;
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
static uint64_t bitforce_get_temp(struct cgpu_info *bitforce)
|
||||
=======
|
||||
static bool bitforce_init(struct cgpu_info *bitforce)
|
||||
{
|
||||
int fdDev = bitforce->device_fd;
|
||||
char *devpath = bitforce->device_path;
|
||||
char pdevbuf[0x100];
|
||||
char *s;
|
||||
|
||||
BFclose(fdDev);
|
||||
|
||||
fdDev = BFopen(devpath);
|
||||
if (unlikely(fdDev == -1)) {
|
||||
applog(LOG_ERR, "BitForce init: Failed to open %s", devpath);
|
||||
return false;
|
||||
}
|
||||
|
||||
bitforce->device_fd = fdDev;
|
||||
|
||||
mutex_lock(&bitforce->dev_lock);
|
||||
BFwrite(fdDev, "ZGX", 3);
|
||||
BFgets(pdevbuf, sizeof(pdevbuf), fdDev);
|
||||
mutex_unlock(&bitforce->dev_lock);
|
||||
|
||||
if (unlikely(!pdevbuf[0])) {
|
||||
applog(LOG_ERR, "Error reading from BitForce (ZGX)");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (unlikely(!strstr(pdevbuf, "SHA256"))) {
|
||||
applog(LOG_ERR, "BitForce init: Didn't recognise BitForce on %s", devpath);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (likely((!memcmp(pdevbuf, ">>>ID: ", 7)) && (s = strstr(pdevbuf + 3, ">>>"))))
|
||||
{
|
||||
s[0] = '\0';
|
||||
bitforce->name = strdup(pdevbuf + 7);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool bitforce_get_temp(struct cgpu_info *bitforce)
|
||||
>>>>>>> origin/master
|
||||
{
|
||||
int fdDev = bitforce->device_fd;
|
||||
char pdevbuf[0x100];
|
||||
char *s;
|
||||
<<<<<<< HEAD
|
||||
|
||||
BFwrite(fdDev, "ZLX", 3);
|
||||
BFgets(pdevbuf, sizeof(pdevbuf), fdDev);
|
||||
@ -284,53 +335,89 @@ static uint64_t bitforce_get_temp(struct cgpu_info *bitforce)
|
||||
applog(LOG_WARNING, "Hit thermal cutoff limit on %s %d, disabling!", bitforce->api->name, bitforce->device_id);
|
||||
bitforce->deven = DEV_RECOVER;
|
||||
|
||||
=======
|
||||
|
||||
mutex_lock(&bitforce->dev_lock);
|
||||
BFwrite(fdDev, "ZLX", 3);
|
||||
BFgets(pdevbuf, sizeof(pdevbuf), fdDev);
|
||||
mutex_unlock(&bitforce->dev_lock);
|
||||
|
||||
if (unlikely(!pdevbuf[0])) {
|
||||
applog(LOG_ERR, "Error reading temp from BitForce (ZLX)");
|
||||
return false;
|
||||
}
|
||||
if ((!strncasecmp(pdevbuf, "TEMP", 4)) && (s = strchr(pdevbuf + 4, ':'))) {
|
||||
float temp = strtof(s + 1, NULL);
|
||||
if (temp > 0) {
|
||||
bitforce->temp = temp;
|
||||
if (temp > bitforce->cutofftemp) {
|
||||
applog(LOG_WARNING, "Hit thermal cutoff limit on %s %d, disabling!", bitforce->api->name, bitforce->device_id);
|
||||
bitforce->deven = DEV_RECOVER;
|
||||
|
||||
>>>>>>> origin/master
|
||||
bitforce->device_last_not_well = time(NULL);
|
||||
bitforce->device_not_well_reason = REASON_DEV_THERMAL_CUTOFF;
|
||||
bitforce->dev_thermal_cutoff_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
<<<<<<< HEAD
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static uint64_t bitforce_send_work(struct cgpu_info *bitforce, struct work *work)
|
||||
{
|
||||
=======
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool bitforce_send_work(struct thr_info *thr, struct work *work)
|
||||
{
|
||||
struct cgpu_info *bitforce = thr->cgpu;
|
||||
>>>>>>> origin/master
|
||||
int fdDev = bitforce->device_fd;
|
||||
char pdevbuf[0x100];
|
||||
unsigned char ob[61] = ">>>>>>>>12345678901234567890123456789012123456789012>>>>>>>>";
|
||||
char *s;
|
||||
|
||||
mutex_lock(&bitforce->dev_lock);
|
||||
BFwrite(fdDev, "ZDX", 3);
|
||||
BFgets(pdevbuf, sizeof(pdevbuf), fdDev);
|
||||
if (unlikely(!pdevbuf[0])) {
|
||||
applog(LOG_ERR, "Error reading from BitForce (ZDX)");
|
||||
return 0;
|
||||
mutex_unlock(&bitforce->dev_lock);
|
||||
return false;
|
||||
}
|
||||
if (unlikely(pdevbuf[0] != 'O' || pdevbuf[1] != 'K')) {
|
||||
applog(LOG_ERR, "BitForce ZDX reports: %s", pdevbuf);
|
||||
return 0;
|
||||
mutex_unlock(&bitforce->dev_lock);
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(ob + 8, work->midstate, 32);
|
||||
memcpy(ob + 8 + 32, work->data + 64, 12);
|
||||
|
||||
BFwrite(fdDev, ob, 60);
|
||||
if (opt_debug) {
|
||||
s = bin2hex(ob + 8, 44);
|
||||
applog(LOG_DEBUG, "BitForce block data: %s", s);
|
||||
free(s);
|
||||
}
|
||||
|
||||
BFgets(pdevbuf, sizeof(pdevbuf), fdDev);
|
||||
mutex_unlock(&bitforce->dev_lock);
|
||||
if (unlikely(!pdevbuf[0])) {
|
||||
applog(LOG_ERR, "Error reading from BitForce (block data)");
|
||||
return 0;
|
||||
return false;
|
||||
}
|
||||
if (unlikely(pdevbuf[0] != 'O' || pdevbuf[1] != 'K')) {
|
||||
applog(LOG_ERR, "BitForce block data reports: %s", pdevbuf);
|
||||
return 0;
|
||||
return false;
|
||||
}
|
||||
<<<<<<< HEAD
|
||||
return 1;
|
||||
=======
|
||||
return true;
|
||||
>>>>>>> origin/master
|
||||
}
|
||||
|
||||
static uint64_t bitforce_get_result(struct thr_info *thr, struct work *work)
|
||||
@ -339,38 +426,65 @@ static uint64_t bitforce_get_result(struct thr_info *thr, struct work *work)
|
||||
int fdDev = bitforce->device_fd;
|
||||
|
||||
char pdevbuf[0x100];
|
||||
<<<<<<< HEAD
|
||||
int i;
|
||||
char *pnoncebuf;
|
||||
uint32_t nonce;
|
||||
|
||||
i = 5000;
|
||||
while (i < 10000) {
|
||||
=======
|
||||
char *pnoncebuf;
|
||||
uint32_t nonce;
|
||||
int i;
|
||||
|
||||
i = BITFORCE_SLEEP_MS;
|
||||
while (i < BITFORCE_TIMEOUT_MS) {
|
||||
mutex_lock(&bitforce->dev_lock);
|
||||
>>>>>>> origin/master
|
||||
BFwrite(fdDev, "ZFX", 3);
|
||||
BFgets(pdevbuf, sizeof(pdevbuf), fdDev);
|
||||
mutex_unlock(&bitforce->dev_lock);
|
||||
if (unlikely(!pdevbuf[0])) {
|
||||
applog(LOG_ERR, "Error reading from BitForce (ZFX)");
|
||||
mutex_unlock(&bitforce->dev_lock);
|
||||
return 0;
|
||||
}
|
||||
if (pdevbuf[0] != 'B')
|
||||
break;
|
||||
break;
|
||||
usleep(10000);
|
||||
i += 10;
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
if (i >= 10000) {
|
||||
applog(LOG_DEBUG, "BitForce took longer than 10s");
|
||||
return 0;
|
||||
}
|
||||
=======
|
||||
if (i >= BITFORCE_TIMEOUT_MS) {
|
||||
applog(LOG_ERR, "BitForce took longer than 30s");
|
||||
bitforce->device_last_not_well = time(NULL);
|
||||
bitforce->device_not_well_reason = REASON_THREAD_ZERO_HASH;
|
||||
bitforce->thread_zero_hash_count++;
|
||||
return 1;
|
||||
}
|
||||
>>>>>>> origin/master
|
||||
|
||||
applog(LOG_DEBUG, "BitForce waited %dms until %s\n", i, pdevbuf);
|
||||
work->blk.nonce = 0xffffffff;
|
||||
if (pdevbuf[2] == '-')
|
||||
return 0xffffffff; /* No valid nonce found */
|
||||
<<<<<<< HEAD
|
||||
else if (pdevbuf[0] == 'I')
|
||||
return 0x1; /* Device idle */
|
||||
=======
|
||||
else if (pdevbuf[0] == 'I')
|
||||
return 1; /* Device idle */
|
||||
>>>>>>> origin/master
|
||||
else if (strncasecmp(pdevbuf, "NONCE-FOUND", 11)) {
|
||||
applog(LOG_ERR, "BitForce result reports: %s", pdevbuf);
|
||||
return 0;
|
||||
applog(LOG_WARNING, "BitForce result reports: %s", pdevbuf);
|
||||
return 1;
|
||||
}
|
||||
|
||||
pnoncebuf = &pdevbuf[12];
|
||||
@ -413,11 +527,53 @@ static uint64_t bitforce_scanhash(struct thr_info *thr, struct work *work, uint6
|
||||
|
||||
}
|
||||
|
||||
static void bitforce_shutdown(struct thr_info *thr)
|
||||
{
|
||||
struct cgpu_info *bitforce = thr->cgpu;
|
||||
int fdDev = bitforce->device_fd;
|
||||
|
||||
BFclose(fdDev);
|
||||
}
|
||||
|
||||
static uint64_t bitforce_scanhash(struct thr_info *thr, struct work *work, uint64_t __maybe_unused max_nonce)
|
||||
{
|
||||
struct cgpu_info *bitforce = thr->cgpu;
|
||||
bool dev_enabled = (bitforce->deven == DEV_ENABLED);
|
||||
static enum dev_enable last_dev_state = DEV_ENABLED;
|
||||
|
||||
if (bitforce->deven == DEV_DISABLED) {
|
||||
bitforce_shutdown(thr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// if device has just gone from disabled to enabled, re-initialise it
|
||||
if (last_dev_state == DEV_DISABLED && dev_enabled)
|
||||
bitforce_init(bitforce);
|
||||
|
||||
if (dev_enabled)
|
||||
if (!bitforce_send_work(thr, work))
|
||||
return 0;
|
||||
|
||||
usleep(BITFORCE_SLEEP_US);
|
||||
|
||||
if (dev_enabled)
|
||||
return bitforce_get_result(thr, work);
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
|
||||
static bool bitforce_get_stats(struct cgpu_info *bitforce)
|
||||
{
|
||||
return bitforce_get_temp(bitforce);
|
||||
}
|
||||
|
||||
struct device_api bitforce_api = {
|
||||
.dname = "bitforce",
|
||||
.name = "BFL",
|
||||
.api_detect = bitforce_detect,
|
||||
.get_statline_before = get_bitforce_statline_before,
|
||||
.get_stats = bitforce_get_stats,
|
||||
.thread_prepare = bitforce_thread_prepare,
|
||||
.scanhash = bitforce_scanhash,
|
||||
.thread_shutdown = bitforce_shutdown
|
||||
};
|
||||
|
518
driver-icarus.c
518
driver-icarus.c
@ -51,17 +51,136 @@
|
||||
#include "elist.h"
|
||||
#include "miner.h"
|
||||
|
||||
// This is valid for a standard Icarus Rev 3
|
||||
// Assuming each hash pair takes 5.26ns then a whole nonce range would take 11.3s
|
||||
// Giving a little leaway 11.1s would be best
|
||||
//#define ICARUS_READ_COUNT_DEFAULT 111
|
||||
#define ICARUS_READ_COUNT_DEFAULT 80
|
||||
// The serial I/O speed - Linux uses a define 'B115200' in bits/termios.h
|
||||
#define ICARUS_IO_SPEED 115200
|
||||
|
||||
// 2 x 11.1 / (5.26 x 10^-9)
|
||||
//#define ESTIMATE_HASHES 0xFB90365E
|
||||
// The size of a successful nonce read
|
||||
#define ICARUS_READ_SIZE 4
|
||||
|
||||
// This is the 8s value
|
||||
#define ESTIMATE_HASHES 0xB54E9147
|
||||
// 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);
|
||||
|
||||
#define ICARUS_READ_TIME ((double)ICARUS_READ_SIZE * (double)8.0 / (double)ICARUS_IO_SPEED)
|
||||
|
||||
// Fraction of a second, USB timeout is measured in
|
||||
// i.e. 10 means 1/10 of a second
|
||||
#define TIME_FACTOR 10
|
||||
// In Linux it's 10 per second, thus value = 10/TIME_FACTOR =
|
||||
#define LINUX_TIMEOUT_VALUE 1
|
||||
// In Windows it's 1000 per second, thus value = 1000/TIME_FACTOR =
|
||||
#define WINDOWS_TIMEOUT_VALUE 100
|
||||
|
||||
// In timing mode: Default starting value until an estimate can be obtained
|
||||
// 5 seconds allows for up to a ~840MH/s device
|
||||
#define ICARUS_READ_COUNT_TIMING (5 * TIME_FACTOR)
|
||||
|
||||
// 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)
|
||||
#define ICARUS_REV3_HASH_TIME 0.0000000026316
|
||||
#define NANOSEC 1000000000.0
|
||||
|
||||
// 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) 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 started
|
||||
// We set 2) to 'the calculated estimate' - 1
|
||||
// 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 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 above 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_LONG_STR = "long";
|
||||
static const char *MODE_VALUE_STR = "value";
|
||||
static const char *MODE_UNKNOWN_STR = "unknown";
|
||||
|
||||
struct ICARUS_INFO {
|
||||
// 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;
|
||||
|
||||
// seconds per Hash
|
||||
double Hs;
|
||||
int read_count;
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
// One for each possible device
|
||||
static struct ICARUS_INFO *icarus_info[MAX_DEVICES];
|
||||
|
||||
struct device_api icarus_api;
|
||||
|
||||
@ -98,7 +217,7 @@ static int icarus_open(const char *devpath)
|
||||
ISTRIP | INLCR | IGNCR | ICRNL | IXON);
|
||||
my_termios.c_oflag &= ~OPOST;
|
||||
my_termios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
|
||||
my_termios.c_cc[VTIME] = 1; /* block 0.1 second */
|
||||
my_termios.c_cc[VTIME] = LINUX_TIMEOUT_VALUE; /* how long to block */
|
||||
my_termios.c_cc[VMIN] = 0;
|
||||
tcsetattr(serialfd, TCSANOW, &my_termios);
|
||||
|
||||
@ -119,7 +238,7 @@ static int icarus_open(const char *devpath)
|
||||
comCfg.dwSize = sizeof(COMMCONFIG);
|
||||
comCfg.wVersion = 1;
|
||||
comCfg.dcb.DCBlength = sizeof(DCB);
|
||||
comCfg.dcb.BaudRate = 115200;
|
||||
comCfg.dcb.BaudRate = ICARUS_IO_SPEED;
|
||||
comCfg.dcb.fBinary = 1;
|
||||
comCfg.dcb.fDtrControl = DTR_CONTROL_ENABLE;
|
||||
comCfg.dcb.fRtsControl = RTS_CONTROL_ENABLE;
|
||||
@ -127,42 +246,57 @@ static int icarus_open(const char *devpath)
|
||||
|
||||
SetCommConfig(hSerial, &comCfg, sizeof(comCfg));
|
||||
|
||||
// block 0.1 second
|
||||
COMMTIMEOUTS cto = {100, 0, 100, 0, 100};
|
||||
// How long to block
|
||||
COMMTIMEOUTS cto = {WINDOWS_TIMEOUT_VALUE, 0, WINDOWS_TIMEOUT_VALUE, 0, WINDOWS_TIMEOUT_VALUE};
|
||||
SetCommTimeouts(hSerial, &cto);
|
||||
|
||||
return _open_osfhandle((LONG)hSerial, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
static int icarus_gets(unsigned char *buf, size_t bufLen, int fd, int thr_id, int read_count)
|
||||
static int icarus_gets(unsigned char *buf, int fd, struct timeval *tv_finish, int thr_id, int read_count)
|
||||
{
|
||||
ssize_t ret = 0;
|
||||
int rc = 0;
|
||||
int read_amount = ICARUS_READ_SIZE;
|
||||
bool first = true;
|
||||
|
||||
while (bufLen) {
|
||||
// Read reply 1 byte at a time to get earliest tv_finish
|
||||
while (true) {
|
||||
ret = read(fd, buf, 1);
|
||||
if (ret == 1) {
|
||||
bufLen--;
|
||||
buf++;
|
||||
|
||||
if (first)
|
||||
gettimeofday(tv_finish, NULL);
|
||||
|
||||
if (ret >= read_amount)
|
||||
return 0;
|
||||
|
||||
if (ret > 0) {
|
||||
buf += ret;
|
||||
read_amount -= ret;
|
||||
first = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
rc++;
|
||||
if (rc >= read_count) {
|
||||
applog(LOG_DEBUG,
|
||||
"Icarus Read: No data in %.2f seconds", (float)(rc/10.0f));
|
||||
if (opt_debug) {
|
||||
applog(LOG_DEBUG,
|
||||
"Icarus Read: No data in %.2f seconds",
|
||||
(float)rc/(float)TIME_FACTOR);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (thr_id >= 0 && work_restart[thr_id].restart) {
|
||||
applog(LOG_DEBUG,
|
||||
"Icarus Read: Work restart at %.2f seconds", (float)(rc/10.0f));
|
||||
if (opt_debug) {
|
||||
applog(LOG_DEBUG,
|
||||
"Icarus Read: Work restart at %.2f seconds",
|
||||
(float)(rc)/(float)TIME_FACTOR);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int icarus_write(int fd, const void *buf, size_t bufLen)
|
||||
@ -178,8 +312,110 @@ static int icarus_write(int fd, const void *buf, size_t bufLen)
|
||||
|
||||
#define icarus_close(fd) close(fd)
|
||||
|
||||
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(struct cgpu_info *icarus)
|
||||
{
|
||||
struct ICARUS_INFO *info = icarus_info[icarus->device_id];
|
||||
double Hs;
|
||||
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 < icarus->device_id; 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';
|
||||
}
|
||||
|
||||
info->Hs = 0;
|
||||
info->read_count = 0;
|
||||
|
||||
if (strcasecmp(buf, MODE_SHORT_STR) == 0) {
|
||||
info->Hs = ICARUS_REV3_HASH_TIME;
|
||||
info->read_count = ICARUS_READ_COUNT_TIMING;
|
||||
|
||||
info->timing_mode = MODE_SHORT;
|
||||
info->do_icarus_timing = true;
|
||||
} else if (strcasecmp(buf, MODE_LONG_STR) == 0) {
|
||||
info->Hs = ICARUS_REV3_HASH_TIME;
|
||||
info->read_count = ICARUS_READ_COUNT_TIMING;
|
||||
|
||||
info->timing_mode = MODE_LONG;
|
||||
info->do_icarus_timing = true;
|
||||
} else if ((Hs = atof(buf)) != 0) {
|
||||
info->Hs = Hs / NANOSEC;
|
||||
info->fullnonce = info->Hs * (((double)0xffffffff) + 1);
|
||||
|
||||
if ((eq = strchr(buf, '=')) != NULL)
|
||||
info->read_count = atoi(eq+1);
|
||||
|
||||
if (info->read_count < 1)
|
||||
info->read_count = (int)(info->fullnonce * TIME_FACTOR) - 1;
|
||||
|
||||
if (unlikely(info->read_count < 1))
|
||||
info->read_count = 1;
|
||||
|
||||
info->timing_mode = MODE_VALUE;
|
||||
info->do_icarus_timing = false;
|
||||
} else {
|
||||
// Anything else in buf just uses DEFAULT mode
|
||||
|
||||
info->Hs = ICARUS_REV3_HASH_TIME;
|
||||
info->fullnonce = info->Hs * (((double)0xffffffff) + 1);
|
||||
|
||||
if ((eq = strchr(buf, '=')) != NULL)
|
||||
info->read_count = atoi(eq+1);
|
||||
|
||||
if (info->read_count < 1)
|
||||
info->read_count = (int)(info->fullnonce * TIME_FACTOR) - 1;
|
||||
|
||||
info->timing_mode = MODE_DEFAULT;
|
||||
info->do_icarus_timing = false;
|
||||
}
|
||||
|
||||
info->min_data_count = MIN_DATA_COUNT;
|
||||
|
||||
applog(LOG_DEBUG, "Icarus: Init: %d mode=%s read_count=%d Hs=%e",
|
||||
icarus->device_id, timing_mode_str(info->timing_mode), info->read_count, info->Hs);
|
||||
|
||||
}
|
||||
|
||||
static bool icarus_detect_one(const char *devpath)
|
||||
{
|
||||
struct ICARUS_INFO *info;
|
||||
struct timeval tv_start, tv_finish;
|
||||
int fd;
|
||||
|
||||
@ -194,8 +430,9 @@ static bool icarus_detect_one(const char *devpath)
|
||||
"0000000087320b1a1426674f2fa722ce";
|
||||
|
||||
const char golden_nonce[] = "000187a2";
|
||||
const uint32_t golden_nonce_val = 0x000187a2;
|
||||
|
||||
unsigned char ob_bin[64], nonce_bin[4];
|
||||
unsigned char ob_bin[64], nonce_bin[ICARUS_READ_SIZE];
|
||||
char *nonce_hex;
|
||||
|
||||
if (total_devices == MAX_DEVICES)
|
||||
@ -212,8 +449,7 @@ static bool icarus_detect_one(const char *devpath)
|
||||
gettimeofday(&tv_start, NULL);
|
||||
|
||||
memset(nonce_bin, 0, sizeof(nonce_bin));
|
||||
icarus_gets(nonce_bin, sizeof(nonce_bin), fd, -1, 1);
|
||||
gettimeofday(&tv_finish, NULL);
|
||||
icarus_gets(nonce_bin, fd, &tv_finish, -1, 1);
|
||||
|
||||
icarus_close(fd);
|
||||
|
||||
@ -221,16 +457,16 @@ static bool icarus_detect_one(const char *devpath)
|
||||
if (nonce_hex) {
|
||||
if (strncmp(nonce_hex, golden_nonce, 8)) {
|
||||
applog(LOG_ERR,
|
||||
"Icarus Detect: "
|
||||
"Test failed at %s: get %s, should: %s",
|
||||
devpath, nonce_hex, golden_nonce);
|
||||
"Icarus Detect: "
|
||||
"Test failed at %s: get %s, should: %s",
|
||||
devpath, nonce_hex, golden_nonce);
|
||||
free(nonce_hex);
|
||||
return false;
|
||||
}
|
||||
applog(LOG_DEBUG,
|
||||
"Icarus Detect: "
|
||||
"Test succeeded at %s: got %s",
|
||||
devpath, nonce_hex);
|
||||
"Icarus Detect: "
|
||||
"Test succeeded at %s: got %s",
|
||||
devpath, nonce_hex);
|
||||
free(nonce_hex);
|
||||
} else
|
||||
return false;
|
||||
@ -244,7 +480,23 @@ static bool icarus_detect_one(const char *devpath)
|
||||
add_cgpu(icarus);
|
||||
|
||||
applog(LOG_INFO, "Found Icarus at %s, mark as %d",
|
||||
devpath, icarus->device_id);
|
||||
devpath, icarus->device_id);
|
||||
|
||||
if (icarus_info[icarus->device_id] == NULL) {
|
||||
icarus_info[icarus->device_id] = (struct ICARUS_INFO *)malloc(sizeof(struct ICARUS_INFO));
|
||||
if (unlikely(!(icarus_info[icarus->device_id])))
|
||||
quit(1, "Failed to malloc ICARUS_INFO");
|
||||
}
|
||||
|
||||
info = icarus_info[icarus->device_id];
|
||||
|
||||
// Initialise everything to zero for a new device
|
||||
memset(info, 0, sizeof(struct ICARUS_INFO));
|
||||
|
||||
info->golden_hashes = (golden_nonce_val & 0x7fffffff) << 1;
|
||||
timersub(&tv_finish, &tv_start, &(info->golden_tv));
|
||||
|
||||
set_timing_mode(icarus);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -295,11 +547,24 @@ static uint64_t icarus_scanhash(struct thr_info *thr, struct work *work,
|
||||
int fd;
|
||||
int ret;
|
||||
|
||||
unsigned char ob_bin[64], nonce_bin[4];
|
||||
char *ob_hex, *nonce_hex;
|
||||
struct ICARUS_INFO *info;
|
||||
|
||||
unsigned char ob_bin[64], nonce_bin[ICARUS_READ_SIZE];
|
||||
char *ob_hex;
|
||||
uint32_t nonce;
|
||||
uint32_t hash_count;
|
||||
uint64_t hash_count;
|
||||
struct timeval tv_start, tv_finish, elapsed;
|
||||
struct timeval tv_history_start, tv_history_finish;
|
||||
double Ti, Xi;
|
||||
int i;
|
||||
|
||||
struct ICARUS_HISTORY *history0, *history;
|
||||
int count;
|
||||
double Hs, W, fullnonce;
|
||||
int read_count;
|
||||
uint64_t estimate_hashes;
|
||||
uint32_t values;
|
||||
uint64_t hash_count_range;
|
||||
|
||||
elapsed.tv_sec = elapsed.tv_usec = 0;
|
||||
|
||||
@ -315,39 +580,49 @@ static uint64_t icarus_scanhash(struct thr_info *thr, struct work *work,
|
||||
tcflush(fd, TCOFLUSH);
|
||||
#endif
|
||||
ret = icarus_write(fd, ob_bin, sizeof(ob_bin));
|
||||
if (opt_debug)
|
||||
gettimeofday(&tv_start, NULL);
|
||||
if (ret)
|
||||
return 0; /* This should never happen */
|
||||
|
||||
gettimeofday(&tv_start, NULL);
|
||||
|
||||
if (opt_debug) {
|
||||
ob_hex = bin2hex(ob_bin, sizeof(ob_bin));
|
||||
if (ob_hex) {
|
||||
applog(LOG_DEBUG, "Icarus %d sent: %s",
|
||||
icarus->device_id, ob_hex);
|
||||
icarus->device_id, ob_hex);
|
||||
free(ob_hex);
|
||||
}
|
||||
}
|
||||
|
||||
/* Icarus will return 4 bytes nonces or nothing */
|
||||
/* Icarus will return 4 bytes (ICARUS_READ_SIZE) nonces or nothing */
|
||||
memset(nonce_bin, 0, sizeof(nonce_bin));
|
||||
ret = icarus_gets(nonce_bin, sizeof(nonce_bin), fd, thr_id,
|
||||
ICARUS_READ_COUNT_DEFAULT);
|
||||
|
||||
if (opt_debug)
|
||||
gettimeofday(&tv_finish, NULL);
|
||||
info = icarus_info[icarus->device_id];
|
||||
ret = icarus_gets(nonce_bin, fd, &tv_finish, thr_id, info->read_count);
|
||||
|
||||
work->blk.nonce = 0xffffffff;
|
||||
memcpy((char *)&nonce, nonce_bin, sizeof(nonce_bin));
|
||||
|
||||
// aborted before becoming idle, get new work
|
||||
if (nonce == 0 && ret) {
|
||||
if (nonce == 0 && ret) {
|
||||
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;
|
||||
|
||||
if (opt_debug) {
|
||||
timersub(&tv_finish, &tv_start, &elapsed);
|
||||
applog(LOG_DEBUG, "Icarus %d no nonce = 0x%08x hashes (%ld.%06lds)",
|
||||
icarus->device_id, ESTIMATE_HASHES, elapsed.tv_sec, elapsed.tv_usec);
|
||||
applog(LOG_DEBUG, "Icarus %d no nonce = 0x%08llx hashes (%ld.%06lds)",
|
||||
icarus->device_id, estimate_hashes,
|
||||
elapsed.tv_sec, elapsed.tv_usec);
|
||||
}
|
||||
return ESTIMATE_HASHES;
|
||||
|
||||
return estimate_hashes;
|
||||
}
|
||||
|
||||
#if !defined (__BIG_ENDIAN__) && !defined(MIPSEB)
|
||||
@ -356,28 +631,136 @@ static uint64_t icarus_scanhash(struct thr_info *thr, struct work *work,
|
||||
|
||||
submit_nonce(thr, work, nonce);
|
||||
|
||||
if (opt_debug) {
|
||||
timersub(&tv_finish, &tv_start, &elapsed);
|
||||
|
||||
nonce_hex = bin2hex(nonce_bin, sizeof(nonce_bin));
|
||||
if (nonce_hex) {
|
||||
applog(LOG_DEBUG, "Icarus %d returned (elapsed %ld.%06ld seconds): %s",
|
||||
icarus->device_id, elapsed.tv_sec, elapsed.tv_usec, nonce_hex);
|
||||
free(nonce_hex);
|
||||
}
|
||||
}
|
||||
|
||||
hash_count = (nonce & 0x7fffffff);
|
||||
if (hash_count++ == 0x7fffffff)
|
||||
hash_count = 0xffffffff;
|
||||
else
|
||||
hash_count <<= 1;
|
||||
|
||||
if (opt_debug)
|
||||
applog(LOG_DEBUG, "Icarus %d nonce = 0x%08x = 0x%08x hashes (%ld.%06lds)",
|
||||
icarus->device_id, nonce, hash_count, elapsed.tv_sec, elapsed.tv_usec);
|
||||
if (opt_debug || info->do_icarus_timing)
|
||||
timersub(&tv_finish, &tv_start, &elapsed);
|
||||
|
||||
return hash_count;
|
||||
if (opt_debug) {
|
||||
applog(LOG_DEBUG, "Icarus %d nonce = 0x%08x = 0x%08llx hashes (%ld.%06lds)",
|
||||
icarus->device_id, nonce, hash_count, elapsed.tv_sec, elapsed.tv_usec);
|
||||
}
|
||||
|
||||
// ignore possible end condition values
|
||||
if (info->do_icarus_timing && (nonce & 0x7fffffff) > 0x000fffff && (nonce & 0x7fffffff) < 0x7ff00000) {
|
||||
gettimeofday(&tv_history_start, NULL);
|
||||
|
||||
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)
|
||||
- ICARUS_READ_TIME;
|
||||
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_count 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_count = (int)(fullnonce * TIME_FACTOR) - 1;
|
||||
|
||||
info->Hs = Hs;
|
||||
info->read_count = read_count;
|
||||
|
||||
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, "Icarus %d Re-estimate: read_count=%d fullnonce=%fs history count=%d Hs=%e W=%e values=%d hash range=0x%08lx min data count=%u", icarus->device_id, read_count, fullnonce, count, Hs, W, values, hash_count_range, info->min_data_count);
|
||||
applog(LOG_WARNING, "Icarus %d Re-estimate: Hs=%e W=%e read_count=%d fullnonce=%.3fs",
|
||||
icarus->device_id, Hs, W, read_count, fullnonce);
|
||||
}
|
||||
info->history_count++;
|
||||
gettimeofday(&tv_history_finish, NULL);
|
||||
|
||||
timersub(&tv_history_finish, &tv_history_start, &tv_history_finish);
|
||||
timeradd(&tv_history_finish, &(info->history_time), &(info->history_time));
|
||||
}
|
||||
|
||||
return hash_count;
|
||||
}
|
||||
|
||||
static void icarus_api_stats(char *buf, struct cgpu_info *cgpu, bool isjson)
|
||||
{
|
||||
struct ICARUS_INFO *info = icarus_info[cgpu->device_id];
|
||||
|
||||
// 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'
|
||||
sprintf(buf, isjson
|
||||
? "\"read_count\":%d,\"fullnonce\":%f,\"count\":%d,\"Hs\":%.15f,\"W\":%f,\"total_values\":%u,\"range\":%"PRIu64",\"history_count\":%"PRIu64",\"history_time\":%f,\"min_data_count\":%u,\"timing_values\":%u"
|
||||
: "read_count=%d,fullnonce=%f,count=%d,Hs=%.15f,W=%f,total_values=%u,range=%"PRIu64",history_count=%"PRIu64",history_time=%f,min_data_count=%u,timing_values=%u",
|
||||
info->read_count, info->fullnonce,
|
||||
info->count, info->Hs, info->W,
|
||||
info->values, info->hash_count_range,
|
||||
info->history_count,
|
||||
(double)(info->history_time.tv_sec)
|
||||
+ ((double)(info->history_time.tv_usec))/((double)1000000),
|
||||
info->min_data_count, info->history[0].values);
|
||||
}
|
||||
|
||||
static void icarus_shutdown(struct thr_info *thr)
|
||||
@ -390,6 +773,7 @@ struct device_api icarus_api = {
|
||||
.dname = "icarus",
|
||||
.name = "ICA",
|
||||
.api_detect = icarus_detect,
|
||||
.get_api_stats = icarus_api_stats,
|
||||
.thread_prepare = icarus_prepare,
|
||||
.scanhash = icarus_scanhash,
|
||||
.thread_shutdown = icarus_shutdown,
|
||||
|
7
miner.h
7
miner.h
@ -235,6 +235,7 @@ struct device_api {
|
||||
void (*get_statline_before)(char*, struct cgpu_info*);
|
||||
void (*get_statline)(char*, struct cgpu_info*);
|
||||
void (*get_api_stats)(char*, struct cgpu_info*, bool);
|
||||
bool (*get_stats)(struct cgpu_info*);
|
||||
|
||||
// Thread-specific functions
|
||||
bool (*thread_prepare)(struct thr_info*);
|
||||
@ -366,6 +367,8 @@ struct cgpu_info {
|
||||
int dev_thermal_cutoff_count;
|
||||
|
||||
struct cgminer_stats cgminer_stats;
|
||||
|
||||
pthread_mutex_t dev_lock;
|
||||
};
|
||||
|
||||
extern bool add_cgpu(struct cgpu_info*);
|
||||
@ -515,6 +518,7 @@ extern bool opt_api_listen;
|
||||
extern bool opt_api_network;
|
||||
extern bool opt_delaynet;
|
||||
extern bool opt_restart;
|
||||
extern char *opt_icarus_timing;
|
||||
|
||||
extern pthread_rwlock_t netacc_lock;
|
||||
|
||||
@ -717,11 +721,13 @@ struct work {
|
||||
int thr_id;
|
||||
struct pool *pool;
|
||||
struct timeval tv_staged;
|
||||
|
||||
bool mined;
|
||||
bool clone;
|
||||
bool cloned;
|
||||
bool rolltime;
|
||||
bool longpoll;
|
||||
bool stale;
|
||||
|
||||
unsigned int work_block;
|
||||
int id;
|
||||
@ -740,6 +746,7 @@ extern void kill_work(void);
|
||||
extern void switch_pools(struct pool *selected);
|
||||
extern void remove_pool(struct pool *pool);
|
||||
extern void write_config(FILE *fcfg);
|
||||
extern void default_save_file(char *filename);
|
||||
extern void log_curses(int prio, const char *f, va_list ap);
|
||||
extern void clear_logwin(void);
|
||||
extern bool pool_tclear(struct pool *pool, bool *var);
|
||||
|
343
miner.php
343
miner.php
@ -2,6 +2,7 @@
|
||||
session_start();
|
||||
#
|
||||
global $miner, $port, $readonly, $notify, $rigs, $socktimeoutsec;
|
||||
global $checklastshare;
|
||||
#
|
||||
# Don't touch these 2 - see $rigs below
|
||||
$miner = null;
|
||||
@ -17,6 +18,13 @@ $readonly = false;
|
||||
# coz it doesn't have notify - it just shows the error status table
|
||||
$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 $rigs to an array of your cgminer rigs that are running
|
||||
# format: 'IP:Port' or 'Host:Port'
|
||||
# If you only have one rig, it will just show the detail of that rig
|
||||
@ -70,6 +78,7 @@ td.err { color:black; font-family:verdana,arial,sans; font-size:13pt; background
|
||||
td.warn { color:black; font-family:verdana,arial,sans; font-size:13pt; background:#ffb050 }
|
||||
td.sta { color:green; font-family:verdana,arial,sans; font-size:13pt; }
|
||||
td.tot { color:blue; font-family:verdana,arial,sans; font-size:13pt; background:#fff8f2 }
|
||||
td.lst { color:blue; font-family:verdana,arial,sans; font-size:13pt; background:#ffffdd }
|
||||
</style>
|
||||
</head><body bgcolor=#ecffff>
|
||||
<script type='text/javascript'>
|
||||
@ -222,140 +231,230 @@ function getparam($name, $both = false)
|
||||
return substr($a, 0, 1024);
|
||||
}
|
||||
#
|
||||
function fmt($section, $name, $value)
|
||||
function classlastshare($when, $alldata, $warnclass, $errorclass)
|
||||
{
|
||||
global $checklastshare;
|
||||
|
||||
if ($checklastshare === false)
|
||||
return '';
|
||||
|
||||
if ($when == 0)
|
||||
return '';
|
||||
|
||||
if (!isset($alldata['MHS av']))
|
||||
return '';
|
||||
|
||||
if (!isset($alldata['Last Share Time']))
|
||||
return '';
|
||||
|
||||
$expected = pow(2, 32) / ($alldata['MHS av'] * pow(10, 6));
|
||||
$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 fmt($section, $name, $value, $when, $alldata)
|
||||
{
|
||||
global $dfmt;
|
||||
|
||||
if ($alldata == null)
|
||||
$alldata = array();
|
||||
|
||||
$errorclass = ' class=err';
|
||||
$warnclass = ' class=warn';
|
||||
$lstclass = ' class=lst';
|
||||
$b = ' ';
|
||||
|
||||
$ret = $value;
|
||||
$class = '';
|
||||
|
||||
switch ($section.'.'.$name)
|
||||
{
|
||||
case 'GPU.Last Share Time':
|
||||
case 'PGA.Last Share Time':
|
||||
$ret = date('H:i:s', $value);
|
||||
break;
|
||||
case 'SUMMARY.Elapsed':
|
||||
$s = $value % 60;
|
||||
$value -= $s;
|
||||
$value /= 60;
|
||||
if ($value == 0)
|
||||
$ret = $s.'s';
|
||||
else
|
||||
if ($value === null)
|
||||
$ret = $b;
|
||||
else
|
||||
switch ($section.'.'.$name)
|
||||
{
|
||||
$m = $value % 60;
|
||||
$value -= $m;
|
||||
$value /= 60;
|
||||
if ($value == 0)
|
||||
$ret = sprintf("%dm$b%02ds", $m, $s);
|
||||
case 'GPU.Last Share Time':
|
||||
case 'PGA.Last Share Time':
|
||||
if ($value == 0
|
||||
|| (isset($alldata['Last Share Pool']) && $alldata['Last Share Pool'] == -1))
|
||||
{
|
||||
$ret = 'Never';
|
||||
$class = $warnclass;
|
||||
}
|
||||
else
|
||||
{
|
||||
$h = $value % 24;
|
||||
$value -= $h;
|
||||
$value /= 24;
|
||||
$ret = date('H:i:s', $value);
|
||||
$class = classlastshare($when, $alldata, $warnclass, $errorclass);
|
||||
}
|
||||
break;
|
||||
case 'POOL.Last Share Time':
|
||||
if ($value == 0)
|
||||
$ret = 'Never';
|
||||
else
|
||||
$ret = date('H:i:s d-M', $value);
|
||||
break;
|
||||
case 'GPU.Last Share Pool':
|
||||
case 'PGA.Last Share Pool':
|
||||
if ($value == -1)
|
||||
{
|
||||
$ret = 'None';
|
||||
$class = $warnclass;
|
||||
}
|
||||
break;
|
||||
case 'SUMMARY.Elapsed':
|
||||
$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("%dh$b%02dm$b%02ds", $h, $m, $s);
|
||||
$ret = sprintf("%dm$b%02ds", $m, $s);
|
||||
else
|
||||
{
|
||||
if ($value == 1)
|
||||
$days = '';
|
||||
$h = $value % 24;
|
||||
$value -= $h;
|
||||
$value /= 24;
|
||||
if ($value == 0)
|
||||
$ret = sprintf("%dh$b%02dm$b%02ds", $h, $m, $s);
|
||||
else
|
||||
$days = 's';
|
||||
|
||||
$ret = sprintf("%dday$days$b%02dh$b%02dm$b%02ds", $value, $h, $m, $s);
|
||||
{
|
||||
if ($value == 1)
|
||||
$days = '';
|
||||
else
|
||||
$days = 's';
|
||||
|
||||
$ret = sprintf("%dday$days$b%02dh$b%02dm$b%02ds", $value, $h, $m, $s);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'NOTIFY.Last Well':
|
||||
if ($value == '0')
|
||||
{
|
||||
$ret = 'Never';
|
||||
$class = $warnclass;
|
||||
}
|
||||
else
|
||||
$ret = date('H:i:s', $value);
|
||||
break;
|
||||
case 'NOTIFY.Last Not Well':
|
||||
if ($value == '0')
|
||||
$ret = 'Never';
|
||||
else
|
||||
{
|
||||
$ret = date('H:i:s', $value);
|
||||
$class = $errorclass;
|
||||
}
|
||||
break;
|
||||
case 'NOTIFY.Reason Not Well':
|
||||
if ($value != 'None')
|
||||
$class = $errorclass;
|
||||
break;
|
||||
case 'GPU.Utility':
|
||||
case 'PGA.Utility':
|
||||
case 'SUMMARY.Utility':
|
||||
$ret = $value.'/m';
|
||||
if ($value == 0)
|
||||
$class = $warnclass;
|
||||
break;
|
||||
case 'PGA.Temperature':
|
||||
$ret = $value.'°C';
|
||||
break;
|
||||
case 'GPU.Temperature':
|
||||
$ret = $value.'°C';
|
||||
case 'GPU.GPU Clock':
|
||||
case 'GPU.Memory Clock':
|
||||
case 'GPU.GPU Voltage':
|
||||
case 'GPU.GPU Activity':
|
||||
if ($value == 0)
|
||||
$class = $warnclass;
|
||||
break;
|
||||
case 'GPU.Fan Percent':
|
||||
if ($value == 0)
|
||||
$class = $warnclass;
|
||||
else
|
||||
{
|
||||
if ($value == 100)
|
||||
$class = $errorclass;
|
||||
else
|
||||
if ($value > 85)
|
||||
$class = $warnclass;
|
||||
}
|
||||
break;
|
||||
case 'GPU.Fan Speed':
|
||||
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 'GPU.MHS av':
|
||||
case 'PGA.MHS av':
|
||||
case 'SUMMARY.MHS av':
|
||||
case 'GPU.Total MH':
|
||||
case 'PGA.Total MH':
|
||||
case 'SUMMARY.Total MH':
|
||||
case 'SUMMARY.Getworks':
|
||||
case 'GPU.Accepted':
|
||||
case 'PGA.Accepted':
|
||||
case 'SUMMARY.Accepted':
|
||||
case 'GPU.Rejected':
|
||||
case 'PGA.Rejected':
|
||||
case 'SUMMARY.Rejected':
|
||||
case 'SUMMARY.Local Work':
|
||||
case 'POOL.Getworks':
|
||||
case 'POOL.Accepted':
|
||||
case 'POOL.Rejected':
|
||||
case 'POOL.Discarded':
|
||||
$parts = explode('.', $value, 2);
|
||||
if (count($parts) == 1)
|
||||
$dec = '';
|
||||
else
|
||||
$dec = '.'.$parts[1];
|
||||
$ret = number_format($parts[0]).$dec;
|
||||
break;
|
||||
case 'GPU.Status':
|
||||
case 'PGA.Status':
|
||||
case 'POOL.Status':
|
||||
if ($value != 'Alive')
|
||||
$class = $errorclass;
|
||||
break;
|
||||
case 'GPU.Enabled':
|
||||
case 'PGA.Enabled':
|
||||
if ($value != 'Y')
|
||||
$class = $warnclass;
|
||||
break;
|
||||
case 'STATUS.When':
|
||||
$ret = date($dfmt, $value);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'NOTIFY.Last Well':
|
||||
if ($value == '0')
|
||||
{
|
||||
$ret = 'Never';
|
||||
$class = $warnclass;
|
||||
}
|
||||
else
|
||||
$ret = date('H:i:s', $value);
|
||||
break;
|
||||
case 'NOTIFY.Last Not Well':
|
||||
if ($value == '0')
|
||||
$ret = 'Never';
|
||||
else
|
||||
{
|
||||
$ret = date('H:i:s', $value);
|
||||
$class = $errorclass;
|
||||
}
|
||||
break;
|
||||
case 'NOTIFY.Reason Not Well':
|
||||
if ($value != 'None')
|
||||
$class = $errorclass;
|
||||
break;
|
||||
case 'GPU.Utility':
|
||||
case 'PGA.Utility':
|
||||
case 'SUMMARY.Utility':
|
||||
$ret = $value.'/m';
|
||||
break;
|
||||
case 'PGA.Temperature':
|
||||
$ret = $value.'°C';
|
||||
break;
|
||||
case 'GPU.Temperature':
|
||||
$ret = $value.'°C';
|
||||
case 'GPU.Fan Speed':
|
||||
case 'GPU.Fan Percent':
|
||||
case 'GPU.GPU Clock':
|
||||
case 'GPU.Memory Clock':
|
||||
case 'GPU.GPU Voltage':
|
||||
case 'GPU.GPU Activity':
|
||||
if ($value == 0)
|
||||
$class = $warnclass;
|
||||
break;
|
||||
case 'GPU.MHS av':
|
||||
case 'PGA.MHS av':
|
||||
case 'SUMMARY.MHS av':
|
||||
case 'GPU.Total MH':
|
||||
case 'PGA.Total MH':
|
||||
case 'SUMMARY.Total MH':
|
||||
case 'SUMMARY.Getworks':
|
||||
case 'GPU.Accepted':
|
||||
case 'PGA.Accepted':
|
||||
case 'SUMMARY.Accepted':
|
||||
case 'GPU.Rejected':
|
||||
case 'PGA.Rejected':
|
||||
case 'SUMMARY.Rejected':
|
||||
case 'SUMMARY.Local Work':
|
||||
case 'POOL.Getworks':
|
||||
case 'POOL.Accepted':
|
||||
case 'POOL.Rejected':
|
||||
case 'POOL.Discarded':
|
||||
$parts = explode('.', $value, 2);
|
||||
if (count($parts) == 1)
|
||||
$dec = '';
|
||||
else
|
||||
$dec = '.'.$parts[1];
|
||||
$ret = number_format($parts[0]).$dec;
|
||||
break;
|
||||
case 'GPU.Status':
|
||||
case 'PGA.Status':
|
||||
case 'POOL.Status':
|
||||
if ($value != 'Alive')
|
||||
$class = $errorclass;
|
||||
break;
|
||||
case 'GPU.Enabled':
|
||||
case 'PGA.Enabled':
|
||||
if ($value != 'Y')
|
||||
$class = $warnclass;
|
||||
break;
|
||||
case 'STATUS.When':
|
||||
$ret = date($dfmt, $value);
|
||||
break;
|
||||
}
|
||||
|
||||
if ($section == 'NOTIFY' && substr($name, 0, 1) == '*' && $value != '0')
|
||||
$class = $errorclass;
|
||||
|
||||
if ($class == '' && $section != 'POOL')
|
||||
$class = classlastshare($when, $alldata, $lstclass, $lstclass);
|
||||
|
||||
return array($ret, $class);
|
||||
}
|
||||
#
|
||||
@ -390,6 +489,8 @@ function details($cmd, $list, $rig)
|
||||
global $poolcmd, $readonly;
|
||||
global $showndate;
|
||||
|
||||
$when = 0;
|
||||
|
||||
$stas = array('S' => 'Success', 'W' => 'Warning', 'I' => 'Informational', 'E' => 'Error', 'F' => 'Fatal');
|
||||
|
||||
echo $tablebegin;
|
||||
@ -408,7 +509,10 @@ function details($cmd, $list, $rig)
|
||||
echo '<tr>';
|
||||
echo '<td>Computer: '.$list['STATUS']['Description'].'</td>';
|
||||
if (isset($list['STATUS']['When']))
|
||||
{
|
||||
echo '<td>When: '.date($dfmt, $list['STATUS']['When']).'</td>';
|
||||
$when = $list['STATUS']['When'];
|
||||
}
|
||||
$sta = $list['STATUS']['STATUS'];
|
||||
echo '<td>Status: '.$stas[$sta].'</td>';
|
||||
echo '<td>Message: '.$list['STATUS']['Msg'].'</td>';
|
||||
@ -436,7 +540,7 @@ function details($cmd, $list, $rig)
|
||||
|
||||
foreach ($values as $name => $value)
|
||||
{
|
||||
list($showvalue, $class) = fmt($section, $name, $value);
|
||||
list($showvalue, $class) = fmt($section, $name, $value, $when, $values);
|
||||
echo "<td$class>$showvalue</td>";
|
||||
}
|
||||
|
||||
@ -587,6 +691,8 @@ function doforeach($cmd, $des, $sum, $head, $datetime)
|
||||
global $tablebegin, $tableend, $warnfont, $warnoff, $dfmt;
|
||||
global $rigerror;
|
||||
|
||||
$when = 0;
|
||||
|
||||
$header = $head;
|
||||
$anss = array();
|
||||
|
||||
@ -652,7 +758,7 @@ function doforeach($cmd, $des, $sum, $head, $datetime)
|
||||
else
|
||||
{
|
||||
if (isset($row[$name]))
|
||||
list($showvalue, $class) = fmt('STATUS', $name, $row[$name]);
|
||||
list($showvalue, $class) = fmt('STATUS', $name, $row[$name], $when, null);
|
||||
else
|
||||
{
|
||||
$class = '';
|
||||
@ -710,6 +816,10 @@ function doforeach($cmd, $des, $sum, $head, $datetime)
|
||||
|
||||
foreach ($anss as $rig => $ans)
|
||||
{
|
||||
$when = 0;
|
||||
if (isset($ans['STATUS']['When']))
|
||||
$when = $ans['STATUS']['When'];
|
||||
|
||||
foreach ($ans as $item => $row)
|
||||
{
|
||||
if ($item == 'STATUS')
|
||||
@ -733,12 +843,11 @@ function doforeach($cmd, $des, $sum, $head, $datetime)
|
||||
else
|
||||
{
|
||||
if (isset($row[$name]))
|
||||
list($showvalue, $class) = fmt($section, $name, $row[$name]);
|
||||
$value = $row[$name];
|
||||
else
|
||||
{
|
||||
$class = '';
|
||||
$showvalue = ' ';
|
||||
}
|
||||
$value = null;
|
||||
|
||||
list($showvalue, $class) = fmt($section, $name, $value, $when, $row);
|
||||
|
||||
if ($rig === 'total' and $class == '')
|
||||
$class = ' class=tot';
|
||||
|
Loading…
Reference in New Issue
Block a user