/////////////////////////////////////////////////////////////////// // Harvest entropy from audio I/O system. // In part distantly related to audio-entropyd.c by Damien Miller // Those parts Copyright 1999 Damien Miller // and licensed under the GNU Public License version 2 // Please see the file COPYING for more details. using namespace std; /* needed by some versions of the compiler */ #include #include #include #include "Getopt.h" #include extern "C" { // This should have been provided by unistd.h but it wasn't; // I have no idea why not: pid_t getpgid(pid_t pid); }; #include #include #include #include #include #include #include #include #include #include #include #include /* needed by some compilers */ #include #include #include "minmax.h" #include // Two defines needed to prevent bit-rot // due to poorly-documented changes to the ALSA API #define ALSA_PCM_OLD_HW_PARAMS_API #define ALSA_PCM_OLD_SW_PARAMS_API #include #include "sha1.h" #include "gauss.h" #include "probe.h" #include "dbuffer.h" #include "stretch.h" #include "turbid.h" #include "feed_krng.h" #include "daemon_fork.h" #include "alsactl.h" #ifndef SND_CTL_RDONLY /* backwards compatibility */ # warning You may want to use ./excl.patch to implement ALSA exclusive access. # define SND_CTL_RDONLY 0 #endif const double kT(1.38e-23 * (273.15 + 20)); const int FPP(1024); // frames per period const double twopi = 2 * pi; // reference level is saturation minus 15 dB const double ref_level(INT_MAX * pow(10, -15./20.)); // sine wave RMS is sqrt(2) less than peak: const double ref_level_2(ref_level/sqrt(2.0)); typedef int32_t datum; // all internal calculations are 32bit // a few non-constant global variables: string pidfile; int verbose=0; int d_xrun=0; // be verbose about underrun and overrun string progname; class alsa_dsp{ public: snd_pcm_t* phndl; // pcm handle snd_output_t* errlog; snd_pcm_hw_params_t *hwparams; int nchan; // samples per frame; 2 ==> stereo int rate; // frames per second int sbits; // significant bits // int sizeofsamp; // bytes per sample snd_pcm_format_t format; const char* moniker; void error(const char*, const int); int setup(const string carddevice, snd_pcm_stream_t stream, const int rate, const int lurking); // Constructor doesn't do much, // so it is cheap to allocate one that you're not // necessarily going to use: inline alsa_dsp(const char* mm) : errlog(0), moniker(mm) {} }; class Keeper{ public: const alsa_dsp* dsp; const int fpp; // frames per period valarray buf; int finbuf; // frames in buffer at the moment int nchan; Keeper(const alsa_dsp*, const int fsize); }; class snark { public: snark(); // constructor string card_device; // principal input, e.g. "0,0" string rando_fname; // fifo: principal output string stretch_fname; // fifo: stretched-orandom output string pid_fname; string wcheck_fname; // place to write raw data (checkfile) int nframe; int onepass; // exit after one pass int desired_rate; int channel_mask; double bandwidth; double kout; // output voltage scale, assuming sine-wave // ... which has RMS 3dB below ref_level double vmeter; // voltage (from voltmeter) double qsig; // quantum of significance double r_in; // input impedance double zref; // reference resistor double calfreq; double calival; double calamp; int daemonflag; int lurking; int feed_k_flag; string mixer_ctlfile; // not commandline options, but derived therefrom: int bufpipe[2]; // for double buffering int obs; // output buffer size (for double buffering) int rando_fd; // principal output int stretch_fd; // streched random output int wcheck_fd; string justcard; // the "card" part of that, before the comman double sps; // entropy (bits per sample) double snooze; // time to snooze between actions if not // actually writing to output fifo, // e.g. during calibration. //// functions void usage(const int err); void cmdline(int argc, char** argv); void open_devices(); void main_loop(alsa_dsp*, const double snooze); int read_stuff(alsa_dsp*, datum*); void analyze(alsa_dsp* dsp, valarray& cap_buffer, const int finbuf); void snark::printx( const char* name, const double foo_1, const double foo_2, double plogp, const alsa_dsp* dsp, const double vmeter ); }; // Forward references: void calout(const double ofreq, const double calival, const double gain, alsa_dsp* phndl, char** argv); void config_mixer(const string ctlfile, const string justcard); void write_pid_file(const string fname); void dummy(int signum); void trap_exit(int signum); void trap_detach(int signum); void drain(const char* draindev); void drain(const int fd); int isfile(const char* fname); // Functions void snark::usage(const int err) { (err ? cerr : cout) << "Harvest entropy from the audio I/O system.\n" "Usage: " << progname << " [options]\n\n" "Options:\n" "--buffer-size, -b [] Number of frames to read at a time. [= " << nframe << "].\n" "--card, -c [] Sound card, subdevice to use. [= " << card_device << "].\n" "--detach, -d Detach from terminal (daemonize).\n" "--mixer-ctl, -m [] Mixer set-up from alsactl file. " "[= " << mixer_ctlfile << "];\n" " -m \"\" ==> don't set up; just use inherited settings.\n" "--output-fifo -o [] Output fifo [= " << rando_fname << "].\n" "--stretch-fifo -s [] Stretched-random fifo [= " << stretch_fname << "].\n" "--pidfile-dir -P [] File to contain our PID [= " << pid_fname << "].\n" "--Amplitude, -A [] Calibrator amplitude / dB " "[= " << calamp << "] relative to -15dBFS.\n" "--Bandwidth, -B [] Bandwidth / Hz\n" "--Channel-mask -C [] 1==>left 2==>right 3==> stereo etc.\n" "--Frequency, -F [] Calibrator frequency / Hz (useful= 440).\n" "--Interval, -I [] Calibrator 2nd chan freq relative to 1st" " [= " << calival << "].\n" "--frame-rate, -N [] Audio measurement rate, frames per second;\n" " -N 0 ==> default ==> max rate supported by the hardware.\n" "--Vmeter, -V [] Voltmeter reading, for calibration.\n" "--Quantum-sig, -Q [] Quantum of significance, from calibration.\n" "--Rin, -R [] Input resistance, from calibration.\n" "--feed-kernel, -f Feed entropy to /dev/random\n" "--Zref, -Z [] External reference resistor / Ohms " "[= " << zref << "]\n" "--verbose, -v Print extra debugging info (-vv ==> even more).\n" "--lurk, -l Lurk (instead of exiting) if device busy.\n" "--write-check, -w [] Write checkfile containing one buffer of audio.\n" /////" -w \"\" ==> default ==> don't bother.\n" "--1pass, -1 Exit after one pass through main loop.\n" << endl; } // Just like snd_pcm_readi(), // except the buff is always of the type of datum (int32_t), // no matter what type the raw hardware uses. // Return value: // nonnegative: # of frames read // negative: error code int readi(const alsa_dsp* dsp, datum* buff, const int nframe){ if (dsp->format == SND_PCM_FORMAT_S32_LE) { return snd_pcm_readi(dsp->phndl, buff, nframe); } else if (dsp->format == SND_PCM_FORMAT_S16_LE) { valarray tmp(nframe * dsp->nchan); int rslt = snd_pcm_readi(dsp->phndl, &tmp[0], nframe); if (rslt <= 0) return rslt; int16_t* from (&tmp[0]); datum* to (buff); int fudge(1<<16); for (int ii = 0; ii < rslt * dsp->nchan; ii++){ *to++ = *from++ * fudge; } return rslt; } else { cerr << "Don't know how to convert dsp data from format " << snd_pcm_format_name(dsp->format) << endl; exeunt(1); } return 0; // defeat stupid compiler warning } // Just like snd_pcm_writei(), // except the buff is always of the type ofdatum (int32_t), // no matter what type the raw hardware uses. // Return value: // nonnegative: # of frames written // negative: error code int writei(const alsa_dsp* dsp, const datum* buff, const int nframe){ if (dsp->format == SND_PCM_FORMAT_S32_LE) { return snd_pcm_writei(dsp->phndl, buff, nframe); } else if (dsp->format == SND_PCM_FORMAT_S16_LE) { valarray tmp(nframe * dsp->nchan); int16_t* to (&tmp[0]); const datum* from (buff); const int fudge(1<<16); for (int ii = 0; ii < nframe * dsp->nchan; ii++){ *to++ = *from++ / fudge; } return snd_pcm_writei(dsp->phndl, &tmp[0], nframe); } else if (dsp->format == SND_PCM_FORMAT_S8) { valarray tmp(nframe * dsp->nchan); char* to (&tmp[0]); const datum* from (buff); const int fudge(1<<24); for (int ii = 0; ii < nframe * dsp->nchan; ii++){ *to++ = *from++ / fudge; } return snd_pcm_writei(dsp->phndl, &tmp[0], nframe); } else { cerr << "Don't know how to convert dsp data to format " << snd_pcm_format_name(dsp->format) << endl; exeunt(1); } return 0; // defeat stupid compiler warning } // Just like the library routine of the same name, except: // 1) The first arg is a "keeper", // which means that we will accept writes that are // not an integer multiple of the period. // 2) The second arg is a datum* (not void*). // We call writei() to convert things to the sample-size // of the actual device. snd_pcm_sframes_t snd_pcm_writei(Keeper& kk, const datum* buffer, snd_pcm_uframes_t nframes){ int rslt; int todo(nframes); int head(0); // frames taken from head of buffer const datum* mybuf(buffer); datum* start = &kk.buf[0]; if (kk.finbuf) { head = kk.fpp - kk.finbuf; memcpy(start + kk.nchan*kk.finbuf, buffer, kk.nchan*sizeof(datum)*head); rslt = writei(kk.dsp, start, kk.fpp); if (rslt <= 0) return rslt; // no change in kk if (rslt != kk.fpp) return -98; todo -= head; kk.finbuf = 0; } mybuf += kk.nchan*head; rslt = writei(kk.dsp, mybuf, todo); if (rslt < 0) return rslt; // some error, let caller deal with it if (rslt == 0) return head; // head got written, nothing more // OK, at this point rslt must be positive. int left(todo - rslt); // number of frames left to do.... if (!left) return nframes; // Nothing? Great! if (left < kk.fpp) { // can we keep it for next time? kk.finbuf = left; memcpy(start, mybuf + kk.nchan*rslt, kk.nchan*sizeof(datum)*left); return nframes; } // here with a short write that doesn't fit in our keep-buffer: kk.finbuf = 0; return head + rslt; } // Constructor Keeper::Keeper(const alsa_dsp* _dsp, const int _fpp) : dsp(_dsp), // remember the args fpp(_fpp), buf(dsp->nchan * _fpp), // allocate the buffer finbuf(0), nchan(_dsp->nchan) {} // no code, just initializers (above) // Constructor snark::snark() : card_device("0,0"), rando_fname("/dev/hrandom"), stretch_fname("/dev/srandom"), pid_fname("/var/run/" + progname + ".pid"), wcheck_fname(""), nframe(2048), onepass(0), desired_rate(0), channel_mask(-1), bandwidth(0), kout(0), vmeter(0), qsig(0), r_in(0), zref(1e6), // assume reference = 1 megohm calfreq(0), // default is no calibrator calival(4./3.), calamp(0.), // default amplitude (dB relative to -15dBFS) daemonflag(0), lurking(0), feed_k_flag(0), mixer_ctlfile(""), obs(512), // there are two buffers, so actual storage is twice this rando_fd(-1), stretch_fd(-1), wcheck_fd(-1), sps(0), snooze(.5) {} // no code, just the initializers (above) void alsa_dsp::error(const char* msg, const int val) { if (val != -999) { cerr << "Device won't accept parameter '" << msg << "'" << " value '" << val << "'" << endl; } else { cerr << msg << " for " << moniker << endl; } snd_pcm_hw_params_dump(hwparams, errlog); int sbits = snd_pcm_hw_params_get_sbits(hwparams); if (sbits > 0) { cerr << "Allegedly " << sbits << " significant bits per sample." << endl; } else { // Can't determine sigbits if the card is capable of // multiple sample-sizes and we haven't chosen one yet. cerr << "Can't (yet) determine sigbits per sample: " << snd_strerror(sbits) << endl; sbits = 0; } snd_pcm_close(phndl); snd_output_close(errlog); exeunt(1); } int alsa_dsp::setup(const string carddev, snd_pcm_stream_t stream, const int desired_rate, const int lurking ){ int err; // Connect error reporting to stderr if (!errlog) snd_output_stdio_attach(&errlog, stderr, 0); int mode = lurking ? 0 : SND_PCM_NONBLOCK; err = snd_pcm_open(&phndl, carddev.c_str(), stream, mode); if (err < 0){ fprintf(stderr, "Error opening soundcard %s for %s: %s\n", carddev.c_str(), moniker, snd_strerror(err)); snd_output_close(errlog); return 1; } // now that we've grabbed the device, without blocking // if it was busy, we now switch to blocking mode for all I/O: err = snd_pcm_nonblock(phndl, 0); // Allocate a hwparams struct... snd_pcm_hw_params_alloca(&hwparams); // ... and initialize it to the "wide open" ranges // allowed by phndl: err = snd_pcm_hw_params_any(phndl, hwparams); if (err < 0) error("Initialization error", -999); // User can force a dump of parameters, // here at a nice early stage: if (desired_rate == -1) error("Possibilities are:", -999); // Access method, interleaved or non-interleaved err = snd_pcm_hw_params_set_access(phndl, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED); if (err < 0) error("access method", SND_PCM_ACCESS_RW_INTERLEAVED); // Try various sample formats: do { // so I can use break instead of goto. format = SND_PCM_FORMAT_S32_LE; err = snd_pcm_hw_params_set_format(phndl, hwparams, format); if (err >= 0) break; format = SND_PCM_FORMAT_S16_LE; err = snd_pcm_hw_params_set_format(phndl, hwparams, format); if (err >= 0) break; format = SND_PCM_FORMAT_S8; err = snd_pcm_hw_params_set_format(phndl, hwparams, format); if (err >= 0) break; error("DSP error: format not S32_LE nor S16_LE nor S8", -999); } while (0); // execute the block once // Sample rate rate = desired_rate; if (!rate) rate = snd_pcm_hw_params_get_rate_max(hwparams, 0); err = snd_pcm_hw_params_set_rate(phndl, hwparams, rate, 0); if (err < 0) error("rate", rate); // Number of channels we want, stereo = 2 nchan = snd_pcm_hw_params_get_channels_max(hwparams); err = snd_pcm_hw_params_set_channels(phndl, hwparams, nchan); if (err < 0) error("channels", nchan); // The period size. For all practical purposes this is synonymous // to OSS/Free's fragment size. // Note that this in frames (frame = nr_channels * sample_width) // For example, a setup using FPP=1024, stereo, 16-bit format // gives a period size of 4096 bytes (1024 x 2 x 2 bytes). if (1) { err = snd_pcm_hw_params_set_period_size(phndl, hwparams, FPP, 0); if (err < 0) error("period-size", FPP); } // The number of periods we want to allocate, 4 is reasonable err = snd_pcm_hw_params_set_periods(phndl, hwparams, 4, 0); if (err < 0) error("#-of-periods", 4); // Finally set up our hardware with the selected values err = snd_pcm_hw_params(phndl, hwparams); if (err < 0) { fprintf(stderr, "Unable to set hardware parameter:\n"); snd_pcm_hw_params_dump(hwparams, errlog); return 2; } err = sbits = snd_pcm_hw_params_get_sbits(hwparams); if (sbits <= 0 || sbits > 32) { // if driver won't tell us how many are significant, // assume they all are: sbits = snd_pcm_samples_to_bytes(phndl, 8); fprintf(stderr, "Trouble %d determining sig bits; assuming %d\n", err, sbits); } if (verbose >= 2) { fprintf(stderr, ">>>>>> After setup (%s)\n", moniker); snd_pcm_hw_params_dump(hwparams, errlog); } // At this point you can start sending PCM data to the device return 0; } string crtRed ("\033[1;31m"); string crtNormal("\033[0;39m"); vector clean_me; void trapme(){ signal(SIGHUP, trap_exit); // hangup signal(SIGINT, trap_exit); // interrupt signal(SIGTERM, trap_exit); // terminate signal(SIGPIPE, trap_exit); // broken pipe signal(SIGUSR1, trap_detach); // daemon_fork kills parent signal(SIGCHLD, SIG_DFL); } double rms(const int* ptr, const int nn){ if (!nn) return 0; double val; double sum(0); for (int ii = 0; ii < nn; ii++){ val = ptr[ii]; sum += val*val; } sum /= nn; return sqrt(sum); } int main(int argc, char** argv) { double probe_level(rms(probe, nprobe)); if (abs(probe_level/ref_level - 1) > 1e-9) { cerr << "Probe_level / ref_level mismatch " << probe_level/ref_level - 1 << endl; // should never happen, unless bogus probe signal is // compiled in. exit(1); } progname = argv[0]; unsigned int where = progname.rfind('/'); // find final slash if (where != progname.npos) { progname = progname.substr(1+where); } snark foo; // holder for cmdline options ... foo.cmdline(argc, argv); // ... process them trapme(); // install trap handlers daemon_fork myfork; if (foo.daemonflag) myfork.setup(); // so we know the right pid write_pid_file(foo.pid_fname); // write it to file { int logflags = LOG_CONS | LOG_PID; if (!foo.daemonflag) logflags |= LOG_PERROR; openlog(progname.c_str(), logflags, LOG_DAEMON); } // Lurkers should detach early, so we don't hang the // init.d/turbid startup script. // Non-lurkers should not detach early, to increase the // chance of detecting errors and exiting with a meaningful // error status. if (foo.lurking) myfork.detach(); // be sure to setup the capture handler first, so // on a soundblaster16 capture gets the 16-bit DMA // channel and calout gets the 8-bit channel alsa_dsp in("capture"); int err; err = in.setup("hw:" + foo.card_device, SND_PCM_STREAM_CAPTURE, foo.desired_rate, foo.lurking); if (err) exit(1); // msgs have already been printed alsa_dsp out("calout"); if (foo.calamp < -200) foo.calfreq = 0; // too far down ==> off if (foo.calfreq) { // off ==> don't even initialize err = out.setup("hw:" + foo.card_device, SND_PCM_STREAM_PLAYBACK, foo.desired_rate, foo.lurking); if (err) exit(1); // msgs have already been printed #ifdef disgusting usleep(100000); // let hardware settle down ... // (SB16 has nasty transients right after initialization) #endif } // Be sure to configure mixer _after_ opening the pcm handlers, // because we want to hold exclusive access to the mixer (control // device) but snd_pcm_open wants to temporarily open the control // device for its own reasons. config_mixer(foo.mixer_ctlfile, foo.justcard); foo.open_devices(); // random-output, raw-output, etc. if (verbose) syslog(LOG_NOTICE, "starting %s", progname.c_str()); myfork.detach(); // Now that detach has done the setsid, we can fork our subprocesses: if (foo.sps && foo.rando_fd >= 0) clean_me.push_back(dbuffer(foo.bufpipe, foo.rando_fd, foo.obs)); if (foo.sps && foo.stretch_fd >= 0) clean_me.push_back(stretch(foo.bufpipe, foo.stretch_fd, foo.obs)); if (foo.calfreq) calout(foo.calfreq, foo.calival, foo.calamp, &out, argv); if (foo.feed_k_flag) feed_krng(foo.bufpipe, verbose, argv); // do the following close() after all consumers (dbuffer _and_ // feed_krng) have been started. It means the main program // will be killed via SIGPIPE if/when the last consumer dies. // Whether it is wise to keep running if dbuffer has died // is an unanswered question. close(foo.bufpipe[0]); foo.main_loop(&in, foo.snooze); exeunt(0); } string purify(const string foo) { string rslt; for (unsigned int ii = 0; ii < foo.length(); ii++) { char ch = foo[ii]; if (isupper(ch)) ch += 'a' - 'A'; if (isalnum(ch)) rslt += ch; } return rslt; } // return a string like "sb16.ctl" // based on ALSA's notion of the driver name string invent_ctlfile(const string justcard){ string driver("unknown"); snd_ctl_t* chndl; // control handle string xxx = "hw:" + justcard; int err = snd_ctl_open(&chndl, xxx.c_str(), SND_CTL_RDONLY); if (err < 0) { chndl = 0; cerr << "Error opening control channel to card " << justcard << endl; // proceed with driver = "unknown" } else { snd_ctl_card_info_t *info; snd_ctl_card_info_alloca(&info); err = snd_ctl_card_info(chndl, info); if (err < 0) { cerr << "Can't get card ID info: " << err << endl; } else { #if 0 // example: hw:0 cerr << "Name: " << snd_ctl_name(chndl) << endl; // example: card0 (but could be changed in modules.conf): cerr << "Card ID: " << snd_ctl_card_info_get_id(info) << endl; // example: M Audio Delta 1010 at 0x1400, irq 22 cerr << "Longname: " << snd_ctl_card_info_get_longname(info) << endl; // example: ICE1712 cerr << "Driver: " << snd_ctl_card_info_get_driver(info) << endl; cerr << "Mixername: " << snd_ctl_card_info_get_mixername(info) << endl; #endif driver = snd_ctl_card_info_get_driver(info); } } return purify(driver) + ".ctl"; } static string digits = "0123456789"; void dump_io() { for (int ii = 0; ii") { mixer_ctlfile = invent_ctlfile(justcard); }// else cmdline has explict ctlfile, use that if (mixer_ctlfile.length()) { char mx = mixer_ctlfile[0]; if (mx != '.' && mx != '/') { mixer_ctlfile = "/etc/" + progname + "/" + mixer_ctlfile; if (home != "/root") { mixer_ctlfile = home + mixer_ctlfile; } } // else . or / means use it verbatim } if(helpme) { usage(0); exit(0); // no need to exeunt() } if (verbose > 1) { cerr << "Bandwidth: " << bandwidth << endl; cerr << "R_in: " << r_in << endl; cerr << "Qsig: " << qsig << endl; } if (qsig && r_in && bandwidth) { double sigmaJ(sqrt(4 * kT * r_in * bandwidth)); sps = normEnt(0, 0, sigmaJ/qsig); if (verbose) cerr << "SigmaJ: " << sigmaJ << " Bits per sample: " << sps << endl; } }// cmdline void config_mixer(const string ctlfilestr, const string justcard){ if (!ctlfilestr.length()) return; const char* ctlfile = ctlfilestr.c_str(); int rslt = open(ctlfile, O_RDONLY); if (rslt < 0) { syslog(LOG_WARNING, "Could not open alsactl file '%s': %m " "... will use inherited mixer settings.", ctlfile); return; } close(rslt); rslt = load_state(ctlfile, justcard.c_str(), 1, 1); if (rslt) { syslog(LOG_ERR, "error in load_state"); exit(1); } } int open_fifo(const string fname) { if (!fname.length()) return -1; int rslt = mkfifo(fname.c_str(), 0644); if (rslt < 0) { if (errno != EEXIST) { syslog(LOG_ERR, "Couldn't create output fifo (%s): %m", fname.c_str()); exit(1); } else { // already exists struct stat statbuf; rslt = stat(fname.c_str(), &statbuf); if (rslt < 0) { syslog(LOG_ERR, "Couldn't stat output fifo (%s): %m", fname.c_str()); exit(1); } if (!S_ISFIFO(statbuf.st_mode)){ syslog(LOG_WARNING, "Output (%s) exists but isn't a fifo," " hope you know what you're doing.", fname.c_str()); // proceed } } } // here with named fifo existing // We (main process) quasi-open it for reading. // Otherwise the open (for writing) would block for lack // of a listener, which would be bad. // Note that we never actually do any reading, and this // doesn't interfere with other processes (e.g. independent // customers) who do the real reading. int junk = open(fname.c_str(), O_RDONLY | O_NONBLOCK); if (junk < 0) { syslog(LOG_WARNING, "Stretch-open failed '%s' -- %m", fname.c_str()); // don't exit -- maybe not fatal -- but weird. } int fd = open(fname.c_str(), O_WRONLY); if (fd < 0) { syslog(LOG_WARNING, "Couldn't open fifo (%s) for writing: %m", fname.c_str()); syslog(LOG_WARNING, "Proceeding in 'test' mode...."); } return fd; } // Open miscellaneous i/o devices: void snark::open_devices(){ if (wcheck_fname.length()) { wcheck_fd = open(wcheck_fname.c_str(), O_WRONLY|O_CREAT|O_TRUNC, 0600); if (wcheck_fd == -1) { syslog(LOG_ERR, "Couldn't open checkfile for writing: %m"); exit(1); } } if (rando_fname.length()) { rando_fd = open_fifo(rando_fname); // Also open the double-buffering pipe: int rslt = pipe(bufpipe); if (rslt < 0) { syslog(LOG_WARNING, "Can't open pipe for double-buffering: %m"); exit(1); } } if (stretch_fname.length()) { stretch_fd = open_fifo(stretch_fname); } } datum smash(double foo) { if (foo > INT_MAX) return INT_MAX; if (foo < -INT_MAX) return -INT_MAX; return datum(foo); } void calout(const double ofreq, const double calival, const double calamp, alsa_dsp* dsp, char** argv){ if (calamp < -200.) return; // no amplitude: don't even bother. if (!ofreq) return; int parent(getpid()); int cal_pid=fork(); clean_me.push_back(cal_pid); if (cal_pid) return; progname = "turbid-calibrator"; argv[0] = "turbid calibrator"; if (verbose) cerr << "Calout starts, ofreq = " << ofreq << endl; Keeper keep(dsp, FPP); double caltime = .2; // # of seconds in output buffer int calframe = int(dsp->rate * caltime); // # of frames double phpf0 = twopi * ofreq / dsp->rate; // phase per frame double phpf1 = phpf0 * calival; int bufsize(calframe * dsp->nchan); valarray calout_buffer(bufsize); datum* ptr; datum foo[2]; int flip; double gfactor(pow(10, calamp/20)); double amplitude(ref_level * gfactor); int phase(0); // main loop for (;;) { // loop over all buffers if ( getpgid (parent) < 0) { syslog(LOG_ERR, "Calibrator exiting due to abandonment"); exit(1); } // loop over all frames in this buffer: for (int ff = 0; ff < calframe; ff++) { if (ofreq < 0) { foo[0] = foo[1] = smash(gfactor * probe[phase]); phase++; if (phase >= nprobe) phase = 0; } else { foo[0] = smash(amplitude * sin(phase * phpf0)); foo[1] = smash(amplitude * sin(phase * phpf1)); phase++; } // loop over all channels in frame: ptr = &calout_buffer[ff * dsp->nchan]; for (int jj = 0; jj < dsp->nchan; jj++) { flip = jj & 1; *ptr++ = foo[flip]; } } datum* doit(&calout_buffer[0]); int todo = calframe; while (todo) { // keep trying to get this buffer out int rslt = snd_pcm_writei(keep, doit, todo); if (rslt == todo) break; // good! if (rslt > 0) { // short write todo -= rslt; doit += rslt; continue; } if (rslt == -EPIPE) { // explicit underrrun if (d_xrun) syslog(LOG_WARNING, "Calout underrun: wrote %d got %d", todo, rslt); snd_pcm_prepare(dsp->phndl); // recover from underruns continue; } // some error we don't understand syslog(LOG_WARNING, "Calout wrote %d got %d", todo, rslt); snd_pcm_prepare(dsp->phndl); // try to recover } } } // inrange set to 1 iff no clipping occurs // double histo_step(const datum* buf, const int bufsize, const int slots, const datum mean, const int stride, const int loss, int& inrange){ inrange = 0; int histo[slots]; for (int jj = 0; jj < slots; jj++){ histo[jj] = 0; } datum xx; const datum* from = buf; for (int ii = 0; ii < bufsize; ii++){ xx = *from; from += stride; xx /= loss; xx += slots/2; // the middle slot corresponds to zero voltage if (xx < 0) xx = 0; if (xx >= slots) xx = slots-1; histo[xx] += 1; } // OK, the histogram is built; now evalute p log p. double sumlog(0); int nj; // Note: // - sum[p log p] = - sum[(n/d) log (n/d)] where d = sum[n] // = (-1/d) (sum[n log n] - sum[n log d]) // = log d - (1/d) sum[n log n] for (int jj = 0; jj < slots; jj++){ nj = histo[jj]; if(nj) { sumlog += nj * log2(nj); } } if (verbose >= 3) printf("log2(bufsize) %g, sumlog %g, sumlog/bufsize %g, loss %d\n", log2(bufsize), sumlog, sumlog / bufsize, loss); if (histo[0] == 0 && histo[slots-1] == 0) inrange = 1; return log2(bufsize) - sumlog / bufsize; } // Calculate histogram. // This higher-level routine iterates checking for stuck bits. // double histogram(const datum* buf, const int bufsize, const int slots, const datum mean, const int stride, int sigbits){ double max_ent = -1.; double ent; int inrange; int loss(1); if (sigbits > 0) { loss = 1 << (bPB*sizeof(datum) - sigbits); } do { ent = histo_step(buf, bufsize, slots, mean, stride, loss, inrange); if (ent > max_ent) max_ent = ent; if (0 && verbose >= 3) fprintf(stderr, "Loss %d returns %g max %g\n", loss, ent, max_ent); loss *= 2; } while (!inrange); return max_ent; } void snark::printx( const char* name, const double foo_1, const double foo_2, double plogp, const alsa_dsp* dsp, const double vmeter ) { double stdev = sqrt(foo_2 - foo_1*foo_1); double dbdev(-999); if (stdev != 0.0) dbdev = 20 * log10(stdev / ref_level_2); printf("%6s: 1st %9.3g, 2nd %9.3g, stdev %9.3g " "= %7.3fdB, plp %6.3f\n", name, foo_1, foo_2, stdev, dbdev, plogp); if (dsp) { // for 24 bits of significance in a 32-bit representation, // rsig will equal 256 int rsig(1); if (dsp->sbits > 0) rsig = 1 << (bPB*sizeof(datum) - dsp->sbits); if (!qsig) { // no -Q flag on command line if (vmeter) cout << "--Q " << (vmeter * rsig) / stdev << " stdev " << stdev << endl; } else { double vin = stdev * qsig / rsig; double vratio = vin / vmeter; double rval(-1); if (vratio > 0 && vratio < 0.9) { rval = zref * vratio/(1-vratio); } int printit(0); if (!r_in) { cout << "--Rin " << rval; printit++; } if (vmeter) { cout << " Vin/Vmeter " << vratio ; printit++; } double vindiv(vin); if (r_in) { vindiv = vin * (r_in + zref) / r_in; if (vmeter) { cout << " Vin/Vmeter/divider " << vindiv/vmeter; printit++; } if (printit) cout << endl; double gfactor(pow(10, calamp/20)); // Kout could have been determined earlier, but we don't // reveal it until now, because we want to encourage the // user to put Zref in series before calibrating Kout. if (!kout) { if (vmeter) cout << "--Kout " << vmeter/gfactor << endl; } else { // probe RMS is at ref_level, 3dB higher than sine wave double koutprime(kout); if (calfreq < 0) koutprime *= sqrt(2.0); double vgain(vindiv/gfactor/koutprime); if (vmeter) cout << " Vmeter/Vout " << vmeter/gfactor/koutprime; cout << " Vin/Vout/divider " << vgain ; double bw(vgain*vgain*dsp->rate / 2); cout << endl; if (calfreq < 0) { if (!bandwidth) { cout << "--Bandwidth " << bw << endl; } else if (r_in) { cout << " Bw meas/arg " << bw/bandwidth; double sigmaJsq = 4 * kT * r_in * bandwidth; cout << " sigmaJ " << sqrt(sigmaJsq) << " Entropy " << sps << " bits/sample" << endl; } } }// if kout } else { // not r_in if (printit) cout << endl; } } } } void snark::analyze(alsa_dsp* dsp, valarray& cap_buffer, const int finbuf){ int left, right, sdiff; valarray sdiff_buffer(nframe); int left_idx = 0; int right_idx = 1; /// Kludge test: left_idx = 10; right_idx = 11; // Be careful to index outside array boundaries: int topchan = dsp->nchan - 1; if (left_idx > topchan) left_idx = topchan; if (right_idx > topchan) right_idx = topchan; double left_1st(0), left_2nd(0), left_s(0); double right_1st(0), right_2nd(0), right_s(0); double sdiff_1st(0), sdiff_2nd(0), sdiff_s(0); for(int frame = 0; frame < nframe; frame++) { left = cap_buffer[frame * dsp->nchan + left_idx ]; right = cap_buffer[frame * dsp->nchan + right_idx]; sdiff = left - right; sdiff_buffer[frame] = sdiff; if (0) if (verbose) if (frame < 4) { printf(" >>> %5d %5d %5d\n", left, right, sdiff); } left_1st += left; left_2nd += ((double)left)*left; right_1st += right; right_2nd += ((double)right)*right; sdiff_1st += sdiff; sdiff_2nd += ((double)sdiff)*sdiff; } // Do the normalization denominators: left_1st /= nframe; left_2nd /= nframe; right_1st /= nframe; right_2nd /= nframe; sdiff_1st /= nframe; sdiff_2nd /= nframe; int histoslots(512); left_s = histogram(&cap_buffer[0], nframe, histoslots, int(left_1st), dsp->nchan, dsp->sbits); right_s = histogram(&cap_buffer[1], nframe, histoslots, int(right_1st), dsp->nchan, dsp->sbits); sdiff_s = histogram(&sdiff_buffer[0], nframe, histoslots, int(sdiff_1st), 1, dsp->sbits); if (verbose) { printx("left", left_1st, left_2nd, left_s, dsp, vmeter); } if (verbose) { cout << crtRed; printx("right", right_1st, right_2nd, right_s, dsp, vmeter); cout << crtNormal; } if (verbose) printx("sdiff", sdiff_1st, sdiff_2nd, sdiff_s, 0, 0); } //////////////////////////////////////// // Read some data and maybe write checkfile int snark::read_stuff(alsa_dsp* dsp, datum* cap_buffer){ int sts; int frames_read; for (;;){ // keep trying to read frames_read = readi(dsp, &cap_buffer[0], nframe); if (frames_read == nframe) break; // good! if (frames_read > 0 || frames_read == -EPIPE) { // recover from overruns if (d_xrun) syslog(LOG_WARNING, // Note that these underruns are normal. They // happen whenever we transition from being busy to // being happily blocked due to output pipeline full. "Read_stuff overrun: requested %i got %i: %s", nframe, frames_read, snd_strerror(frames_read)); snd_pcm_prepare(dsp->phndl); } else { // unrecoverable error syslog(LOG_WARNING, "Read_stuff requested %i got %i: %s", nframe, frames_read, snd_strerror(frames_read)); exeunt(1); } } int gotbytes = snd_pcm_frames_to_bytes(dsp->phndl, frames_read); if (wcheck_fd >= 0) { sts = write(wcheck_fd, &cap_buffer[0], gotbytes); if (sts != gotbytes) { syslog(LOG_ERR, "Write error on raw output file (%i): %m", wcheck_fd); exeunt(1); } close(wcheck_fd); wcheck_fd = -1; } return frames_read; } #include "bufwrite.h" void snark::main_loop(alsa_dsp* dsp, const double snooze){ valarray cap_buf(nframe * dsp->nchan); valarray chosen_buf(dsp->nchan); bufwrite chunky(bufpipe[1], obs); SHA1_CTX shacon; // the SHA-1 "context" unsigned char digest[20]; SHA1_Init(&shacon); double sincon(0); // entropy in shacon // 10 megabytes in 26 seconds // time dd if=~/dev/hrandom of=foo.dat bs=20 count=500k int do_out(sps && channel_mask && (rando_fd >= 0)); while (1) { int finbuf = read_stuff(dsp, &cap_buf[0]); datum* cap_ptr(&cap_buf[0]); if (verbose) analyze(dsp, cap_buf, finbuf); if (do_out) while (finbuf) { int mymask(channel_mask); int chosench(0); for (int ii = 0; ii < dsp->nchan; ii++){ if ((mymask & 1)) chosen_buf[chosench++] = cap_ptr[ii]; mymask = mymask >> 1; } SHA1_Update(&shacon, (unsigned char*) (&chosen_buf[0]), chosench * sizeof(datum)); cap_ptr += dsp->nchan; // use a new frame next time finbuf--; // fewer frames remaining sincon += chosench * sps; // more entropy in shacon if (sincon > 170){ SHA1_Final(digest, &shacon); chunky.Write(digest, sizeof(digest)); #if 0 int sts = write(rando_fd, digest, sizeof(digest)); if (sts != sizeof(digest)){ syslog(LOG_ERR, "Write error on fifo (principal output): %m"); sleep(10); // or exit ??? } #endif SHA1_Init(&shacon); // get ready for next time sincon = 0; } } if (onepass) exeunt(0); const double microsec(1e-6); if (!do_out) usleep(int(snooze/microsec)); }// while (1) } //////////////////// // The whole idea of a pidfile is a crock. // It is inconsistent with the idea that you might // reasonably run multiple copies of turbid. void write_pid_file(const string fname) { pidfile = ""; // assume the worst, for now if (!fname.length()) return; FILE* junk = fopen(fname.c_str(), "r"); if (junk) { // ???!!! todo: be smart if pidfile exists but points // to extinct process ...... syslog(LOG_WARNING, "PID file \"%s\" already exists, not overwriting", fname.c_str()); fclose(junk); return; } FILE* p = fopen(fname.c_str(), "w"); if (!p) { syslog(LOG_WARNING, "Couldn't open PID file \"%s\" for writing: %m.", fname.c_str()); } else { int kidpid = getpid(); fprintf(p, "Kidpid: %i\n", kidpid); fclose(p); pidfile = fname; // remember fname in global (pidfile) // for later use during cleanup } } void cleanup(int sts){ for (unsigned int ii = 0; ii < clean_me.size(); ii++) { kill(clean_me[ii], SIGQUIT); } if (pidfile.length()) unlink(pidfile.c_str()); cout << crtNormal; exit(sts); } void trap_exit(int signum) { syslog(LOG_NOTICE, "%s stopping on signal %i", progname.c_str(), signum); cleanup(0); } void trap_detach(int signum) { syslog(LOG_NOTICE, "%s detaching on signal %i", progname.c_str(), signum); exit(0); } void dummy(int signum) { syslog(LOG_NOTICE, "Dummy caught signal %i", signum); } void exeunt(int sts){ syslog(LOG_NOTICE, "%s exiting, status %i", progname.c_str(), sts); cleanup(sts); }