Qt Extended includes an API for accessing Ethernet, Storage and Serial USB gadgets. The Greenphone USB gadget stack provides support for Ethernet and Serial gadgets. In this tutorial we will demonstrate how to implement the provider class for the Ethernet gadget.
Ethernet Provider
To implement an Ethernet provider class, subclass QUsbEthernetGadget and reimplement all of the virtual functions. The class declaration:
#include <QUsbEthernetGadget>
class QUsbManager;
class GreenphoneEthernetGadgetProvider : public QUsbEthernetGadget
{
Q_OBJECT
public:
GreenphoneEthernetGadgetProvider(const QString &group = QString(), QObject *parent = 0);
~GreenphoneEthernetGadgetProvider();
public slots:
void setVendorId(const quint16 id);
void setProductId(const quint16 id);
void setVendor(const QByteArray &vendor);
void setProduct(const QByteArray &product);
void saveConfig();
void activate();
void deactivate();
void setRemoteMac(const QByteArray &mac);
void setLocalMac(const QByteArray &mac);
private:
QUsbManager *m_manager;
QByteArray m_defaultLocalMac;
QByteArray m_defaultRemoteMac;
void initFromConfig();
private slots:
void loadModule();
void abort();
};
In the constructor we check whether the Ethernet gadget is active by parsing /proc/usbd, stored in the PeripheralController/Path setting. Because the Greenphone USB gadget stack does not provide a method of retrieving the configuration of the activated gadget, we always initialize the gadget from the configuration settings stored in the Usb.conf settings file.
GreenphoneEthernetGadgetProvider::GreenphoneEthernetGadgetProvider(const QString &group, QObject *parent)
: QUsbEthernetGadget(group, parent, Server), m_manager(0)
{
QSettings settings("Trolltech", "Usb");
QString function = settings.value("PeripheralController/Path").toString();
setValue("gadget", QByteArray(GADGET_NAME));
QProcess tat;
tat.start("tat", QStringList() << "remotemac");
tat.waitForFinished(-1);
m_defaultRemoteMac = tat.readAllStandardOutput().trimmed();
tat.start("tat", QStringList() << "localmac");
tat.waitForFinished(-1);
m_defaultLocalMac = tat.readAllStandardOutput().trimmed();
QFile file(function);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
initFromConfig();
setValue("active", false);
return;
}
foreach (QByteArray line, file.readAll().split('\n')) {
if (line.startsWith("Function: ")) {
initFromConfig();
if (line.mid(10) == "Generic Network")
setValue("active", true);
else
setValue("active", false);
}
}
}
The Greenphone Ethernet gadget supports setting the USB vendor and product ids. Reimplement setVendorId() and setProductId() to store the new values in the Value Space.
void GreenphoneEthernetGadgetProvider::setVendorId(const quint16 id)
{
setValue("vendorId", id);
}
void GreenphoneEthernetGadgetProvider::setProductId(const quint16 id)
{
setValue("productId", id);
}
Setting the USB vendor and product strings is not supported. Reimplement setVendor() and setProduct() to do nothing.
void GreenphoneEthernetGadgetProvider::setVendor(const QByteArray &)
{
}
void GreenphoneEthernetGadgetProvider::setProduct(const QByteArray &)
{
}
Setting the local and remote MAC addresses is supported. Reimplement setRemoteMac() and setLocalMac() to store the new values in the Value Space.
void GreenphoneEthernetGadgetProvider::setRemoteMac(const QByteArray &mac)
{
setValue("remoteMac", mac);
}
void GreenphoneEthernetGadgetProvider::setLocalMac(const QByteArray &mac)
{
setValue("localMac", mac);
}
The saveConfig() slot saves all of the configuration settings to the Usb.conf configuration file.
void GreenphoneEthernetGadgetProvider::saveConfig()
{
QSettings settings("Trolltech", "Usb");
settings.beginGroup(GADGET_NAME);
QVariant v = value("productId");
if (v.isValid())
settings.setValue("ProductId", v.toUInt());
v = value("vendorId");
if (v.isValid())
settings.setValue("VendorId", v.toUInt());
v = value("localMac");
if (v.isValid() && v.toByteArray() != m_defaultLocalMac)
settings.setValue("LocalMac", v.toString());
v = value("remoteMac");
if (v.isValid() && v.toByteArray() != m_defaultRemoteMac)
settings.setValue("RemoteMac", v.toString());
settings.endGroup();
}
Before activating the Ethernet gadget we must ensure that no other gadget is active, deactivating it if there is an gadget already active. Because the call to QUsbGadget::deactivate() is asynchronous we do the actual activation of the Ethernet gadget upon receipt of the QUsbManager::deactivateCompleted() signal, or abort the activation if QUsbManager::deactivateAborted() is received.
void GreenphoneEthernetGadgetProvider::activate()
{
if (!value("active", false).toBool()) {
if (!m_manager)
m_manager = new QUsbManager;
if (m_manager->canActivate(GADGET_NAME)) {
loadModule();
} else {
connect(m_manager, SIGNAL(deactivateCompleted()), this, SLOT(loadModule()));
connect(m_manager, SIGNAL(deactivateAborted()), this, SLOT(abort()));
m_manager->deactivateGadgets();
}
}
}
The loadModule() private slot activates the Ethernet gadget by loading the required kernel modules.
void GreenphoneEthernetGadgetProvider::loadModule()
{
QStringList args;
args << "net_fd";
QVariant v = value("productId");
if (v.isValid())
args << "product_id=0x" + QString::number(v.toInt(), 16);
v = value("vendorId");
if (v.isValid())
args << "vendor_id=0x" + QString::number(v.toInt(), 16);
v = value("localMac");
if (v.isValid())
args << "local_mac_address=" + v.toString().remove(':');
v = value("remoteMac");
if (v.isValid())
args << "remote_mac_address=" + v.toString().remove(':');
qLog(USB) << "loading module" << args;
if (QProcess::execute("/sbin/insmod", args) != 0) {
abort();
return;
}
args.clear();
args << "bvd_bi";
qLog(USB) << "loading module" << args;
if (QProcess::execute("/sbin/insmod", args) != 0) {
qLog(USB) << "unloading module net_fd";
QProcess::execute("/sbin/rmmod net_fd");
abort();
} else {
setValue("interface", "eth0");
setValue("active", true);
qLog(USB) << "ethernet gadget activated";
QTimer::singleShot(500, this, SIGNAL(activated()));
}
}
The abort() private slot handles failed gadget activation.
void GreenphoneEthernetGadgetProvider::abort()
{
removeValue("interface");
qLog(USB) << "ethernet gadget activate failed";
emit activateFailed();
}
Reimplement the deactivate() slot to unload the Ethernet gadget modules.
void GreenphoneEthernetGadgetProvider::deactivate()
{
if (value("active", false).toBool()) {
qLog(USB) << "unloading modules bvd_bi net_fd";
if (QProcess::execute("/sbin/rmmod bvd_bi net_fd") == 0) {
setValue("active", false);
removeValue("interface");
qLog(USB) << "ethernet gadget deactivated";
emit deactivated();
} else {
qLog(USB) << "ethernet gadget deactivate failed";
emit deactivateFailed();
}
}
}
Helper function to load the Ethernet gadget configuration of the Usb.conf settings file.
void GreenphoneEthernetGadgetProvider::initFromConfig()
{
QSettings settings("Trolltech", "Usb");
settings.beginGroup(GADGET_NAME);
QVariant v = settings.value("VendorId");
if (v.isValid())
setVendorId(v.toUInt());
else
removeValue("vendorId");
v = settings.value("ProductId");
if (v.isValid())
setProductId(v.toUInt());
else
removeValue("productId");
v = settings.value("RemoteMac");
if (v.isValid())
setRemoteMac(v.toByteArray());
else
setRemoteMac(m_defaultRemoteMac);
v = settings.value("LocalMac");
if (v.isValid())
setLocalMac(v.toByteArray());
else
setLocalMac(m_defaultLocalMac);
settings.endGroup();
}
The source code for the Greenphone Ethernet gadget provider is located in devices/greenphone/server/greenphoneethernetgadget.{cpp,h}.
Server Task
The next step is to create a new Server Task that will instantiate each gadget provider. As the server task has no other function apart from instantiating the gadget providers we will use a static task.
class GreenphoneUsbGadgetTask
{
public:
static void loadProviders();
};
The single member function loads all of the support gadget providers that it understands, and activates the default gadget if one is specified.
void GreenphoneUsbGadgetTask::loadProviders()
{
QSettings settings("Trolltech", "Usb");
settings.beginGroup("PeripheralController");
QList<QByteArray> supportedGadgets = settings.value("SupportedGadgets").toByteArray().split(',');
QByteArray defaultGadget = settings.value("DefaultGadget").toByteArray();
foreach (QByteArray gadget, supportedGadgets) {
if (gadget == "GreenphoneEthernet") {
GreenphoneEthernetGadgetProvider *gp = new GreenphoneEthernetGadgetProvider("Greenphone");
if (gadget == defaultGadget)
gp->activate();
} else if (gadget == "GreenphoneSerial") {
GreenphoneSerialGadgetProvider *gp = new GreenphoneSerialGadgetProvider("Greenphone");
if (gadget == defaultGadget)
gp->activate();
}
}
}
Finally we register our task with the task system.
QTOPIA_STATIC_TASK(GreenphoneUsbGadget, GreenphoneUsbGadgetTask::loadProviders());
Configuration
We need modify the Usb.conf settings file to disable the standard gadget providers and activate our alternative providers. This is done by removing the standard gadget providers from the SupportedGadgets setting and replacing them with the alternate implementation GreenphoneEthernet and GreenphoneSerial.
[PeripheralController]
Path=/proc/usbd
SupportedGadgets="GreenphoneEthernet,GreenphoneSerial"
DefaultGadget=GreenphoneEthernet
[GreenphoneEthernet]
ProductId=2
VendorId=26214
RemoteMac=00:11:22:33:44:55
LocalMac=00:66:77:88:99:AA
[GreenphoneSerial]
ProductId=1
VendorId=26214