|
|
|
/* This file is part of the KDE project
|
|
|
|
*
|
|
|
|
* Copyright (C) 2001 George Staikos <staikos@kde.org>
|
|
|
|
*
|
|
|
|
* 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
|
|
|
|
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
|
|
* Boston, MA 02110-1301, USA.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Much of the linux code was migrated from:
|
|
|
|
* kardinfo Copyright 1999, Mirko Sucker <mirko.sucker@unibw-hamburg.de>
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <tqtimer.h>
|
|
|
|
#include <tqfile.h>
|
|
|
|
#include <tqregexp.h>
|
|
|
|
|
|
|
|
#include <tdelocale.h>
|
|
|
|
#include <kinstance.h>
|
|
|
|
#include <kstandarddirs.h>
|
|
|
|
|
|
|
|
|
|
|
|
#include "kpcmcia.h"
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef __linux__
|
|
|
|
extern "C" {
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <sys/ioctl.h>
|
|
|
|
#include <sys/file.h>
|
|
|
|
#include <sys/time.h>
|
|
|
|
#include "linux/version.h"
|
|
|
|
#include "linux/cs_types.h"
|
|
|
|
#include "linux/cs.h"
|
|
|
|
#include "linux/cistpl.h"
|
|
|
|
#include "linux/ds.h"
|
|
|
|
}
|
|
|
|
|
|
|
|
// Taken from cardinfo.c
|
|
|
|
typedef struct event_tag_t {
|
|
|
|
event_t event;
|
|
|
|
const char *name;
|
|
|
|
} event_tag_t;
|
|
|
|
|
|
|
|
|
|
|
|
static event_tag_t event_tag[] = {
|
|
|
|
{ CS_EVENT_CARD_INSERTION, "card insertion" },
|
|
|
|
{ CS_EVENT_CARD_REMOVAL, "card removal" },
|
|
|
|
{ CS_EVENT_RESET_PHYSICAL, "prepare for reset" },
|
|
|
|
{ CS_EVENT_CARD_RESET, "card reset successful" },
|
|
|
|
{ CS_EVENT_RESET_COMPLETE, "reset request complete" },
|
|
|
|
{ CS_EVENT_EJECTION_REQUEST, "user eject request" },
|
|
|
|
{ CS_EVENT_INSERTION_REQUEST, "user insert request" },
|
|
|
|
{ CS_EVENT_PM_SUSPEND, "suspend card" },
|
|
|
|
{ CS_EVENT_PM_RESUME, "resume card" },
|
|
|
|
{ CS_EVENT_REQUEST_ATTENTION, "request attention" }
|
|
|
|
};
|
|
|
|
|
|
|
|
#define NTAGS (sizeof(event_tag)/sizeof(event_tag_t))
|
|
|
|
|
|
|
|
static int lookupDevice(const char *x);
|
|
|
|
static int openDevice(dev_t dev);
|
|
|
|
|
|
|
|
#else
|
|
|
|
#include <unistd.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
KPCMCIACard::KPCMCIACard() {
|
|
|
|
_fd = -1;
|
|
|
|
_num = 9999999;
|
|
|
|
_status = 0;
|
|
|
|
_last = 0;
|
|
|
|
_interrupt = -1;
|
|
|
|
_ports = "";
|
|
|
|
_device = "";
|
|
|
|
_module = "";
|
|
|
|
_type = "";
|
|
|
|
_iotype = 0;
|
|
|
|
_cardname = i18n("Empty slot.");
|
|
|
|
_vcc = _vpp = _vpp2 = 0;
|
|
|
|
_inttype = 0;
|
|
|
|
_cfgbase = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
KPCMCIACard::~KPCMCIACard() {
|
|
|
|
if (_fd != -1) close(_fd);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// RETURN: <0 => error
|
|
|
|
// =0 => no error, no update
|
|
|
|
// >0 => no error, update happened
|
|
|
|
//
|
|
|
|
int KPCMCIACard::refresh() {
|
|
|
|
//////////////////////////////////////////////
|
|
|
|
///////////// LINUX ONLY
|
|
|
|
///////////////////////////////////////////////
|
|
|
|
#ifdef __linux__
|
|
|
|
struct timeval tv;
|
|
|
|
cs_status_t status;
|
|
|
|
fd_set rfds;
|
|
|
|
int rc;
|
|
|
|
event_t event;
|
|
|
|
struct stat sb;
|
|
|
|
config_info_t cfg;
|
|
|
|
KPCMCIACard oldValues(*this);
|
|
|
|
int updated = 0;
|
|
|
|
oldValues._fd = -1;
|
|
|
|
|
|
|
|
#define CHECK_CHANGED(x, y) do { if (x.y != y) updated = 1; } while(0)
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get any events on the pcmcia device
|
|
|
|
*/
|
|
|
|
tv.tv_sec = 0; tv.tv_usec = 0;
|
|
|
|
FD_ZERO(&rfds);
|
|
|
|
FD_SET(_fd, &rfds);
|
|
|
|
rc = select(_fd+1, &rfds, NULL, NULL, &tv);
|
|
|
|
if (rc > 0) {
|
|
|
|
rc = read(_fd, (void *)&event, 4);
|
|
|
|
if (rc == 4) {
|
|
|
|
int thisEvent = -1; // thisEvent is the index of event in event_tag
|
|
|
|
for (unsigned int j = 0; j < NTAGS; j++) {
|
|
|
|
if (event_tag[j].event == event) {
|
|
|
|
thisEvent = j;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (thisEvent < 0) return -1;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
} else return updated;
|
|
|
|
|
|
|
|
if (event == CS_EVENT_EJECTION_REQUEST) {
|
|
|
|
_interrupt = -1;
|
|
|
|
_ports = "";
|
|
|
|
_device = "";
|
|
|
|
_module = "";
|
|
|
|
_type = "";
|
|
|
|
_iotype = 0;
|
|
|
|
_inttype = 0;
|
|
|
|
_cfgbase = 0;
|
|
|
|
_cardname = i18n("Empty slot.");
|
|
|
|
_vcc = _vpp = _vpp2 = 0;
|
|
|
|
return updated;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Read in the stab file.
|
|
|
|
*/
|
|
|
|
if (!stat(_stabPath.latin1(), &sb) && sb.st_mtime >= _last) {
|
|
|
|
TQFile f(_stabPath.latin1());
|
|
|
|
|
|
|
|
if (f.open(IO_ReadOnly)) {
|
|
|
|
TQTextStream ts(&f);
|
|
|
|
bool foundit = false;
|
|
|
|
TQString _thisreg = "^Socket %1: ";
|
|
|
|
TQRegExp thisreg ( _thisreg.arg(_num) );
|
|
|
|
|
|
|
|
if (flock(f.handle(), LOCK_SH)) return updated;
|
|
|
|
|
|
|
|
_last = sb.st_mtime;
|
|
|
|
|
|
|
|
// find the card
|
|
|
|
while(!foundit) {
|
|
|
|
TQString s;
|
|
|
|
if (ts.eof()) break;
|
|
|
|
s = ts.readLine();
|
|
|
|
if (s.contains(thisreg)) {
|
|
|
|
_cardname = s.right(s.length() - s.find(':') - 1);
|
|
|
|
_cardname = _cardname.stripWhiteSpace();
|
|
|
|
foundit = true;
|
|
|
|
CHECK_CHANGED(oldValues, _cardname);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// read it in
|
|
|
|
if (foundit && !ts.eof()) { // FIXME: ts.eof() is a bad error!!
|
|
|
|
TQString s = ts.readLine();
|
|
|
|
int end;
|
|
|
|
s.simplifyWhiteSpace();
|
|
|
|
|
|
|
|
end = s.find(TQRegExp("[ \r\t\n]"));
|
|
|
|
s = s.remove(0, end+1);
|
|
|
|
|
|
|
|
end = s.find(TQRegExp("[ \r\t\n]"));
|
|
|
|
_type = s;
|
|
|
|
_type.truncate(end);
|
|
|
|
s = s.remove(0, end+1);
|
|
|
|
|
|
|
|
end = s.find(TQRegExp("[ \r\t\n]"));
|
|
|
|
_module = s;
|
|
|
|
_module.truncate(end);
|
|
|
|
s = s.remove(0, end+1);
|
|
|
|
|
|
|
|
end = s.find(TQRegExp("[ \r\t\n]"));
|
|
|
|
s = s.remove(0, end+1);
|
|
|
|
|
|
|
|
end = s.find(TQRegExp("[ \r\t\n]"));
|
|
|
|
_device = s;
|
|
|
|
_device.truncate(end);
|
|
|
|
s = s.remove(0, end+1);
|
|
|
|
CHECK_CHANGED(oldValues, _type);
|
|
|
|
CHECK_CHANGED(oldValues, _module);
|
|
|
|
CHECK_CHANGED(oldValues, _device);
|
|
|
|
}
|
|
|
|
|
|
|
|
flock(f.handle(), LOCK_UN);
|
|
|
|
f.close();
|
|
|
|
} else return -1;
|
|
|
|
} else return updated;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the card's status and configuration information
|
|
|
|
*/
|
|
|
|
status.Function = 0;
|
|
|
|
ioctl(_fd, DS_GET_STATUS, &status);
|
|
|
|
memset(&cfg, 0, sizeof(cfg));
|
|
|
|
ioctl(_fd, DS_GET_CONFIGURATION_INFO, &cfg);
|
|
|
|
// status is looked up in the table at the top
|
|
|
|
if (cfg.Attributes & CONF_VALID_CLIENT) {
|
|
|
|
if (cfg.AssignedIRQ == 0)
|
|
|
|
_interrupt = -1;
|
|
|
|
else _interrupt = cfg.AssignedIRQ;
|
|
|
|
|
|
|
|
if (cfg.NumPorts1 > 0) {
|
|
|
|
int stop = cfg.BasePort1 + cfg.NumPorts1;
|
|
|
|
if (cfg.NumPorts2 > 0) {
|
|
|
|
if (stop == cfg.BasePort2) {
|
|
|
|
_ports.sprintf("%#x-%#x", cfg.BasePort1, stop+cfg.NumPorts2-1);
|
|
|
|
} else {
|
|
|
|
_ports.sprintf("%#x-%#x, %#x-%#x", cfg.BasePort1, stop-1,
|
|
|
|
cfg.BasePort2, cfg.BasePort2+cfg.NumPorts2-1);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
_ports.sprintf("%#x-%#x", cfg.BasePort1, stop-1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
CHECK_CHANGED(oldValues, _ports);
|
|
|
|
CHECK_CHANGED(oldValues, _interrupt);
|
|
|
|
}
|
|
|
|
|
|
|
|
_vcc = cfg.Vcc;
|
|
|
|
_vpp = cfg.Vpp1;
|
|
|
|
_vpp2 = cfg.Vpp2;
|
|
|
|
CHECK_CHANGED(oldValues, _vcc);
|
|
|
|
CHECK_CHANGED(oldValues, _vpp);
|
|
|
|
CHECK_CHANGED(oldValues, _vpp2);
|
|
|
|
_inttype = cfg.IntType;
|
|
|
|
CHECK_CHANGED(oldValues, _inttype);
|
|
|
|
_iotype = cfg.IOAddrLines;
|
|
|
|
CHECK_CHANGED(oldValues, _iotype);
|
|
|
|
_cfgbase = cfg.ConfigBase;
|
|
|
|
CHECK_CHANGED(oldValues, _cfgbase);
|
|
|
|
|
|
|
|
if (status.CardState & CS_EVENT_CARD_DETECT)
|
|
|
|
_status |= CARD_STATUS_PRESENT;
|
|
|
|
if (status.CardState & CS_EVENT_CARD_REMOVAL)
|
|
|
|
_status &= ~CARD_STATUS_PRESENT;
|
|
|
|
if (event & CS_EVENT_CARD_REMOVAL)
|
|
|
|
_status &= ~CARD_STATUS_PRESENT;
|
|
|
|
|
|
|
|
if (!(status.CardState & CS_EVENT_PM_SUSPEND)) {
|
|
|
|
if (status.CardState & CS_EVENT_READY_CHANGE) {
|
|
|
|
_status |= CARD_STATUS_READY;
|
|
|
|
_status &= ~(CARD_STATUS_BUSY|CARD_STATUS_SUSPEND);
|
|
|
|
} else {
|
|
|
|
_status |= CARD_STATUS_BUSY;
|
|
|
|
_status &= ~(CARD_STATUS_READY|CARD_STATUS_SUSPEND);
|
|
|
|
}
|
|
|
|
} else if (status.CardState & CS_EVENT_PM_SUSPEND) {
|
|
|
|
_status |= CARD_STATUS_SUSPEND;
|
|
|
|
_status &= ~(CARD_STATUS_READY|CARD_STATUS_BUSY);
|
|
|
|
}
|
|
|
|
|
|
|
|
CHECK_CHANGED(oldValues, _status);
|
|
|
|
|
|
|
|
return updated;
|
|
|
|
#else
|
|
|
|
return 0;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int KPCMCIACard::insert() {
|
|
|
|
#ifdef __linux__
|
|
|
|
ioctl(_fd, DS_INSERT_CARD);
|
|
|
|
return 0;
|
|
|
|
#else
|
|
|
|
return -1;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int KPCMCIACard::eject() {
|
|
|
|
#ifdef __linux__
|
|
|
|
ioctl(_fd, DS_EJECT_CARD);
|
|
|
|
return 0;
|
|
|
|
#else
|
|
|
|
return -1;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int KPCMCIACard::reset() {
|
|
|
|
#ifdef __linux__
|
|
|
|
ioctl(_fd, DS_RESET_CARD);
|
|
|
|
return 0;
|
|
|
|
#else
|
|
|
|
return -1;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int KPCMCIACard::suspend() {
|
|
|
|
#ifdef __linux__
|
|
|
|
ioctl(_fd, DS_SUSPEND_CARD);
|
|
|
|
return 0;
|
|
|
|
#else
|
|
|
|
return -1;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int KPCMCIACard::resume() {
|
|
|
|
#ifdef __linux__
|
|
|
|
ioctl(_fd, DS_RESUME_CARD);
|
|
|
|
return 0;
|
|
|
|
#else
|
|
|
|
return -1;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
KPCMCIA::KPCMCIA(int maxSlots, const char *stabpath) : _maxSlots(maxSlots),
|
|
|
|
_stabPath(stabpath) {
|
|
|
|
|
|
|
|
_refreshSpeed = 750;
|
|
|
|
|
|
|
|
_haveCardServices = false;
|
|
|
|
_timer = new TQTimer(this);
|
|
|
|
connect(_timer, TQT_SIGNAL(timeout()), this, TQT_SLOT(updateCardInfo()));
|
|
|
|
_cards = new TQMemArray<KPCMCIACard *>(_maxSlots+1);
|
|
|
|
_cardCnt = 0;
|
|
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////
|
|
|
|
// LINUX code
|
|
|
|
///////////////////////////////////////////////////
|
|
|
|
#ifdef __linux__
|
|
|
|
servinfo_t serv;
|
|
|
|
|
|
|
|
int device = lookupDevice("pcmcia");
|
|
|
|
|
|
|
|
if (device >= 0) {
|
|
|
|
for (int z = 0; z < _maxSlots; z++) {
|
|
|
|
int fd = openDevice((device << 8) + z);
|
|
|
|
if (fd < 0) break;
|
|
|
|
(*_cards)[_cardCnt] = new KPCMCIACard;
|
|
|
|
(*_cards)[_cardCnt]->_stabPath = _stabPath;
|
|
|
|
(*_cards)[_cardCnt]->_fd = fd;
|
|
|
|
(*_cards)[_cardCnt]->_num = _cardCnt;
|
|
|
|
//(*_cards)[_cardCnt]->refresh();
|
|
|
|
_cardCnt++;
|
|
|
|
//kdDebug() << "Found a pcmcia slot" << endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_cardCnt > 0) {
|
|
|
|
if (ioctl((*_cards)[0]->_fd, DS_GET_CARD_SERVICES_INFO, &serv) == 0) {
|
|
|
|
// FIXME: what to do here?
|
|
|
|
}
|
|
|
|
_haveCardServices = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_timer->start(_refreshSpeed, true);
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////
|
|
|
|
// No supported platform.
|
|
|
|
///////////////////////////////////////////////////
|
|
|
|
#else
|
|
|
|
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
KPCMCIA::~KPCMCIA() {
|
|
|
|
///////////////////////////////////////////////////
|
|
|
|
// LINUX code
|
|
|
|
///////////////////////////////////////////////////
|
|
|
|
#ifdef __linux__
|
|
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////
|
|
|
|
// No supported platform.
|
|
|
|
///////////////////////////////////////////////////
|
|
|
|
#else
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
delete _timer;
|
|
|
|
delete _cards;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
KPCMCIACard* KPCMCIA::getCard(int num) {
|
|
|
|
if (num >= _cardCnt || num < 0) return NULL;
|
|
|
|
return (*_cards)[num];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void KPCMCIA::updateCardInfo() {
|
|
|
|
for (int i = 0; i < _cardCnt; i++) {
|
|
|
|
int rc = (*_cards)[i]->refresh();
|
|
|
|
/*
|
|
|
|
kdDebug() << "CARD UPDATED: " << i << endl
|
|
|
|
<< " Name: " << (*_cards)[i]->_cardname << endl
|
|
|
|
<< " Device: " << (*_cards)[i]->_device << endl
|
|
|
|
<< " VCC: " << (*_cards)[i]->_vcc << endl
|
|
|
|
<< " VPP: " << (*_cards)[i]->_vpp << endl
|
|
|
|
<< " IRQ: " << (*_cards)[i]->_interrupt << endl
|
|
|
|
<< " Ports: " << (*_cards)[i]->_ports << endl
|
|
|
|
<< " Type: " << (*_cards)[i]->_type << endl
|
|
|
|
<< " Module: " << (*_cards)[i]->_module << endl
|
|
|
|
<< endl;
|
|
|
|
*/
|
|
|
|
if (rc > 0) emit cardUpdated(i);
|
|
|
|
}
|
|
|
|
_timer->start(_refreshSpeed, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void KPCMCIA::setRefreshSpeed(int msec) {
|
|
|
|
_refreshSpeed = msec;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int KPCMCIA::getCardCount() {
|
|
|
|
return _cardCnt;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool KPCMCIA::haveCardServices() {
|
|
|
|
return _haveCardServices;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#include "kpcmcia.moc"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef __linux__
|
|
|
|
static int lookupDevice(const char *x) {
|
|
|
|
TQFile df("/proc/devices");
|
|
|
|
TQString thisreg;
|
|
|
|
|
|
|
|
thisreg = "^[0-9]+ %1$";
|
|
|
|
thisreg = thisreg.arg(x);
|
|
|
|
|
|
|
|
if (df.open(IO_ReadOnly)) {
|
|
|
|
TQTextStream t(&df);
|
|
|
|
TQString s;
|
|
|
|
while (!t.eof()) {
|
|
|
|
s = t.readLine();
|
|
|
|
|
|
|
|
if (s.contains(TQRegExp(thisreg))) {
|
|
|
|
int n = (s.left(3).stripWhiteSpace()).toInt();
|
|
|
|
df.close();
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
df.close();
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int openDevice(dev_t dev) {
|
|
|
|
TQString tmp_path = locateLocal("tmp", TDEGlobal::instance()->instanceName());
|
|
|
|
TQString ext = "_socket%1";
|
|
|
|
|
|
|
|
tmp_path += ext.arg((int)dev);
|
|
|
|
|
|
|
|
int rc = mknod(tmp_path.latin1(), (S_IFCHR | S_IREAD), dev);
|
|
|
|
if (rc < 0) return -1;
|
|
|
|
|
|
|
|
int fd = open(tmp_path.latin1(), O_RDONLY);
|
|
|
|
if (fd < 0) {
|
|
|
|
unlink(tmp_path.latin1());
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (unlink(tmp_path.latin1()) < 0) {
|
|
|
|
close(fd);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return fd;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|