На мгновение появляется Raspberry Pi GPIO / файл значений с неправильными разрешениями

Мой сын внедряет сервер на Raspberry Pi, который позволяет управлять GPIO через сетевое соединение. Он обнаружил странное поведение, которое сначала показалось ошибкой (но см. Ответ ниже).

Во-первых, используемая ОС - это Raspbian, версия Debian Linux. Он использует стандартный системный файл для управления портами GPIO.

Начнем с вывода GPIO, например вывод 17, в неэкспортированном состоянии. Например,

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

Теперь, если серверу предлагается включить вывод 17, он выполняет следующие действия:

  1. Открывает /sys/class/gpio/export, записывает в него "17" и закрывает файл экспорта.
  2. Откройте /sys/class/gpio/gpio17/direction файл для чтения, исследует его, чтобы увидеть, установлен ли он как ввод или вывод. Закрывает файл. Затем, если необходимо, повторно открывает файл для записи и записывает «out» в файл, чтобы установить вывод как выходной вывод, и закрывает файл направления.

На этом этапе мы должны иметь возможность открыть /sys/class/gpio/gpio17/value для записи и записать в него «1».

Однако разрешения для файла /sys/class/gpio/gpio17/value существуют, но разрешения группы доступны только для чтения. Если мы перейдем в «спящий режим», чтобы подождать долю секунды, разрешения изменятся, так что разрешение группы будет иметь разрешения на запись.

Я ожидал, что ОС не должна возвращаться из записи в direction файл, пока она не установит права доступа к файлу значений правильно.

Почему это происходит? Похоже на ошибку. Куда мне сообщить об этом (подробнее ...)? См. Ответ ниже.

Ниже приведены соответствующие фрагменты кода. Код был отредактирован и немного перефразирован, но по сути это то, что используется. (Имейте в виду, что это код ученика 12-го класса, пытающегося изучить концепции C ++ и Unix):

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);
}

Ключевой момент заключается в следующем: без for(;;) цикла, который stat является файлом, выполнение завершается неудачно, и мы можем видеть изменение разрешений для файла в течение 100 мс.


person David I. McIntosh    schedule 06.09.2014    source источник
comment
Вам лучше попытаться отправить свой вопрос на raspberrypi.stackexchange.com   -  person mpromonet    schedule 06.09.2014
comment
И спасибо за правки - теперь я знаю, как составлять список.   -  person David I. McIntosh    schedule 06.09.2014
comment
Пожалуйста, я попытался воспроизвести описанную вами ситуацию, но на моем raspberry значение / sys / class / gpio / gpioxx / всегда доступно для записи независимо от конфигурации направления. Может, конфигурация udev другая?   -  person mpromonet    schedule 06.09.2014
comment
У вас есть только миллисекунды до изменения химической завивки. Мы не могли понять, почему установка значения на 1 не удалась, но когда мы фиксируем файл сразу после (кодирование на C, такая небольшая задержка), perms по-прежнему доступны только для чтения. Включение цикла ожидания до записи сработало, и потребовалась только одна итерация цикла. Не могу вспомнить точное время ожидания, но мы использовали usleep и ждали только миллисекунды. Я проверю конфигурацию udev. У вас есть ссылка? Спасибо.   -  person David I. McIntosh    schedule 06.09.2014
comment
Кроме того, файл значений всегда, в конечном счете, доступен для записи, независимо от конфигурации направления, как вы говорите. Проблема в том, что он не доступен для записи в момент экспорта, что мне кажется ошибкой. Многопоточность всегда сложна. Кажется, кто-то где-то допустил небольшую ошибку.   -  person David I. McIntosh    schedule 07.09.2014
comment
Кстати, конфигурация udev будет такой же, как и в стандартном дистрибутиве NOOBS - мы ничего не меняли.   -  person David I. McIntosh    schedule 07.09.2014
comment
Я пробую цикл в C, вызывающий unport / export / setdirection out / write out без проблем. Добавление кода к вопросу может быть полезным   -  person mpromonet    schedule 07.09.2014
comment
Я бы сказал, что какой-то другой процесс (udev? Какой-то другой демон?) Меняет разрешения, а также право собственности. Исходный код ядра для драйверов GPIO, похоже, не включает в себя возможности изменять разрешения в зависимости от направления и фактически инициализирует право собственности на root: root. См. github.com/raspberrypi/linux /blob/rpi-3.12.y/drivers/gpio/ - функция gpio_direction_store.   -  person harmic    schedule 10.09.2014


