[Из песочницы] Linux, отложенная загрузка драйверов и неработающие прерывания
Сегодня я расскажу о неожиданных проблемах, которые возникли при подключении матричной клавиатуры к ARM-борде под управлением Linux. А конкретно о том, почему драйвер adp5589 не захотел получать прерывания и как мы смогли заставить его это делать.
Кому интересно — добро пожаловать под кат.
Оглавление статьи:
- Описание железной части
- Где у нас проблема?
- Пару слов о Device Tree
- Немного о регистрации устройств и драйверов
- Механизм отложенной загрузки драйверов
- Как заставить всё работать
Описание железа вокруг клавиатуры:
У самой клавиатуры контроллера нет — она подключена по шине I2C с помощью специального контроллера матричной клавиатуры — микросхемы adp5589. У микросхемы есть линия прерывания, заведённая на один из GPIO пинов ARM SoCа. В итоге схема подключения выглядит примерно так:
portb — это порт, на пин которого заведено прерывание от контроллера клавиатуры;
intc — главный контроллер прерываний;
i2c0 — контроллер шины i2c.
Драйвер adp5589 по каким-то причинам упорно не хочет получать номер прерывания. Что же может быть причиной такого поведения? Возможно, для загрузки драйвера клавиатуры не хватает каких-то ресурсов. Может быть не успели загрузиться устройства, от которых он зависит? Давайте посмотрим, от каких устройств он может зависеть:
Во-первых — от контроллера шины I2C, к которой он подключен.
Во-вторых — от контроллера порта, на пин которого у нас заведена линия прерывания.
Теперь посмотрим в каком порядке загружаются драйвера этих устройств:
gic
designware-i2c
adp5589
dw-apb-gpio-port
Ага! Вот и причина — когда загружается драйвер клавиатуры, его interrupt-parent ещё не загружен. Как результат — драйвер клавиатуры не получает номер прерывания. Стандартное решение этой проблемы — механизм отложенной загрузки драйверов.
Его суть в том, что драйвер может потребовать повторной загрузки, если какой-нибудь нужный ему ресурс ещё не доступен. А потребовать он это может, вернув значение -EPROBE_DEFER из своей функции probe. Тогда этот драйвер будет повторно загружен позже. К тому времени или нужный ресурс уже будет доступен, или загрузка драйвера снова будет отложена.
Добавляем проверку в функцию probe драйвера клавиатуры:
if (!client->irq) {
dev_err(&client->dev, "no IRQ boss?\n");
return -EPROBE_DEFER;
}
В надежде смотрим на новый порядок загрузки:
gic
adp5589
designware-i2c
dw-apb-gpio-port
(deferred)adp5589
(deferred)adp5589
(deferred)adp5589
Что-то пошло не так — драйвер клавиатуры повторно загрузился после драйвера GPIO, но прерывания так и не получил. Похоже, придётся зарываться в исходный код глубже, чем ожидалось.
Напрашивается три возможных пути решения:
- Захардкодить номер прерывания прямо в драйвер
- Каким-либо способом задать порядок загрузки драйверов
- Разобраться с механизмом отложенной загрузки драйверов, который почему-то не заработал
Первый вариант:
Вариант рабочий, но не желательный. Подойдёт в качестве временного, но если что-нибудь поменять в аппаратной части (например выход прерывания подключить к другому порту GPIO), то изменения придётся вносить не только в Device Tree, но и в исходный код драйвера.
Второй вариант:
Явно задать порядок загрузки драйверов возможности нет. Так что этот вариант не подходит.
Третий вариант:
Самый правильный. Его и будем рассматривать.
Здесь, пожалуй, стоит кратко рассказать про такую вещь как Device Tree, так как далее будут отсылки к ней.
Device Tree — это одна из форм описания аппаратной части устройства, на котором мы хотим использовать Linux. Представляется в виде дерева узлов, в которых задаётся нужная информация. DT существует в виде текстовых человекочитаемых файлов (.dts; .dtsi) и собираемого из них бинарного файла (.dtb).
Для примера рассмотрим кусочек .dts файла описывающий структуру подключения нашего контроллера клавиатуры к другим устройствам SoCа.
i2c0: i2c@ffc04000 {
compatible = "snps,designware-i2c";
keybs@34 {
compatible = "adi,adp5589";
interrupts = <19 IRQ_TYPE_LEVEL_LOW>;
interrupt-parent = <&portb>;
};
};
intc: intc@fffed000 {
compatible = "arm,cortex-a9-gic";
#interrupt-cells = <3>;
interrupt-controller;
};
portb: gpio-controller@0 {
compatible = "snps,dw-apb-gpio-port";
interrupt-controller;
#interrupt-cells = <2>;
interrupts = <0 165 4>;
interrupt-parent = <&intc>;
};
(Не интересующие нас сейчас узлы и свойства вырезаны для облегчения понимания)
i2c0, keybs, inc и portb — узлы, всё остальное — их свойства. Из кода сразу становится видно что чип контроллера клавиатуры подключен к I2C шине. В свойстве compatible — строка, которая описывает производителя и модель устройства. Именно по этому свойству ОС понимает какой драйвер нужно связать с этим устройством.
interrupt-controller — свойство, указывающее, что это устройство может являться контроллером прерываний, а interrupt-parent указывает к кому подключено прерывание от текущего устройства.
#interrupt-cells — свойство, указывающее на количество параметров, которыми описываются прерывания данного контроллера прерываний, а interrupts — свойство, в котором задаются параметры для данного прерывания.
Например в portb указано: #interrupt-cells = <2> Это значит, что в узлах, для которых portb это interrupt-parent в свойстве interrupts нужно описать два параметра. portb это interrupt-parent для keybs. Смотрим в keybs. Там указано: interrupts = <19 IRQ_TYPE_LEVEL_LOW>. Что это значит?
Здесь описываются два параметра. Первый — это номер пина в порте portb, на который у нас заведена линия прерывания от контроллера клавиатуры. Второй — тип прерывания (по низкому или высокому уровню). Как узнать сколько для контроллера прерываний нужно описывать параметров, и что каждый из них значат? Обычно про это написано в документации. Так, про portb написано в этом файле: Documentation/devicetree/bindings/gpio/snps-dwapb-gpio.txt.
&portb — ссылка на узел portb (в нашем случае ссылка на portb будет равна /soc/gpio@ff709000/gpio-controller@0)
Остальные свойства нам пока не понадобятся, про них, и вообще про Device Tree, подробно можно почитать здесь: devicetree.org/Device_Tree_Usage.
Ещё не лишним будет упомянуть про процесс регистрации устройств и драйверов (не беспокойтесь, к основной теме мы вернёмся уже в следующем абзаце). Согласно Linux Device Model:
Устройство — физический или виртуальный объект, который подключен к шине (возможно тоже виртуальной)
Драйвер — программный объект, который может быть связан с устройством и может выполнять какие-либо функции управления.
Шина — устройство, предназначенное быть «точкой крепления» других устройств. Базовая функциональность всех шин, поддерживаемых ядром, определяется структурой bus_type. В этой структуре объявлена вложенная структура subsys_private, в которой объявлены два списка: klist_devices и klist_drivers.
klist_devices — список устройств, которые подключены к шине.
klist_drivers — список драйверов, которые могут управлять устройствами на этой шине.
Устройства и драйвера добавляются в эти списки с помощью функций device_register и driver_register. Кроме того, device_register и driver_register связывают устройство с подходящим драйвером. device_register проходит по списку драйверов и пытается найти драйвер, подходящий для данного устройства. (driver_register проходит по списку устройств и пытается найти устройства, которыми он может управлять) Проверка подходит ли драйвер для устройства производится с помощью функции match(dev, drv), указатель на которую есть в структуре bus_type.
Теперь можно перейти и к основной теме — реализации механизма отложенной загрузки драйверов. Заглянем в файл drivers/base/dd.c Вот краткое описание того, что мы там увидим:
Для управления повторной загрузкой драйверов имеются два списка — deferred_probe_pending_list и deferred_probe_active_list.
deferred_probe_pending_list — список устройств, для загрузки драйвера которых не хватает каких-то ресурсов.
deferred_probe_active_list — список устройств, драйвер которых можно попробовать запустить повторно.
В функции really_probe вызывается функция probe для шины, на которой расположено устройство. В нашем случае это функция i2c_device_probe и выглядит это так dev→bus→probe (dev). Возвращаемое значение проверяется на ошибки, и, если оно равно -EPROBE_DEFER, то устройство добавляется в deferred_probe_pending_list.
Но самое интересное — как и когда драйвер вызывается заново. Пока драйверы возвращают -EPROBE_DEFER, устройства последовательно добавляются в deferred_probe_pending_list. Но как только для какого-либо драйвера функция probe завершилась успешно, все устройства из deferred_probe_pending_list переносятся в deferred_probe_active_list. Выглядит логично — возможно, того драйвера, который у нас последний был успешно загружен, и не хватало для нормальной загрузки отложенных драйверов. Повторная попытка запуска драйверов из deferred_probe_active_list производится функцией deferred_probe_work_func. В ней вызывается bus_probe_device для каждого устройства из списка.
Вызов bus_probe_device в конечном итоге снова приведёт нас к функции really_probe для пары из нашего устройства и его драйвера (см. выше).
Но подождите! Мы сейчас говорили о вызове функции probe для шины, на которой расположено устройство. То есть о i2c_device_probe. А как же функция probe драйвера клавиатуры? Нет, про неё мы не забыли, она как раз будет вызвана из i2c_device_probe. В этом можно убедиться, взглянув на её код в файле drivers/i2c/i2c-core.с:
static int i2c_device_probe(struct device *dev)
{
struct i2c_client *client = i2c_verify_client(dev);
struct i2c_driver *driver;
int status;
if (!client)
return 0;
driver = to_i2c_driver(dev->driver);
if (!driver->probe || !driver->id_table)
return -ENODEV;
if (!device_can_wakeup(&client->dev))
device_init_wakeup(&client->dev,
client->flags & I2C_CLIENT_WAKE);
dev_dbg(dev, "probe\n");
status = of_clk_set_defaults(dev->of_node, false);
if (status < 0)
return status;
status = dev_pm_domain_attach(&client->dev, true);
if (status != -EPROBE_DEFER) {
//Вот и вызов probe драйвера клавиатуры (в нашем случае)
status = driver->probe(client, i2c_match_id(driver->id_table,
client));
if (status)
dev_pm_domain_detach(&client->dev, true);
}
return status;
}
Ладно, повторная загрузка вроде бы работает, почему же тогда драйвер клавиатуры не получает номера прерывания?
Попробуем отследить то, как номер прерывания должен попасть в наш драйвер.
В функцию adp5589_probe(struct i2c_client *client, const struct i2c_device_id *id) передаётся структура client, одно из полей которой — irq — номер прерывания, которое будет генерировать наше устройство (контроллер клавиатуры). adp5589_probe вызовется из функции i2c_device_probe(struct device * dev). В неё передаётся структура device, из указателя на которую вычисляется указатель на структуру i2c_client (с помощью магии макроса container_of).
Про его работу хорошо расписано здесь.
Значит нужно найти где заполняется структура i2c_client. Заполняется она в функции i2c_new_device(struct i2c_adapter * adap, struct i2c_board_info const * info); Конкретно поле irq копируется из одноимённого поля структуры i2c_board_info.
struct i2c_client *client;
client->irq = info->irq;
Структура i2c_board_info заполняется в функции of_i2c_register_devices(struct i2c_adapter * adap).
info.irq = irq_of_parse_and_map(node, 0);
irq_of_parse_and_map — это обёртка для цепочки из двух функций — of_irq_parse_one и irq_create_of_mapping; функция of_irq_parse_one пытается найти узел, который заявлен в device tree как interrupt-controller для текущего устройства.
Помните эти несколько строчек в device tree?
expander: pca9535@20 {
interrupt-parent = <&portb>;
};
Именно portb и ищет of_irq_parse_one, а по результатам своей работы заполняет структуру of_phandle_args, которая передаётся функции irq_create_of_mapping. irq_create_of_mapping уже и возвращает искомый номер прерывания.
В первый раз of_irq_parse_one не находит порт GPIO, на что ругается в лог:
irq: no irq domain found for /soc/gpio@ff709000/gpio-controller@0!
А что происходит при повторной загрузке драйвера? А ничего. Вызываются то только i2c_device_probe и adp5589_probe.
Вот в чём и проблема. Прерывание устанавливается только в первый раз и остаётся таким навечно, сколько бы мы не выполняли повторную загрузку нашего драйвера.
Проблему нашли, но как её исправить?
Можно попробовать перенести код получения прерывания в i2c_device_probe. До этого нам номер прерывания нигде не требуется, так что проблем возникнуть не должно.
Но лучше давайте заглянем в исходники более свежей версии ядра (у нас установлена версия 3.18) Вот что мы там увидим:
Установку прерывания i2c клиента перенесли в функцию i2c_device_probe.
if (!client->irq && dev->of_node) {
int irq = of_irq_get(dev->of_node, 0);
if (irq == -EPROBE_DEFER)
return irq;
if (irq < 0)
irq = 0;
client->irq = irq;
}
В структуре i2c_board_info хоть и осталось поле irq, но оно не используется. Так что в новых версиях ядра проблема исправлена.
Осталось всего лишь перенести изменения в нашу версию. Все изменения коснутся файла drivers/i2c/i2c-core.c
Добавим в нашу i2c_device_probe установку прерывания клиента i2c, что появилась в свежей версии, а в функции of_i2c_register_devices удаляем установку прерывания.
--- a/drivers/i2c/i2c-core.c
+++ b/drivers/i2c/i2c-core.c
@@ -626,6 +626,17 @@ static int i2c_device_probe(struct device *dev)
if (!client)
return 0;
+ if (!client->irq && dev->of_node) {
+ int irq = of_irq_get(dev->of_node, 0);
+
+ if (irq == -EPROBE_DEFER)
+ return irq;
+ if (irq < 0)
+ irq = 0;
+
+ client->irq = irq;
+ }
+
driver = to_i2c_driver(dev->driver);
if (!driver->probe || !driver->id_table)
return -ENODEV;
@@ -1407,7 +1418,12 @@ static void of_i2c_register_devices(struct i2c_adapter *adap)
continue;
}
- info.irq = irq_of_parse_and_map(node, 0);
+ /*
+ * Now, we don't need to set interrupt here, because we set
+ * it in i2c_device_probe function
+ * info.irq = irq_of_parse_and_map(node, 0);
+ */
+
info.of_node = of_node_get(node);
info.archdata = &dev_ad;
Проверяем — клавиатура работает. Смотрим в /proc/interrupt:
$ grep 'adp5589_keys' /proc/interrupts
305: 2 - 20 adp5589_keys
Нажмём несколько кнопок:
$ grep 'adp5589_keys' /proc/interrupts
305: 6 - 20 adp5589_keys
Проблема решена.