Сегодня я расскажу о неожиданных проблемах, которые возникли при подключении матричной клавиатуры к 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
Проблема решена.
ссылка на оригинал статьи http://habrahabr.ru/post/271983/
Добавить комментарий