суббота, 27 ноября 2010 г.

Как сделать невозможное?

Ответ на вопрос прост - не останавливаться, какой бы сложной не была задача. Даже если все вокруг говорят, что это невозможно.
Моя задача была заменить драйвер устройства в Linux, вшитый намертво в ядро. Были бы исходники этого ядра - все решалось бы сравнительно просто, но в нашем случае (ядро читалки Nook) исходники были не рабочие и годичной давности. На x86 я бы дизассемблировал место инициализации драйвера в ядре и забил NOPами, но тут уже ARM архитектура и ядро в zImage..

Сам процесс написания и отладки драйвера был довольно долгим - пришлось допиливать исходники ядра, чтобы устройство хоть как-то загружалось, но это тема для другого рассказа. Важно то, что получился собственный драйвер (бекпорт с другого устройства с дополнительными фозможностями), который может работать как модуль ядра и инициализируется 1в1 так же, как и тот, что надо заменить.

Первые эксперименты были прозаичны - хитрые вызовы insmod/rmmod, шаманство с именем драйвера и пр. Затем я обратился за советом к друзьям-линуксоидам и они подтвердили, что стандартными средствами такого не добиться. Исключения очень редки - если драйвер может принимать параметры с адресами устройств, командами включиться/выключиться и пр., то можно еще что-то сделать, но в нашем случае никаких параметров не было.

Затем начались эксперименты на уровне исходников. Мое внимание привлек такой код:

static int __devinit synaptics_ts_init(void)
{
synaptics_wq = create_singlethread_workqueue("synaptics_wq");
if (!synaptics_wq)
return -ENOMEM;
return i2c_add_driver(&synaptics_ts_driver);
}

static void __exit synaptics_ts_exit(void)
{
i2c_del_driver(&synaptics_ts_driver);
if (synaptics_wq)
destroy_workqueue(synaptics_wq);
}

Если существует функция i2c_del_driver для выгрузки драйвера, то почему нельзя вызвать ее из своего модуля, чтобы выгрузить чужой? К сожалению, в ядре Linux есть ситуации, когда не имея указателя никак нельзя обратиться к обьекту. Например, метод destroy_workqueue можно вызывать только с указателем очереди, а он есть лишь у того, кто эту очередь создал и без получить его малореально - никаких find_workqueue в природе нет.

В нашем же случае шанс был - пусть и i2c не позволяет искать драйвер по имени, но по коду i2c-core.c стало понятно, что используются низкоуровневые вызовы device_register/device_unregister из linux/device.h. Там же нашлась функция driver_find, которая ищет драйвер по имени и идентификатору шины. Имя встроенного драйвера известно, а шина используется i2c и ее идентификатор в переменной i2c_bus_type:

struct device_driver * other;

other = driver_find(SYNAPTICS_I2C_RMI_NAME, &i2c_bus_type);

if (other)
{
printk("Previous driver found: %s\n", other->name);
return -ENOMEM;
}

Этот код корректно находит предыдущий драйвер и выводит его имя. Конечно же, этого было мало, потому я добавил driver_unregister(other) и получил Kernel Panic :)
Логично предположить, что раз мы регистрируем драйвер через i2c_add_driver, то и удалять его надо через i2c_del_driver, а не напрямую. Из описания структуры i2c_driver видно, что она содержит device_driver, которая и возвращается функцией driver_find. Для преобразования из указателя на вложенную структуру к родительской есть несколько подходов, но проще всего использовать стандартную конверсию to_i2c_driver. Получился вот такой код:

struct i2c_driver * otherDriver;
struct device_driver * other;

other = driver_find(SYNAPTICS_I2C_RMI_NAME, &i2c_bus_type);

if (other)
{
otherDriver = to_i2c_driver(other);
printk(KERN_ERR "Previous driver found: %s, addr 0x%x, owner %x\n", other->name, (int)otherDriver, (int)other->owner);
i2c_del_driver(otherDriver);
}

Что характерно, он тоже приводит к Kernel Panic. На этом этапе я уже было решил, что дерегистрация невозможна, но случайно заметил в drivers/base/core.c, что после вызова driver_find обычно вызывается функция put_driver. Оказалось, что у драйверов, как обектов ядра, есть интрузивный подсчет ссылок и после driver_find счетчик увеличивается на единицу, что не дает выгрузить драйвер во время i2c_del_driver. Добавление этого вызова поставило все на свои места и встроенный драйвер стал корректно выгружаться.

Конечно, сразу все не заработало, т.к. еще существовала workqueue с таким же именем, да и встроенный драйвер оказался "нечист на руку" - не удалял sysfs файлы при выгрузке, но это уже все было решаемо.

Результатом этих "танцев с бубном" стал собственный драйвер тачскрина для Nook с поддержкой Multitouch. Подробнее о самом драйвере можно почитать по этим ссылкам:
http://nookdevs.com/Multitouch
http://www.the-ebook.org/forum/viewtopic.php?t=16728

Дальше..

вторник, 23 ноября 2010 г.

Книжкая полка для Nook

Некоторое время назад я написал для Nook простенький менеджер файлов, без возможности удаления, копирования и пр. Потом этот браузер научился фильтровать файлы по типу, а затем и извлекать описания из книг, показывать обложки и пр. Сейчас это полноценная книжкая полка с Cover Flow (листанием обложек), которая при необходимости показывает все файлы устройства, позволяет устанавливать программы, хранит список последних книг.

Вот небольше видео, как это работает:




Хочу напомнить, что программа - не полноценный файловый менеджер, у нее нет возможности что-либо удалять или копировать. В отличие от Trook и NookFileManager, используется верхний экран для отображения файлов.

Пара скриншотов:





Дополнительно программа позволяет выбрать, что именно показывать:
- все файлы (в т.ч. скрытые и системные);
- только документы (картинки, музыку, книги и пр.);
- только книги;

При выходе из программы сохраняется последняя выбранная папка, а каждый открытый документ сохраняется в виртуальную папку "Последние документы".
Заодно, программа позволяет выбрать .apk файл и установить его. Протестирована совместимость с встроеными ридерами epub/pdf/pdb файлов, а также с портом FBReader от mynook. Из файлов epub/fb2/fb2.zip извлекаются данные об авторе и названии книги, а также о ее серии. Открытые через программу книги затем корректно открываются через Reading Now. Также в программе есть список последних 9ти открытых документов и режим листания обложек (Cover Flow). Можно прятать выбраный файл, после чего он будет показываться только в режиме "Просмотр скрытых файлов". Также есть возможность просмотра изображений в форматах PNG и JPEG.

В данный момент стабильная версия программы имеет номер 1.2. Это значит, что в ней реализовано все, что задумывалось изначально, а также некоторые другие возможности вроде просмотра картинок, иконок для папок и пр. Полка работает под любыми версиями Android от 1.5 до 2.2, но в основном расчитана под экран Nook разрешением 600х944.

Программа доступна в двух версиях - как отдельное приложение и как замена B&N Library. При запуске в виде замены B&N Library пользователя спросят, какую библиотеку запустить - собственную или оригинальную. При использовании патченного framework-res.apk можно выбрать галочку "использовать эту программу постоянно".

Программа в виде замены Library

Ссылка: http://runserver.net/nook/nookFileBrowser.apk
Размер: 133593
md5: 3b4906752f89859f17de4ac55e2e385d

Отдельное приложение:

Ссылка: http://runserver.net/nook/nookFileBrowser_standalone.apk
Размер: 133573
md5: d04bfe255d19f751af3c4f7d981065aa

Дальше..