You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
641 lines
14 KiB
641 lines
14 KiB
15 years ago
|
/*
|
||
|
|
||
|
Copyright (C) 2000-2002 Stefan Westerfeld
|
||
|
stefan@space.twc.de
|
||
|
|
||
|
This library is free software; you can redistribute it and/or
|
||
|
modify it under the terms of the GNU Library General Public
|
||
|
License as published by the Free Software Foundation; either
|
||
|
version 2 of the License, or (at your option) any later version.
|
||
|
|
||
|
This library is distributed in the hope that it will be useful,
|
||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
|
Library General Public License for more details.
|
||
|
|
||
|
You should have received a copy of the GNU Library General Public License
|
||
|
along with this library; see the file COPYING.LIB. If not, write to
|
||
15 years ago
|
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||
|
Boston, MA 02110-1301, USA.
|
||
15 years ago
|
|
||
|
*/
|
||
|
|
||
|
#ifdef HAVE_CONFIG_H
|
||
|
#include <config.h>
|
||
|
#endif
|
||
|
|
||
|
/**
|
||
|
* only compile 'csl' AudioIO class if libcsl is present
|
||
|
*/
|
||
|
#ifdef HAVE_LIBCSL
|
||
|
#include <csl/csl.h>
|
||
|
|
||
|
/* g_newa */
|
||
|
#include <gsl/gsldefs.h>
|
||
|
|
||
|
#include <vector>
|
||
|
|
||
|
#include "audioio.h"
|
||
|
#include "audiosubsys.h"
|
||
|
#include "debug.h"
|
||
|
#include "dispatcher.h"
|
||
|
#include "iomanager.h"
|
||
|
|
||
|
namespace Arts {
|
||
|
|
||
|
class AudioIOCSL : public AudioIO,
|
||
|
public IONotify
|
||
|
{
|
||
|
protected:
|
||
|
CslPcmStream *inputStream, *outputStream;
|
||
|
CslDriver *cslDriver;
|
||
|
int requestedFragmentSize;
|
||
|
int requestedFragmentCount;
|
||
|
const char *cslDriverName;
|
||
|
|
||
|
std::vector<CslPollFD> cslFds, cslOldFds;
|
||
|
int csl2iomanager(int cslTypes);
|
||
|
void updateFds();
|
||
|
|
||
|
void notifyIO(int fd, int types);
|
||
|
static void handleRead(void *user_data, CslPcmStream *stream);
|
||
|
static void handleWrite(void *user_data, CslPcmStream *stream);
|
||
|
|
||
|
public:
|
||
|
AudioIOCSL(const char *driverName = 0);
|
||
|
|
||
|
void setParam(AudioParam param, int& value);
|
||
|
int getParam(AudioParam param);
|
||
|
|
||
|
bool open();
|
||
|
void close();
|
||
|
int read(void *buffer, int size);
|
||
|
int write(void *buffer, int size);
|
||
|
};
|
||
|
|
||
|
REGISTER_AUDIO_IO(AudioIOCSL,"csl","Common Sound Layer");
|
||
|
|
||
|
class AudioIOCSLFactory : public AudioIOFactory {
|
||
|
protected:
|
||
|
const char *driverName;
|
||
|
std::string _name;
|
||
|
std::string _fullName;
|
||
|
|
||
|
public:
|
||
|
AudioIOCSLFactory(const char *driverName)
|
||
|
: driverName(driverName)
|
||
|
{
|
||
|
_name = "csl-";
|
||
|
_name += driverName;
|
||
|
|
||
|
_fullName = "Common Sound Layer (";
|
||
|
_fullName += driverName;
|
||
|
_fullName += ")";
|
||
|
}
|
||
|
virtual ~AudioIOCSLFactory()
|
||
|
{
|
||
|
}
|
||
|
AudioIO *createAudioIO() { return new AudioIOCSL(driverName); }
|
||
|
virtual const char *name() { return _name.c_str(); }
|
||
|
virtual const char *fullName() { return _fullName.c_str(); }
|
||
|
};
|
||
|
|
||
|
static class AudioIOCSLInit {
|
||
|
protected:
|
||
|
std::list<AudioIOCSLFactory *> factories;
|
||
|
|
||
|
public:
|
||
|
AudioIOCSLInit()
|
||
|
{
|
||
|
unsigned int i,n;
|
||
|
const char **drivers = csl_list_drivers(&n);
|
||
|
|
||
|
for(i = 0; i < n; i++)
|
||
|
factories.push_back(new AudioIOCSLFactory(drivers[i]));
|
||
|
}
|
||
|
~AudioIOCSLInit()
|
||
|
{
|
||
|
std::list<AudioIOCSLFactory *>::iterator i;
|
||
|
for(i = factories.begin(); i != factories.end(); i++)
|
||
|
delete (*i);
|
||
|
|
||
|
factories.clear();
|
||
|
}
|
||
|
} aci;
|
||
|
|
||
|
};
|
||
|
|
||
|
using namespace std;
|
||
|
using namespace Arts;
|
||
|
|
||
|
AudioIOCSL::AudioIOCSL(const char *driverName)
|
||
|
: cslDriverName(driverName)
|
||
|
{
|
||
|
/*
|
||
|
* default parameters
|
||
|
*/
|
||
|
param(samplingRate) = 44100;
|
||
|
paramStr(deviceName) = "todo";
|
||
|
requestedFragmentSize = param(fragmentSize) = 1024;
|
||
|
requestedFragmentCount = param(fragmentCount) = 7;
|
||
|
param(channels) = 2;
|
||
|
param(direction) = 2;
|
||
|
|
||
|
#ifdef WORDS_BIGENDIAN
|
||
|
param(format) = 17;
|
||
|
#else
|
||
|
param(format) = 16;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
bool AudioIOCSL::open()
|
||
|
{
|
||
|
string& errorMsg = paramStr(lastError);
|
||
|
string& _deviceName = paramStr(deviceName);
|
||
|
int& _channels = param(channels);
|
||
|
int& _fragmentSize = param(fragmentSize);
|
||
|
int& _fragmentCount = param(fragmentCount);
|
||
|
int& _samplingRate = param(samplingRate);
|
||
|
int& _format = param(format);
|
||
|
int fmt = 0;
|
||
|
char *env = 0;
|
||
|
|
||
|
if(cslDriverName && strcmp(cslDriverName, "arts") == 0)
|
||
|
env = arts_strdup_printf("ARTS_SERVER=%s",_deviceName.c_str());
|
||
|
|
||
|
if(env)
|
||
|
{
|
||
|
putenv(env);
|
||
|
arts_debug("AudioIOCsl: set %s\n",env);
|
||
|
}
|
||
|
|
||
|
CslErrorType error;
|
||
|
error = csl_driver_init(cslDriverName, &cslDriver); /* choose backend */
|
||
|
|
||
|
if(env)
|
||
|
{
|
||
|
putenv("ARTS_SERVER");
|
||
|
free(env);
|
||
|
}
|
||
|
|
||
|
if (error)
|
||
|
{
|
||
|
errorMsg = "unable to initialize CSL driver: ";
|
||
|
errorMsg += csl_strerror(error);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if(_format == 8)
|
||
|
fmt = CSL_PCM_FORMAT_U8;
|
||
|
else if(_format == 16)
|
||
|
fmt = CSL_PCM_FORMAT_S16_LE;
|
||
|
else if(_format == 17)
|
||
|
fmt = CSL_PCM_FORMAT_S16_BE;
|
||
|
|
||
|
inputStream = outputStream = 0;
|
||
|
if(param(direction) & directionRead)
|
||
|
{
|
||
|
/* open PCM output stream */
|
||
|
error = csl_pcm_open_output(cslDriver,
|
||
|
"artsd output",
|
||
|
_samplingRate,
|
||
|
_channels,
|
||
|
fmt, &inputStream);
|
||
|
if (error)
|
||
|
{
|
||
|
errorMsg = "failed to open CSL input stream: ";
|
||
|
errorMsg += csl_strerror(error);
|
||
|
return false;
|
||
|
}
|
||
|
csl_pcm_set_callback(inputStream, handleRead, 0, 0);
|
||
|
}
|
||
|
if(param(direction) & directionWrite)
|
||
|
{
|
||
|
/* open PCM output stream */
|
||
|
error = csl_pcm_open_output(cslDriver,
|
||
|
"artsd output",
|
||
|
_samplingRate,
|
||
|
_channels,
|
||
|
fmt, &outputStream);
|
||
|
if (error)
|
||
|
{
|
||
|
close();
|
||
|
|
||
|
errorMsg = "failed to open CSL output stream: ";
|
||
|
errorMsg += csl_strerror(error);
|
||
|
return false;
|
||
|
}
|
||
|
csl_pcm_set_callback(outputStream, handleWrite, 0, 0);
|
||
|
}
|
||
|
#if 0
|
||
|
if (_format && (ossBits(gotFormat) != ossBits(requestedFormat)))
|
||
|
{
|
||
|
char details[80];
|
||
|
sprintf(details," (_format = %d, asked driver to give %d, got %d)",
|
||
|
_format, requestedFormat, gotFormat);
|
||
|
|
||
|
_error = "Can't set playback format";
|
||
|
_error += details;
|
||
|
|
||
|
close();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if(gotFormat == AFMT_U8)
|
||
|
_format = 8;
|
||
|
else if(gotFormat == AFMT_S16_LE)
|
||
|
_format = 16;
|
||
|
else if(gotFormat == AFMT_S16_BE)
|
||
|
_format = 17;
|
||
|
else
|
||
|
{
|
||
|
char details[80];
|
||
|
sprintf(details," (_format = %d, asked driver to give %d, got %d)",
|
||
|
_format, requestedFormat, gotFormat);
|
||
|
|
||
|
_error = "unknown format given by driver";
|
||
|
_error += details;
|
||
|
|
||
|
close();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
|
||
|
int stereo=-1; /* 0=mono, 1=stereo */
|
||
|
|
||
|
if(_channels == 1)
|
||
|
{
|
||
|
stereo = 0;
|
||
|
}
|
||
|
if(_channels == 2)
|
||
|
{
|
||
|
stereo = 1;
|
||
|
}
|
||
|
|
||
|
if(stereo == -1)
|
||
|
{
|
||
|
_error = "internal error; set channels to 1 (mono) or 2 (stereo)";
|
||
|
|
||
|
close();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
int requeststereo = stereo;
|
||
|
|
||
|
if (ioctl(audio_fd, SNDCTL_DSP_STEREO, &stereo)==-1)
|
||
|
{
|
||
|
_error = "SNDCTL_DSP_STEREO failed - ";
|
||
|
_error += strerror(errno);
|
||
|
|
||
|
close();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (requeststereo != stereo)
|
||
|
{
|
||
|
_error = "audio device doesn't support number of requested channels";
|
||
|
|
||
|
close();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
int speed = _samplingRate;
|
||
|
|
||
|
if (ioctl(audio_fd, SNDCTL_DSP_SPEED, &speed)==-1)
|
||
|
{
|
||
|
_error = "SNDCTL_DSP_SPEED failed - ";
|
||
|
_error += strerror(errno);
|
||
|
|
||
|
close();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Some soundcards seem to be able to only supply "nearly" the requested
|
||
|
* sampling rate, especially PAS 16 cards seem to quite radical supplying
|
||
|
* something different than the requested sampling rate ;)
|
||
|
*
|
||
|
* So we have a quite large tolerance here (when requesting 44100 Hz, it
|
||
|
* will accept anything between 38690 Hz and 49510 Hz). Most parts of the
|
||
|
* aRts code will do resampling where appropriate, so it shouldn't affect
|
||
|
* sound quality.
|
||
|
*/
|
||
|
int tolerance = _samplingRate/10+1000;
|
||
|
|
||
|
if (abs(speed-_samplingRate) > tolerance)
|
||
|
{
|
||
|
_error = "can't set requested samplingrate";
|
||
|
|
||
|
char details[80];
|
||
|
sprintf(details," (requested rate %d, got rate %d)",
|
||
|
_samplingRate, speed);
|
||
|
_error += details;
|
||
|
|
||
|
close();
|
||
|
return false;
|
||
|
}
|
||
|
_samplingRate = speed;
|
||
|
|
||
|
/*
|
||
|
* set the fragment settings to what the user requested
|
||
|
*/
|
||
|
|
||
|
_fragmentSize = requestedFragmentSize;
|
||
|
_fragmentCount = requestedFragmentCount;
|
||
|
|
||
|
/*
|
||
|
* lower 16 bits are the fragment size (as 2^S)
|
||
|
* higher 16 bits are the number of fragments
|
||
|
*/
|
||
|
int frag_arg = 0;
|
||
|
|
||
|
int size = _fragmentSize;
|
||
|
while(size > 1) { size /= 2; frag_arg++; }
|
||
|
frag_arg += (_fragmentCount << 16);
|
||
|
if(ioctl(audio_fd, SNDCTL_DSP_SETFRAGMENT, &frag_arg) == -1)
|
||
|
{
|
||
|
char buffer[1024];
|
||
|
_error = "can't set requested fragments settings";
|
||
|
sprintf(buffer,"size%d:count%d\n",_fragmentSize,_fragmentCount);
|
||
|
close();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* now see what we really got as cards aren't required to supply what
|
||
|
* we asked for
|
||
|
*/
|
||
|
audio_buf_info info;
|
||
|
if(ioctl(audio_fd,SNDCTL_DSP_GETOSPACE, &info) == -1)
|
||
|
{
|
||
|
_error = "can't retrieve fragment settings";
|
||
|
close();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// update fragment settings with what we got
|
||
|
_fragmentSize = info.fragsize;
|
||
|
_fragmentCount = info.fragstotal;
|
||
|
|
||
|
artsdebug("buffering: %d fragments with %d bytes "
|
||
|
"(audio latency is %1.1f ms)", _fragmentCount, _fragmentSize,
|
||
|
(float)(_fragmentSize*_fragmentCount) /
|
||
|
(float)(2.0 * _samplingRate * _channels)*1000.0);
|
||
|
|
||
|
/*
|
||
|
* Workaround for broken kernel drivers: usually filling up the audio
|
||
|
* buffer is _only_ required if _fullDuplex is true. However, there
|
||
|
* are kernel drivers around (especially everything related to ES1370/1371)
|
||
|
* which will not trigger select()ing the file descriptor unless we have
|
||
|
* written something first.
|
||
|
*/
|
||
|
char *zbuffer = (char *)calloc(sizeof(char), _fragmentSize);
|
||
|
if(_format == 8)
|
||
|
for(int zpos = 0; zpos < _fragmentSize; zpos++)
|
||
|
zbuffer[zpos] |= 0x80;
|
||
|
|
||
|
for(int fill = 0; fill < _fragmentCount; fill++)
|
||
|
{
|
||
|
int len = ::write(audio_fd,zbuffer,_fragmentSize);
|
||
|
if(len != _fragmentSize)
|
||
|
{
|
||
|
arts_debug("AudioIOCSL: failed prefilling audio buffer (might cause synchronization problems in conjunction with full duplex)");
|
||
|
fill = _fragmentCount+1;
|
||
|
}
|
||
|
}
|
||
|
free(zbuffer);
|
||
|
|
||
|
/*
|
||
|
* Triggering - the original aRts code did this for full duplex:
|
||
|
*
|
||
|
* - stop audio i/o using SETTRIGGER(~(PCM_ENABLE_INPUT|PCM_ENABLE_OUTPUT))
|
||
|
* - fill buffer (see zbuffer code two lines above)
|
||
|
* - start audio i/o using SETTRIGGER(PCM_ENABLE_INPUT|PCM_ENABLE_OUTPUT)
|
||
|
*
|
||
|
* this should guarantee synchronous start of input/output. Today, it
|
||
|
* seems there are too many broken drivers around for this.
|
||
|
*/
|
||
|
|
||
|
if(device_caps & DSP_CAP_TRIGGER)
|
||
|
{
|
||
|
int enable_bits = 0;
|
||
|
|
||
|
if(param(direction) & 1) enable_bits |= PCM_ENABLE_INPUT;
|
||
|
if(param(direction) & 2) enable_bits |= PCM_ENABLE_OUTPUT;
|
||
|
|
||
|
if(ioctl(audio_fd,SNDCTL_DSP_SETTRIGGER, &enable_bits) == -1)
|
||
|
{
|
||
|
_error = "can't start sound i/o";
|
||
|
|
||
|
close();
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
updateFds();
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void AudioIOCSL::close()
|
||
|
{
|
||
|
if(inputStream)
|
||
|
{
|
||
|
csl_pcm_close(inputStream);
|
||
|
inputStream = 0;
|
||
|
}
|
||
|
if(outputStream)
|
||
|
{
|
||
|
csl_pcm_close(outputStream);
|
||
|
outputStream = 0;
|
||
|
}
|
||
|
updateFds();
|
||
|
}
|
||
|
|
||
|
void AudioIOCSL::setParam(AudioParam p, int& value)
|
||
|
{
|
||
|
switch(p)
|
||
|
{
|
||
|
case fragmentSize:
|
||
|
param(p) = requestedFragmentSize = value;
|
||
|
break;
|
||
|
case fragmentCount:
|
||
|
param(p) = requestedFragmentCount = value;
|
||
|
break;
|
||
|
default:
|
||
|
param(p) = value;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int AudioIOCSL::getParam(AudioParam p)
|
||
|
{
|
||
|
CslErrorType error;
|
||
13 years ago
|
CslPcmStatus status;
|
||
15 years ago
|
|
||
|
switch(p)
|
||
|
{
|
||
|
case canRead:
|
||
|
error = csl_pcm_get_status (inputStream, &status);
|
||
|
if (error) /* FIXME */
|
||
|
arts_fatal("unable to obtain csl stream status: %s",
|
||
|
csl_strerror (error));
|
||
|
|
||
|
updateFds();
|
||
|
return status.n_bytes_available;
|
||
|
break;
|
||
|
|
||
|
case canWrite:
|
||
|
error = csl_pcm_get_status(outputStream, &status);
|
||
|
if (error) /* FIXME */
|
||
|
arts_fatal("unable to obtain csl stream status: %s",
|
||
|
csl_strerror (error));
|
||
|
|
||
|
updateFds();
|
||
|
return status.n_bytes_available;
|
||
|
break;
|
||
|
|
||
|
case autoDetect:
|
||
|
/* CSL is pretty experimental currently */
|
||
|
return 1;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
return param(p);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int AudioIOCSL::read(void *buffer, int size)
|
||
|
{
|
||
|
arts_assert(inputStream != 0);
|
||
|
|
||
|
int result = csl_pcm_read(inputStream, size, buffer);
|
||
|
updateFds();
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
void AudioIOCSL::handleRead(void *, CslPcmStream *)
|
||
|
{
|
||
|
AudioSubSystem::the()->handleIO(AudioSubSystem::ioRead);
|
||
|
}
|
||
|
|
||
|
int AudioIOCSL::write(void *buffer, int size)
|
||
|
{
|
||
|
arts_assert(outputStream != 0);
|
||
|
|
||
|
int result = csl_pcm_write(outputStream, size, buffer);
|
||
|
updateFds();
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
void AudioIOCSL::handleWrite(void *, CslPcmStream *)
|
||
|
{
|
||
|
AudioSubSystem::the()->handleIO(AudioSubSystem::ioWrite);
|
||
|
}
|
||
|
|
||
|
/* mainloop integration: make CSL callbacks work inside the aRts mainloop */
|
||
|
|
||
|
int AudioIOCSL::csl2iomanager(int cslTypes)
|
||
|
{
|
||
|
/* FIXME: doublecheck this list */
|
||
|
int types = 0;
|
||
|
|
||
|
if(cslTypes & CSL_POLLIN)
|
||
|
types |= IOType::read;
|
||
|
if(cslTypes & CSL_POLLOUT)
|
||
|
types |= IOType::write;
|
||
|
if(cslTypes & CSL_POLLERR)
|
||
|
types |= IOType::except;
|
||
|
|
||
|
return types;
|
||
|
}
|
||
|
|
||
|
void AudioIOCSL::updateFds()
|
||
|
{
|
||
|
unsigned int n_fds = csl_poll_count_fds(cslDriver);
|
||
|
CslPollFD *newFds = g_newa(CslPollFD, n_fds);
|
||
|
|
||
|
unsigned int have_fds = csl_poll_get_fds(cslDriver, n_fds, newFds);
|
||
|
arts_assert(have_fds == n_fds);
|
||
|
|
||
|
cslFds.clear();
|
||
|
|
||
|
unsigned int i;
|
||
|
for(i = 0; i < have_fds; i++)
|
||
|
cslFds.push_back(newFds[i]);
|
||
|
|
||
|
/* FIXME: if csl provided a flag for this, we could save some work here */
|
||
|
bool fdsChanged;
|
||
|
if(cslFds.size() == cslOldFds.size())
|
||
|
{
|
||
|
fdsChanged = false;
|
||
|
for(i = 0; i < have_fds; i++)
|
||
|
{
|
||
|
if(cslFds[i].events != cslOldFds[i].events)
|
||
|
fdsChanged = true;
|
||
|
if(cslFds[i].fd != cslOldFds[i].fd)
|
||
|
fdsChanged = true;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
fdsChanged = true;
|
||
|
}
|
||
|
if(!fdsChanged)
|
||
|
return;
|
||
|
|
||
|
vector<CslPollFD>::iterator ci;
|
||
|
|
||
|
/* remove old watches */
|
||
|
/*
|
||
|
* UGLY! due to broken API, we can only remove all watches here, and not
|
||
|
* do anything selectively - its not a problem for the code here, but it
|
||
|
* might be a problem elsewhere. Unfortunately, it can't be fixed without
|
||
|
* breaking BC.
|
||
|
*/
|
||
|
Dispatcher::the()->ioManager()->remove(this, IOType::all);
|
||
|
arts_debug("AudioIOCSL::updateFds(): removing watches");
|
||
|
|
||
|
/* add new watches */
|
||
|
for(ci = cslFds.begin(); ci < cslFds.end(); ci++)
|
||
|
{
|
||
|
int types = csl2iomanager(ci->events);
|
||
|
if(types)
|
||
|
{
|
||
|
Dispatcher::the()->ioManager()->watchFD(ci->fd, types, this);
|
||
|
arts_debug("AudioIOCSL::updateFds(): adding watch on %d", ci->fd);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
cslOldFds = cslFds;
|
||
|
}
|
||
|
|
||
|
void AudioIOCSL::notifyIO(int fd, int type)
|
||
|
{
|
||
|
vector<CslPollFD>::iterator fi;
|
||
|
|
||
|
for(fi = cslFds.begin(); fi != cslFds.end(); fi++)
|
||
|
{
|
||
|
if(fi->fd == fd)
|
||
|
{
|
||
|
int ftype = csl2iomanager(fi->events);
|
||
|
fi->revents = 0;
|
||
|
|
||
|
if(type & ftype & IOType::read)
|
||
|
fi->revents |= CSL_POLLIN;
|
||
|
if(type & ftype & IOType::write)
|
||
|
fi->revents |= CSL_POLLOUT;
|
||
|
if(type & ftype & IOType::except)
|
||
|
fi->revents |= CSL_POLLERR;
|
||
|
|
||
|
if(fi->revents)
|
||
|
csl_poll_handle_fds(cslDriver, 1, &(*fi));
|
||
|
}
|
||
|
}
|
||
|
updateFds();
|
||
|
}
|
||
|
|
||
|
#endif
|