Home · All Namespaces · All Classes · Grouped Classes · Modules · Functions codeless banner

Tutorial: Supporting alternative USB Gadget Stacks

Introduction

Qt Extended includes an API that applications can use to control the USB gadget feature of a device. The default implementation that is provided utilizes the Linux USB gadget stack available in 2.6 kernel. There is a need to support alternative USB gadget stacks, either 3rd party stacks or older incompatible gadget stacks.

This tutorial walks you through the steps of implementing USB gadget provider classes for the USB gadget stack found on the Greenphone. The gadget stack on the Greenphone is a deprecated stack found in some versions of the 2.4 series Linux kernel.

In this tutorial we will override the default gadget provider classes by

  1. implementing an alternative Ethernet gadget provider class
  2. instantiated the alternative provider classes in a custom server task
  3. preventing the server from loaded the standard provider classes

The source code for this tutorial can be found in the server directory of the Greenphone device profile.

Gadget Provider Classes

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 == "GreenphoneStorage") {
                GreenphoneStorageGadgetProvider *gp = new GreenphoneStorageGadgetProvider("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


Copyright © 2009 Trolltech Trademarks
Qt Extended 4.4.3