Ответы (1)


С точки зрения ядра файлы «значений» для каждого экспортированного вывода GPIO создаются с режимом 0644 и владельцем root: root. Ядро не делает ничего, чтобы изменить это, когда вы пишете в файл 'direction'.

Описываемое вами поведение связано с работой службы systemd udev. Эта служба отслеживает события ядра об изменениях в состоянии устройства и соответственно применяет правила.

Когда я тестировал свой собственный Pi, я не испытал описанного вами поведения - все файлы gpio в / sys принадлежат root: root, имеют режим 0644 и не меняются независимо от направления. Однако я использую Pidora и не могу найти в своей системе правил udev, относящихся к этому. Я предполагаю, что Raspbian (или, может быть, какой-то пакет, который вы добавили в свою систему) добавил такие правила.

Я нашел эту ветку, в которой упоминаются некоторые предлагаемые правила. В частности, это правило, которое будет иметь эффект, который вы описываете:

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'"

Вы можете выполнить поиск в /lib/udev/rules.d, /usr/lib/udev/rules.d и /etc/udev/rules.d любых файлов, содержащих текст «gpio», чтобы убедиться, что у вас есть такие правила. Кстати, я был бы удивлен, если бы изменение было вызвано изменением направления вывода, скорее всего, действием экспорта вывода в пользовательское пространство.

Причина, по которой вам нужно некоторое время спать после экспорта устройства, заключается в том, что до тех пор, пока ваш процесс не перейдет в спящий режим, служба systemd может не иметь возможности запустить и выполнить правила.

Причина, по которой это делается именно так, а не просто ядро ​​заботится об этом, заключается в том, чтобы продвинуть реализацию политики в пользовательское пространство, чтобы обеспечить максимальную гибкость без чрезмерного усложнения самого ядра.

См. Справочную страницу systemd-udevd.service и < справочную страницу href = "http://www.freedesktop.org/software/systemd/man/udev.html" rel = "nofollow"> udev.

person harmic    schedule 10.09.2014
comment
Я был бы удивлен, если бы изменение было вызвано изменением направления пина, скорее всего, действием экспорта пина в пользовательское пространство, - согласился. Файл направления ведет себя точно так же, поэтому я уверен, что вы правы. - person David I. McIntosh; 10.09.2014
comment
Это звучит как бах. Я попрошу сына проверить детали дома сегодня вечером и вернуться. Один вопрос: ваша установка (файлы, принадлежащие root: root, perms = 0644) подразумевает, что только root может изменять направление или значение. Это кажется странной настройкой, но я предполагаю, что идея состоит в том, что если вы собираетесь использовать gpio, вы должны ввести некоторые правила udev, чтобы изменить настройку - кажется довольно опасным требовать, чтобы любой процесс, который хочет использовать gpio, запускался как корень. - person David I. McIntosh; 10.09.2014
comment
@ DavidI.McIntosh Моя установка не содержит никаких правил udev для GPIO (я не уверен, что это недосмотр в pidora, какой-то пакет, который я не установил, или, возможно, вы должны добавить их вручную), поэтому у меня ядро ​​по умолчанию разрешения, которые носят ограничительный характер. Как вы говорите, идея состоит в том, что вам следует добавить эти правила, если вы хотите использовать GPIO из пользовательского пространства. - person harmic; 11.09.2014
comment
Вы были совершенно правы. В файлах правил была именно такая строка (ну, с && вместо;). Не уверен, что это rasberian идет с этим правилом, или если мы добавили какой-то пакет, который добавлял правило - я подозреваю, что последнее. Однако источник правила здесь не важен. Итак, теперь мы понимаем, как это работает, и что задержка является результатом замысла. Хорошо знать. Спасибо за помощь. - person David I. McIntosh; 14.09.2014