0
votes

My son is implementing a server on a Raspberry Pi that allows control of the GPIO pins via a network connection. He has discovered some strange behaviour, which at first seemed like a bug (but see answer below).

First, the OS being used is Raspbian, a version of Debian Linux. He is using the standard system file to control the GPIO ports.

We start with a GPIO pin, e.g. pin 17, in a non-exported state. For example,

echo "17" > /sys/class/gpio/unexport

Now, if the server is asked to turn on pin 17, it does the following:

  1. Opens the /sys/class/gpio/export, writes "17" to it, and closes the export file
  2. Open the /sys/class/gpio/gpio17/direction file for read, examines it to see if it is set as input or output. Closes the file. Then, if necessary, re-opens the file for write and writes "out" to the file, to set the pin as an output pin, and closes the direction file.

At this point, we should be able to open /sys/class/gpio/gpio17/value for write, and write a "1" to it.

However, the permissions on the /sys/class/gpio/gpio17/value file exists but the group permissions is read-only. If we put in a "sleep" in order to wait for a fraction of a second, the permissions change so the group permission has write permissions.

I would have expected that the OS should not return from the write to the direction file until it had set the permissions on the value file correctly.

Why is this happening? It seems like a bug. Where should I report this (with more detail...)? See answer below.

What follows are the relevant bits of code. The code has been edited and paraphrased a bit, but it is essentially what is being used. (Keep in mind it's the code of a grade 12 student trying to learn C++ and Unix concepts):

class GpioFileOut
{
    private:
        const string m_fName;
        fstream m_fs;

    public:
        GpioFileOut(const string& sName)
        : m_fName(("/sys/class/gpio/" + sName).c_str())
        {
            m_fs.open(m_fName.c_str());
            if (m_fs.fail())
            {
                cout<<"ERROR: attempted to open " << m_fName << " but failed" << endl << endl;
            }
            else
            {
                cout << m_fName << " opened" << endl;
            }
        }

        ~GpioFileOut()
        {
            m_fs.close();
            cout << m_fName << " closed" << endl << endl;
        }

        void reOpen()
        {
            m_fs.close();
            m_fs.open(m_fName);
            if (m_fs.fail())
            {
                cout<<"ERROR: attempted to re-open " << m_fName << " but failed" << endl << endl;
            }
            else
            {
                cout << m_fName << " re-opened" << endl;
            }
        }

        GpioFileOut& operator<<(const string &s)
        {
            m_fs << s << endl;
            cout << s << " sent to " << m_fName << endl;
            return *this;
        }

        GpioFileOut& operator<<(int n)
        {
            return *this << to_string(n); //ostringstream
        }

        bool fail()
        {
            return m_fs.fail();
        }
};

class GpioFileIn
{
    private:
        ifstream m_fs;
        string m_fName;

    public:
        GpioFileIn(const string& sName)
        : m_fs( ("/sys/class/gpio/" + sName).c_str())
        , m_fName(("/sys/class/gpio/" + sName).c_str())
        {
            if (m_fs <= 0 || m_fs.fail())
            {
                cout<<"ERROR: attempted to open " << m_fName << " but failed" << endl;
            }
            else
            {
                cout << m_fName << " opened" << endl;
            }
        }

        ~GpioFileIn()
        {
            m_fs.close();
            cout << m_fName << " closed" << endl << endl;
        }

        void reOpen()
        {
            m_fs.close();
            m_fs.open(m_fName);
            if (m_fs <= 0 || m_fs.fail())
            {
                cout<<"ERROR: attempted to re-open " << m_fName << " but failed" << endl;
            }
            else
            {
                cout << m_fName << " re-opened" << endl;
            }
        }

        GpioFileIn& operator>>(string &s)
        {
            m_fs >> s;
            cout << s << " read from " << m_fName << endl;
            return *this;
        }
        bool fail()
        {
            return m_fs.fail();
        }
};

class Gpio
{
    public:
        static const bool OUT = true;
        static const bool IN = false;
        static const bool ON = true;
        static const bool OFF = false;

        static bool setPinDirection(const int pinId, const bool direction)
        {
            GpioFileOut dirFOut(("gpio" + to_string(pinId) + "/direction").c_str());
            if (dirFOut.fail())
            {
                if (!openPin(pinId))
                {
                    cout << "ERROR! Pin direction not set: Failed to export pin" << endl;
                    return false;
                }
                dirFOut.reOpen();
            }
            dirFOut << (direction == OUT ? "out" : "in");
        }


        static bool setPinValue(const int pinId, const bool pinValue)
        {
            string s;
            {
                GpioFileIn dirFIn(("gpio" + to_string(pinId) + "/direction").c_str());
                if (dirFIn.fail())
                {
                    if (!openPin(pinId))
                    {
                        cout << "ERROR! Pin not set: Failed to export pin"<<endl;
                        return false;
                    }
                    dirFIn.reOpen();
                }
                dirFIn >> s;
            }

            if (strncmp(s.c_str(), "out", 3) == 0)
            {
                struct stat _stat;
                int nTries = 0;
                string fname("/sys/class/gpio/gpio"+to_string(pinId)+"/value");

                for(;;)
                {
                    if (stat(fname.c_str(), &_stat) == 0)
                    {
                        cout << _stat.st_mode << endl;
                        if (_stat.st_mode & 020 )
                            break;
                    }
                    else
                    {
                        cout << "stat failed. (Did the pin get exported successfully?)" << endl;
                    }

                    cout << "sleeping until value file appears with correct permissions." << endl;
                    if (++nTries > 10)
                    {
                        cout << "giving up!";
                        return false;
                    }
                    usleep(100*1000);
                };
                GpioFileOut(("gpio" + to_string(pinId) + "/value").c_str()) << pinValue;
                return true;
            }
            return false;
        }

        static bool openPin(const int pinId)
        {
            GpioFileOut fOut("export");
            if (fOut.fail())
                return false;
            fOut << to_string(pinId);
            return true;
        }
}

int main()
{
    Gpio::openPin(17);
    Gpio::setPinDirection(17, Gpio::OUT)
    Gpio::setPinValue(17, Gpio::ON);
}

The key point is this: without the for(;;) loop that stat's the file, the execution fails, and we can see the permissions change on the file within 100ms.

1
You should better try to post your question to raspberrypi.stackexchange.commpromonet
And thanks for the edits - now I know how to make a list.David I. McIntosh
You are welcome, I tried to reproduce the situation you described, but on my raspberry the /sys/class/gpio/gpioxx/value is always writable independendly of direction configuration. Perhaps udev configuration is different ?mpromonet
You only have milliseconds before the perms change. We couldn't figure out why setting the value to 1 was failing, but when we stat the file immediately after (coding in C, so little delay), the perms are still read-only. Putting in a sleep-until-writeable loop worked, and only required one iteration of the loop. Can't remember the exact wait time, but we used usleep and only waited for milliseconds. I will check into udev configuration. Do you have a reference? Thanks.David I. McIntosh
Also, value file is always, eventually, writeable, independently of direction configuration, as you say. The issue is that it is not writeable the instant it is exported, which seems to me like a bug. Multi-threading is always so tricky. Seems someone somewhere made a small mistake.David I. McIntosh

1 Answers

2
votes

From a kernel perspective, the 'value' files for each GPIO pin that has been exported are created with mode 0644 and ownership root:root. The kernel does not do anything to change this when you write to the 'direction' file.

The behavior you are describing is due to the operation of the systemd udev service. This service listens for events from the kernel about changes in device state, and applies rules accordingly.

When I tested on my own Pi, I did not experience the behavior you described - the gpio files in /sys are all owned by root:root and have mode 0644, and did not change regardless of direction. However I am running Pidora, and I could not find any udev rules in my system relating to this. I am assuming that Raspbian (or maybe some package you have added to your system) has added such rules.

I did find this thread where some suggested rules are mentioned. In particular this rule which would have the effect you describe:

SUBSYSTEM=="gpio*", PROGRAM="/bin/sh -c 'chown -R root:gpio /sys/class/gpio; chmod -R 770 /sys/class/gpio; chown -R root:gpio /sys/devices/virtual/gpio; chmod -R 770 /sys/devices/virtual/gpio'"

You can search in /lib/udev/rules.d, /usr/lib/udev/rules.d and /etc/udev/rules.d for any files containing the text 'gpio' to confirm if you have such rules. By the way, I would be surprised if the change was triggered by changing direction on the pin, more likely by the action of exporting the pin to userspace.

The reason you need to sleep for a while after exporting the device is that until your process sleeps, the systemd service may not get a chance to run and action the rules.

The reason it is done like this, rather than just having the kernel take care of it, is to push policy implementation to userspace in order to provide maximum flexibility without overly complicating the kernel itself.

See: systemd-udevd.service man page and udev man page.