Powered By Blogger

Sunday, October 28, 2007

Toshiba Satellite A100-811: Bluetooth (solved)

Привет, мир! Я долго и упорно молчал, как румынский партизан и вот, наконец нашёл о чём написать :) Долго мне не удавалось заставить полноценно работать встроенный в ноутбук Bluetooth-адаптер. В конце-концов, задача была решена. Не скажу, что решение оказалось экстраординарным, но как это часто бывает, правильные мысли появляются пост-фактум.

Как писалось ранее, в некоторых моделях Toshiba Bluetooth включается через интерфейс HCI, в некоторых - через ACPI. Но стандартные драйвера toshiba и toshiba_acpi, входящие в ядро, для меня так и не заработали. Таким образом, первая проблема, с которой я здесь столкнулся, оказалось отсутствие драйвера. После некоторых поисков я наткнулся на проект omnibook на sourceforge.net. Предполагается, что все необходимые дайверы были скомпилированы статически или в виде модулей. Конфигурация приблизительно такая:

Configuration for 2.6 kernels
  Networking --->

<*> Bluetooth subsystem support --->
--- Bluetooth subsystem support <M> L2CAP protocol support <M> SCO links support <M> RFCOMM protocol support [*] RFCOMM TTY support <M> BNEP protocol support [*] Multicast filter support [*] Protocol filter support <M> HIDP protocol support
Bluetooth device drivers ---> <M> HCI USB driver [*] SCO (voice) support <M> HCI UART driver [*] UART (H4) protocol support [*] BCSP protocol support [*] Transmit CRC with every BCSP packet <M> HCI BCM203x USB driver <M> HCI BPA10x USB driver <M> HCI BlueFRITZ! USB driver (The four drivers below are for PCMCIA Bluetooth devices and will only show up if you have also selected PCMCIA support in your kernel.) <M> HCI DTL1 (PC Card) driver <M> HCI BT3C (PC Card) driver <M> HCI BlueCard (PC Card) driver <M> HCI UART (PC Card) device driver (The driver below is intended for HCI Emulation software.) <M> HCI VHCI (Virtual HCI device) driver
(Move back three levels to Device Drives and then check if USB is enabled. This is required if you use a Bluetooth dongle, which are mostly USB based.) USB support --->
<*> Support for Host-side USB --- USB Host Controller Drivers <M> EHCI HCD (USB 2.0) support [ ] Full speed ISO transactions (EXPERIMENTAL) [ ] Root Hub Transaction Translators (EXPERIMENTAL) <*> OHCI HCD support <*> UHCI HCD (most Intel and VIA) support < > SL811HS HCD support
Правда, PCMCIA я у себя отключил.

Итак, качаем последнюю версию модуля с sourceforge.net.

$ svn co https://omnibook.svn.sourceforge.net/svnroot/omnibook/trunk
...
$ mv trunk omnibook

Собираем модуль:

# cd omnibook
# make -C /lib/modules/$(uname -r)/build modules SUBDIRS=$(pwd)
или просто
# make; make install
:-) Теперь загружаем модуль с помощью modprobe omnibook.

В моём случае, правда, модуль не загрузился, т.к. моя модель не определилась как поддерживаемая. Модель определяется по данным из пула DMI (Desktop Management Interface - часть SMBIOS, хранящая данные о конфигурации платформы). Список официально поддерживаемых моделей можно найти здесь. Но если даже Ваша модель не поддерживается официально, можно выяснить, какая из официально поддерживаемых моделей наиболее близка Вашей и загрузить модуль принудительно с параметром ectype=x (ectype - embedded controller type - тип встроенного контроллера). В моём случае ectype равен 12 (Toshiba Satellite M70). Если всё прошло удачно, в директории /proc появится поддиректория omnibook. Каждый файл в этой директории соответствует одной функции контроллера, кроме файлов version и dmi (версия драйвера и информация, полученная из пула DMI соответственно):

f0x@devel0:~$ ls -la /proc/omnibook/
total 0
dr-xr-xr-x   2 root root 0 2007-10-23 23:50 .
dr-xr-xr-x 161 root root 0 2007-09-02 11:41 ..
-rw-r--r--   1 root root 0 2007-10-23 23:50 blank
-rw-r--r--   1 root root 0 2007-10-23 23:50 bluetooth
-rw-r--r--   1 root root 0 2007-10-23 23:50 display
-r--r--r--   1 root root 0 2007-10-23 23:50 dmi
-rw-r--r--   1 root root 0 2007-10-23 23:50 lcd
-r--r--r--   1 root root 0 2007-10-23 23:50 temperature
-rw-r--r--   1 root root 0 2007-10-23 23:50 throttling
-r--r--r--   1 root root 0 2007-10-23 23:50 version
-r--r--r--   1 root root 0 2007-10-23 23:50 wifi
За описанием функций контроллера Вы можете обратиться на http://omnibook.sourceforge.net/doku.php. Нас же здесь больше всего интересует файл bluetooth. Файл содержит фразу "Bluetooth adapter is present and disabled." Т.е., по умолчанию bluetooth-адаптер отключен. Включить его можно простой записью единицы в файл:
echo 1 > /proc/omnibook/bluetooth
Для выключения, аналогично, записываем в файл "0".

Далее предполагается, что все необходимые пакеты для работы с bluetooth (bluez-* или метапакет bluetooth, к-й потянет за собой по зависимостям всё, что нужно и что ненужно тоже) установлены. Таким образом, следующий шаг - настройка всего, что нужно для работы с bluetooth. Прежде всего следует сконфигурировать hcid - демон, предоставляющий интерфейс к HCI - интерфейсу хост-контроллера. Ниже приводится моя конфигурация с комментариями:

# /etc/bluetooth/hcid.conf
#
# HCI daemon configuration file.
#

# HCId options
options {
 # Automatically initialize new devices
 autoinit yes;

 # Security Manager mode
 #   none - Security manager disabled
 #   auto - Use local PIN for incoming connections
 #   user - Always ask user for a PIN
 #
 security auto;

 # Pairing mode
 #   none  - Pairing disabled
 #   multi - Allow pairing with already paired devices
 #   once  - Pair once and deny successive attempts
 pairing multi;

 # Default PIN code for incoming connections
 passkey "1234";
}

# Default settings for HCI devices
device {
 # Local device name
 #   %d - device id
 #   %h - host name
 name "%h";

 # Local device class
 class 0x3e0100;

 # Default packet type
 pkt_type DM1,DM3,DM5,DH1,DH3,DH5,HV1,HV2,HV3;

 # Inquiry and Page scan
 iscan enable;
 pscan enable;
 discovto 0;

 # Default link mode
 #   none   - no specific policy 
 #   accept - always accept incoming connections
 #   master - become master on incoming connections,
 #            deny role switch on outgoing connections
 lm accept;

 # Default link policy
 #   none    - no specific policy
 #   rswitch - allow role switch
 #   hold    - allow hold mode
 #   sniff   - allow sniff mode
 #   park    - allow park mode
 lp rswitch,hold,sniff,park;
}
Немного по опциям. Секция options - настройки демона:
  1. autoinit - автоматически инициализировать подсоединённые устройства (bluetooth-адаптеры)
  2. security - безопасность: none - не использовать PIN-код;
    auto - использовать для доступа к устройству, подсоединяемому через bluetooth локальный PIN по умолчанию, указанный в опции passkey;
    user - запрашивать PIN-код для установления соединения у пользователя.
  3. pairing - опции соединения: multi - разрешать соединения с устройствами, которые уже соединены;
    none - запретить соединения;
    once - допускать только одно соединение с помощью данного адаптера и игнорировать все последующие попытки установить новое соединение.
  4. passkey - PIN bluetooth-хоста по умолчанию. Он будет использоваться при добавлении Вашего компьютера другими bluetooth-устройствами в их списки устройств. Используйте любое число, которое Вам нравится.
Секция device - свойства контроллера:
  1. name - имя Вашего bluetooth-устройства (хоста). В моём случае bluetooth-хосту присваивается то же имя, что и имя Unix-хоста.
  2. class - класс устройства. Шестнадцатеричное число, определяющее класс bluetooth-устройства (хоста). С помощью битовой маски устанавливается тип хоста. За описанием битов обращайтесь к man hcid.conf или спецификации bluetooth. В моём случае средний байт 01 в 0x3e0100 обозначает, что мой хост - это компьютер, а не телефон, принтер или HID-устройство :-)
  3. pkt_type - типы пакетов. За разъяснениями также советую обращаться к спецификации Bluetooth или просто оставьте, как есть.
  4. iscan - разрешить другим bluetooth-устройствам обнаруживать Ваш хост при сканировании.
  5. pscan - разрешить другим устройствам устанавливать соединения с Вашим хостом, используя канал "page channel".
  6. discovto - время, на протяжении которого Ваш хост будет позволять обнаружить себя другому устройству. Устанавливается в секундах. 0 - устройство может быть обнаружено в любое время.
  7. lm - режим установки соединения: none - не определено;
    accept - всегда принимать входящие запросы на соединение;
    master - принимать входящие соединения в роли ведущего и запретить изменение этой роли при исходящих соединениях.
  8. lp - политика при установке соединения (касается пикосетей): none - не определено;
    rswitch - разрешить изменять роль (ведущий-ведомый);
    hold - эта политика необходима для синхронного обмена данными (нужно для беспроводной гарнитуры);
    sniff - позволить Вашему хосту пребывать в пикосети в определённые временные слоты. Когда устройство "отсутствует", оно может делать что-то другое, например, сканировать эфир, искать другие устройства;
    park - разрешить парковку хоста, т.е. позволять хосту переходить в энергосберегающий режим (сон). Устройство, входящее в пикосеть может "простаивать".
В принципе, то что здесь написано - это мой перевод информации из man hcid.conf, здесь я не буду разъяснять все принципы работы bluetooth-устройств и сетей, в которые такие устройства могут объединяться. Всем любопытным советую обратиться к первоисточнику - спецификации по Bluetooth.

Далее, нам необходимо отредактировать файл настройки подсистемы bluetooth - rfcomm для нашего хост-контроллера. rfcomm - Radio Frequency Communication - радиочастотная связь, это подсистема, которая эмулирует последовательную линию RS232 поверх протокола L2CAP. В принципе, настройка подсистемы rfcomm опциональна. Но для устройства, которое Вы хотите использовать, как модем, для сотового телефона, настроить её будет желательно, чтобы не делать этого каждый раз вручную. Ниже приведён пример конфигурационного файла:

# /etc/bluetooth/rfcomm.conf
#
# RFCOMM configuration file.
#

rfcomm0 {
# # Automatically bind the device at startup
 bind yes;
#
# # Bluetooth address of the device
 device 00:0A:0B:0C:0D:0E;
#
# # RFCOMM channel for the connection
# channel 1;
#
# # Description of the connection
 comment "SonyEricsson T630";
}
Пояснения:
  1. rfcomm0 - первый последовательный интерфейс. Вообще-то, Вы можете сконфигурировать их столько, сколько захотите. Конфигурация каждого последовательного интерфейса начинается с его имени (rfcomm0, rfcomm1, rfcomm2, ...). Сами параметры интерфейса указываются в блоке между двумя фигурными скобками { и };
  2. bind - автоматически привязывать сокет для нашего устройства при его обнаружении (yes или no, в случае, если Вы не хотите, чтобы сокет связывался автоматически;
  3. device - MAC-адрес bluetooth-устройства (сотового телефона, например);
  4. channel - канал bluetooth, который будет использоваться для связи с устройством;
  5. comment - опциональный комментарий к интерфейсу.
При указании МАС-адреса Вашего устройства, его сначала надо узнать. Для этого поднимем устройство hci и просканируем эфир:
# hciconfig hci0 up
# hciconfig piscan
# hcitool scan
Scanning ...
        00:0E:0F:10:11:12       Red Quick Fox
Здесь мы просто запускаем устройство hci0 с помощью утилиты hciconfig и сканируем эфир с помощью hcitool с параметром scan. 00:0E:0F:10:11:12 - это и есть нужный нам MAC-адрес. Утилита hciconfig предназначена для конфигурирования хост-контроллера hci. Если контроллеров несколько, то они имеют имена hci0, hci1 и т.д.. hcitool позволяет совершать некие вменяемые действия с помощью нашего хост-контроллера. Иными словами, она позволяет им управлять. Для списка агрументов обеих утилит см. man hciconfig и man hcitool. Используя утилиту l2ping и МАС-адрес в качестве её аргумента, можно послать пинга телефону :-)

Следующая стадия конфигурирования bluetooth - редактирование ещё одного файла. В debian-based дистрибутивах linux это /etc/default/bluetooth. По существу, этот файл нужен только стартовому скрипту bluetooth из /etc/init.d. Сложного там ничего нет. Всё, что от Вас требуется, указать, должны ли сервисы bluetooth стартовать при загрузке системы и если да, то какие. Приведём пример файла /etc/default/bluetooth:

# Defaults for bluez-utils

# This file supersedes /etc/default/bluez-pan.  If
# that exists on your system, you should use this
# file instead and remove the old one.  Until you
# do so, the contents of this file will be ignored.

# start bluetooth on boot?
# compatibility note: If this variable is not found bluetooth will
# start
BLUETOOTH_ENABLED=1

############ DUND
#
# Run dund -- this allows ppp logins. 1 for enabled, 0 for disabled.
DUND_ENABLED=1

# Arguments to dund: defaults to acting as a server
DUND_OPTIONS="--listen --persist"

# Run dund --help to see the full array of options.
# Here are some examples:
#
# Connect to any nearby host offering access
# DUND_OPTIONS="--search"
#
# Connect to host 00:11:22:33:44:55
# DUND_OPTIONS="--connect 00:11:22:33:44:55"
#
# Listen on channel 3
# DUND_OPTIONS="--listen --channel 3"

# Special consideration is needed for certain devices. Microsoft
# users see the --msdun option.  Ericsson P800 users will need to
# listen on channel 3 and also run 'sdptool add --channel=3 SP'

############ SDPTOOL
# this variable controls the options passed to sdptool on boot, useful if you
# need to setup sdpd on boot.
# options are ;-separated, i.e. for every ; an sdptool instance will be
# launched
#
# examples:
# SDPTOOL_OPTIONS="add --channel=3 SP" # ericsson P800 serial profile
# SDPTOOL_OPTIONS="add --channel=8 OPUSH ; add --channel=9 FTRN" # motorola
#                                             # object push and file transfer
SDPTOOL_OPTIONS=""

Здесь переменная BLUETOOTH_ENABLED=1 определяет, что при загрузке системы должны стартовать и сервисы bluetooth. Если переменная установлена в 0, поддержка bluetooth не включается. Если переменная не найдена, то по умолчанию сервисы загружаются. DUND_ENABLED=1 определяет, что должен быть запущен демон Dial-up-доступа. Ну, вот практически и всё. В переменной SDPTOOL_OPTIONS определяется, какие сервисы должен предоставлять наш компьютер другим bluetooth-устройствам. Список сервисов можно пополнить и вне этого файла с помощью sdptool (см. man sdptool). В общем и в целом Unix-box теперь готов к работе с bluetooth-устройствами. Можете по bluetooth зайти на сотовый телефон, настроить пикосеть, работающую через BNEP или ещё что-нибудь. Про HID я не писал, ибо не имею таковых девайсов. Про настройку пикосети на bluetooth Вы сможете узнать сами.

Что касаемо телефонов, было замечено одно занятное обстоятельство: PIN, указанный в конфигурационном файле hcid.conf внаглую игнорируется с новыми телефонами (т.е., с теми, с которыми мы раньше не работали). В качестве противоядия можно использовать следующее: создать маленький скрипт, который просто выводит с помощью echo PIN-код - echo "PIN:XXXX", где ХХХХ - Ваш PIN. Допустим, этот скрипт будет называться helper. Далее, можно запустить passkey-agent --default ./helper. По идее, агент должен запускаться автоматически для каждой пользовательской сессии, но как показывает опыт, он этого почему-то не делает. Можно самостоятельно запихать passkey-agent в какой-нибудь скрипт. Ну, вот, собственно, и всё. Чтобы использовать сотовый телефон для Dial-up доступа в интернет в качестве GPRS-модема, укажите в качестве устройства модема /dev/rfcomm0 в конфиге Вашей звонилки, настройте доступ согласно информации Вашего оператора. А дальше - всё как всегда.

Tuesday, August 28, 2007

Селим OpenSolaris рядом с Linux: загрузка одним bootloader'ом или история одной глупости

Уже довольно давно, может с месяца с 4, у меня валялся набор из 2 дисков OpenSolaris 10 Starter Kit. Так как солярой я интересовался и раньше и эпизодически ею пользовался (Sun Solaris 9 x86), то решил наконец попробовать и этот продукт. На диске есть 3 дистрибутива в редакции community: Nexenta, Schilix и Belenix. Опробовав последовательно все 3 образа (они загружаются с DVD-диска как Live без необходимоси установки), я остановил свой выбор на последнем, как на наиболее соответствующем моим требованиям дистре (gcc в комплекте и всё необходимое для разработки - библиотеки, плюс, конечно, zfs, Solaris zones, DTrace и прочее).

Кратко о самом дистре Belenix: довольно симпатичный дистр, в качестве рабочей графической среды предоставляется на выбор KDE 3.5 или Xfce, коим я и пользуюсь. По умолчанию графика не запускается. В смысле, она запускается на полуавтомате при загрузке LiveCD, но не в уже установленном солярисе, где по умолчанию пускать Xfce нужно явно, руками (хотя, никто не мешает Вам настроить этот момент под Ваши нужды). Порадовало, что без проблем определяются интегрированное аудио и eth-адаптер на nForce430, с дисковым контроллером проблем также никаких (у меня 2 SATA-II винчестера). В отличие от Nexenta здесь ничего не валится в коры буквально на ровном месте. Установка проходит без проблем и в принципе, процедура довольна тривиальна, поэтому на ней я останавливаься не буду. Единственное, что хотелось бы отметить, что инсталлятор как был во всех версиях Solaris, что мне попадались - текстовый, таковым остался и здесь. Однако, я не считаю это недостатком. Но вместе с тем, опций здесь лишь самый минимум - думаю, можно было бы сделать и побольше, хотя бы на предмет того, какой софт нужно ставить, а какой нет.

Итак, я выкроил свободное место на втором винчестере и поставил OS туда. Непредвиденные трудности начались, когда я попытался при перезагрузке получить мой свежеустановленный Belenix. Суть проблемы в следующем: до этого у меня уже был установлен Debian GNU/Linux 'Etch' и всё, что обреталось у меня на компьютере, грузилось его grub'ом. Честно говоря, мне не было особого резона использовать в качестве общесистемного загрузчика Solaris'овский grub, потому как всё же большую часть времени я провожу в Linux и такая схема была бы не очень удобна. Но Linux'овый grub понятия не имеет о zfs или ufs2 (хотя, последнее по идее должен уметь). В конце концов я уже готов был пойти на крайность и сделать общесистемным загрузчиком Solaris'овский grub. Перезагрузившись с Belenix LiveCD, вызвал installgrub со следующими параметрами:

# grubinstall -m /mnt/solaris0/boot/grub/stage1 /mnt/solaris0/boot/grub/stage2 /dev/rdsk/c2d0s0
В общем, здесь всё довольно self-explanatory: указываем файл для первой стадии загрузки grub (сектор, внедряемый в MBR), образ для второй стадии и слайс, на котором стоит Solaris (честно говоря, я до сих пор путаюсь в схемах BSD и Solaris, но если я не ошибаюсь, то это должен бы быть лейбл, а не слайс, ведь слайс - это раздел в терминологии DOS, однако по имени видно, что у нас контроллер 2, первый диск на 2-м контроллере и 1-й слайс на этом диске - так или иначе, но эта схема совершенно отлична от той, что используется в Linux и не совсем похожа на Free- или NetBSD'шную. ИМХО в FreeBSD имена устройств выглядят логичнее и нагляднее, чем в Solaris). Ключ -m здесь просто добавляет интерактивности. Если Вы не накачались пивом и прекрасно понимаете, что Вы собираетесь сделать, то им можно и пренебречь :) /mnt/solaris0 - подмонтированная ФС с уже установленной на диск солярой - мы будем брать необходимые файлы с этой ФС. Так вот, каково было моё разочарование, когда я обнаружил, что первый загрузочный сектор оказался не в MBR первого винчестера, как я ожидал, а в mboot'e второго. Указать installgrub'у явным образом, куда необходимо воткнуть загрузочный сектор не представлялось возможным.

В общем, ситуация вышла достаточно комичной. Linux'овый grub в упор не воспринимал Solaris'овскую ufs2 и о том, чтобы он мог грузить соляровое ядро непосредственно не могло быть и речи, а с другой стороны соляровый загрузчки упорно становился в mboot второго диска и, следовательно, пролетал при загрузке.

Решение было ясно как день и простое, как карандаш. Честно говоря, если бы не лень, можно было бы не морочить себе голову. Дело в том, что я пытался редактировать меню загрузки на лету, добавляя
rootnoverify (hd1)
chainloader +1
Но по не совсем понятной мне причине Linux'овый grub ругался кодом ошибки 27 и как резанный кричал, что не знает такой команды. Редактирование menu.lst для Linux'ового grub'а расставило всё по своим местам (хотя и странно, почему такого результата не удавалось добиться редактированием параметров загрузки на лету - ограничение внедряемого кода grub-шелла для экономии, чтобы по возможности сократить число секторов, которые необходимо внедрить?). Теперь и солярис и линукс грузятся одним загрузчиком - Linux'овым, с той поправкой, что солярис грузится не непосредственно, а через встроенный в цепь собственный загрузчик, который живёт в MBR второго диска.

Результат: в MBR 1-го диска установлен Linux grub, а в файл menu.lst для загрузки зацеплённого солярового grub'a, что установлен нами ранее в MBR 2-го винчестера installgrub'ом, были добавлены строки:

title  Belenix 32-bit
rootnoverify (hd1)  # Используем в качестве корня MBR 2-го диска (hd1), и не пытаемся ничего монтировать - не получится, т.к. там просто нет ФС и нечего монтировать :)
chainloader +1  # Встраиваем в цепь вторичный бутлоадер, на к-й ссылается MBR 2-го диска
Есть ещё одно обстоятельство. Файл menu.lst для солярового grub'a также нуждается в небольшой правке, которую лучше сделать именно в файле, чем каждый раз корректировать на лету команды загрузки. А именно, по непонятному недоразумению, которое уже описывалось ранее, соляровый инсталлятор grub'a, а вместе с ними вдогонку и сам grub почему-то считают, что они установлены на 1-м диске, а не на 2-м, как у меня обстоит дело в реальности. В результате, в /boot/grub/menu.lst для соляриса имеем такие сроки:
title  Belenix (32Bit, ACPI)
  root (hd0,3,a) # Ошибка: Корневая ФС, на которой находятся файлы образа ядра и загрузочный архив находится на втором диске, а не на первом (первое число должно быть 1, а не 0)!
  # Далее - всё в полном порядке :)
  kernel /platform/i86pc/multiboot kernel/unix
  module /platform/i86pc/boot_archive
Строка root нуждается в следующей правке: 0 необходимо заменить на 1 для второго диска, а в остальном всё правильно в моём случае - слайс 3, лейбл - а. С учётом сказанного, она должна выглядеть так: root (hd1,3,a). Хотя, если у Вас OS установлен на 1-м винчестере, то эта правка Вам не нужна.

Вместо Afterword: как и в Linux'e в OpenSolaris есть определённые ограничения для корневой ФС или, по крайней мере, /boot. Так, в Linux нет возможности внедрить секторы со второй стадией загрузки grub в раздел, который отформатирован в xfs (просто в xfs исторически не предусмотрена такая возможность, т.к. эта ФС была разработана для SGI IRIX и изначально к Linux никакого отношения не имела. Итог состоит в том, что в голове xfs-раздела нет необходимых 64 Кб для внедрения grub-секторов. Поэтому или всю корневую ФС или, по крайней мере, /boot нужно держать на ФС, более традиционной для Linux - ReiserFS, Ext2/3 и т.п.) Аналогично, grub на самом деле не имеет понятия о пулах zfs (на самом деле, если не ошибаюсь), но тот, что имеется в OS прекрасно работает по крайней мере с ufs2. Поэтому, выход здесь, как и в случае с Linux - держать корневую ФС на ufs2, а уже остальное, типа /home - в пуле на zfs.

PS: небольшая попроавка к примеру с xfs и grub. Честно говоря, я не уверен, что информация актуальна, но насколько можно судить сейчас по некоторым данным grub прекрасно работает и с корневой ФС на xfs. Но так как я не уверен, то текст оставил без исправлений. Если я всё-таки ошибся, то извините за неточность - мы все учимся, и не редко на ошибках и с их же помощью :)

Saturday, June 9, 2007

perl-proxy: Немного юмора

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

#!/usr/bin/perl -Tw

use strict;
$ENV{PATH} = join ":", qw(/usr/ucb /bin /usr/bin);
$|++;

my $VERSION_ID = q$Id: proxy,v 1.21 1998/xx/xx xx:xx:xx merlyn Exp $;
my $VERSION = (qw$Revision: 1.21 $ )[-1];

## Copyright (c) 1996, 1998 by Randal L. Schwartz
## This program is free software; you can redistribute it
## and/or modify it under the same terms as Perl itself.

### debug management
sub prefix {
   my $now = localtime;
   join "", map { "[$now] [${$}] $_\n" } split /\n/, join "", @_;
}

$SIG{__WARN__} = sub { warn prefix @_ };
$SIG{__DIE__} = sub { die prefix @_ };

&setup_signals();

### logging flags
my $LOG_PROC = 1;               # begin/end of processes
my $LOG_TRAN = 1;               # begin/end of each transaction
my $LOG_REQ_HEAD = 0;           # detailed header of each request
my $LOG_REQ_BODY = 0;           # header and body of each request
my $LOG_RES_HEAD = 0;           # detailed header of each response
my $LOG_RES_BODY = 0;           # header and body of each response

### configuration
my $HOST = 'WWW.XXX.YYY.ZZZ';
my $PORT = 3128;                   # pick next available user-port
my $SLAVE_COUNT = 8;            # how many slaves to fork
my $MAX_PER_SLAVE = 20;         # how many transactions per slave

### main
warn("running version ", $VERSION);

&main();
exit 0;

### subs
sub main {                      # return void
   use HTTP::Daemon;
   my %kids;

   my $master = HTTP::Daemon->new(LocalPort => $PORT, LocalAddr => $HOST) or die "Cannot create master: $!";
   warn("master is ", $master->url);
   ## fork the right number of children
   for (1..$SLAVE_COUNT) {
      $kids{&fork_a_slave($master)} = "slave";
   }
   {                             # forever:
      my $pid = wait;
      my $was = delete ($kids{$pid}) || "?unknown?";
      warn("child $pid ($was) terminated status $?") if $LOG_PROC;
      if ($was eq "slave") {      # oops, lost a slave
         sleep 1;                  # don't replace it right away
         #(avoid thrash)
         $kids{&fork_a_slave($master)} = "slave";
      }
   } continue { redo };          # semicolon for cperl-mode
}

sub setup_signals {             # return void
   setpgrp;                      # I *am* the leader
   $SIG{HUP} = $SIG{INT} = $SIG{TERM} = sub {
      my $sig = shift;
      $SIG{$sig} = 'IGNORE';
      kill $sig, 0;               # death to all-comers
      die "killed by $sig";
   };
}

sub fork_a_slave {              # return int (pid)
   my $master = shift;           # HTTP::Daemon

   my $pid;
   defined ($pid = fork) or die "Cannot fork: $!";
   &child_does($master) unless $pid;
   $pid;
}

sub child_does {                # return void
   my $master = shift;           # HTTP::Daemon
   my $did = 0;                  # processed count

   warn("child started") if $LOG_PROC;
   {
      flock($master, 2);          # LOCK_EX
      warn("child has lock") if $LOG_TRAN;
      my $slave = $master->accept or die "accept: $!";
      warn("child releasing lock") if $LOG_TRAN;
      flock($master, 8);          # LOCK_UN
      my @start_times = (times, time);
      $slave->autoflush(1);
      warn("connect from ", $slave->peerhost) if $LOG_TRAN;
      &handle_one_connection($slave); # closes $slave at right time
      if ($LOG_TRAN) {
         my @finish_times = (times, time);
         for (@finish_times) {
            $_ -= shift @start_times; # crude, but effective
         }
         warn(sprintf "times: %.2f %.2f %.2f %.2f %d\n", @finish_times);
      }
   } continue { redo if ++$did < $MAX_PER_SLAVE };
   warn("child terminating") if $LOG_PROC;
   exit 0;
}

sub handle_one_connection {     # return void
   use HTTP::Request;
   my $handle = shift;           # HTTP::Daemon::ClientConn

   my $request = $handle->get_request;
   print $request;
   defined($request) or die "bad request"; # XXX

   my $response = &fetch_request($request);
   warn("response: <<<\n", $response->headers_as_string, "\n>>>")
   if $LOG_RES_HEAD and not $LOG_RES_BODY;
   warn("response: <<<\n", $response->as_string, "\n>>>")
   if $LOG_RES_BODY;
   $handle->send_response($response);
   close $handle;
}

sub fetch_request {             # return HTTP::Response
   use HTTP::Response;
   use URI::URL;
   my $request = shift;          # HTTP::Request

   ## XXX
   print "Request was: " . $request->as_string;
   ## XXXX needs policy here
   my $url = URI::URL->new($request->url);

   if ($url->scheme !~ /^(https?|gopher|ftp)$/) {
      my $res = HTTP::Response->new(403, "Forbidden");
      $res->content("bad scheme: @{[$url->scheme]}\n");
      $res;
   } elsif (not $url->rel->netloc) {
      my $res = HTTP::Response->new(403, "Forbidden");
      $res->content("relative URL not permitted\n");
      $res;
   } else {
      ## validated request, get it!
      warn("processing url is $url") if $LOG_TRAN;
      &fetch_validated_request($request);
   }
}

BEGIN {                         # local static block
   my $agent;                    # LWP::UserAgent

sub fetch_validated_request {         # return HTTP::Response
   my $request = shift;         # HTTP::Request

   $agent ||= do {
      use LWP::UserAgent;
      my $agent = LWP::UserAgent->new;
      $agent->agent("proxy/$VERSION " . $agent->agent);
      $agent->env_proxy;
      $agent;
   };

   warn("fetch: <<<\n", $request->headers_as_string, "\n>>>")
   if $LOG_REQ_HEAD and not $LOG_REQ_BODY;
   warn("fetch: <<<\n", $request->as_string, "\n>>>")
   if $LOG_REQ_BODY;

   my $response = $agent->simple_request($request);

   if ($response->is_success and $response->content_type =~ /text\/(plain|html)/ and not ($response->content_encoding || "") =~ /\S/ and ($request->header("accept-encoding") || "") =~ /gzip/) {
      require Compress::Zlib;
      my $content = $response->content;
      my $new_content = Compress::Zlib::memGzip($content);
      if (defined $new_content) {
         $response->content($new_content);
         $response->content_length(length $new_content);
         $response->content_encoding("gzip");
         warn("gzipping content from " . (length $content) . " to " . (length $new_content)) if $LOG_TRAN;
      }
   }

   $response;
}
}

Немного разоблачений:

  1. Код не мой, как видно по копирайту - я лишь подправил кое-что в области URI::URL, чтоб оно работало и добавил print $request->as_string для отладки.
  2. Переменную $HOST устанавливаем в адрес хоста, на котором крутится прокси, а $PORT присваиваем номер порта, который слушает наш прокси.
  3. Настраиваем клиент на использование прокси - указываем адрес хоста и порт $HOST:$PORT
  4. Скрипт при запуске плодит pre-forked потомков, которые слушают порт $PORT
  5. При поступлении запроса на соединение на указанный порт с помощью манипуляций объектами HTTP::Request, HTTP::Response и LWP::UserAgent перебрасываем запрос на сервер-рецепиент.

Вот так вкратце. А впрочем, из кода довольно прозрачно всё видно. Всё гениальное просто :) Можно запустить эту игрушку и шарить т.о. одно dial-up соединение для доступа к web (чем я сейчас и занимаюсь), не трогая NAT.

Saturday, June 2, 2007

Toshiba Satellite A100-811: Ubuntu & power management

Странное дело, но после апгрейда до KUbuntu 7.04 Feisty Fawn на ноутбуке сломался hibernate suspend-to-disk. Правка /etc/hibernate/common.conf ничего не давала. Процесс сброса образа RAM на диск, кажется, шёл вполне нормально, но после возобновления (resume) X оказывался запорот. Система не висит - в неё даже можно залогиниться по ssh, но работа непосредственно на ноутбуке невозможна. Итак, немного подумав, я открыл для себя uswsusp и входящий сюда s2disk. Однако, и тут не всё было гладко. Одно время s2disk работал для меня вполне прекрасно, даже с некоторыми особенностями. Так, например, после слива образа на диск и закрытия крышки лаптопа при её отркывании система включалась сама, без нажатия кнопки power и linux возобновлялся. Но, позже появилась проблема. При открытии крышки ноутбук включался, появлялся сплэш и... всё. Сплэш замерзал на одном месте и система не возобновлялась. Немного подумав, я полез шерстить по конфигам. Итак, найдя файл /etc/uswsusp.conf, я сделал такую правку:

# /etc/uswsusp.conf(8) -- Configuration file for s2disk/s2both 
resume device = /dev/sda5
compress = y
early writeout = y
image size = 512000000
compute checksum = y
RSA key file = /etc/uswsusp.key
shutdown method = platform
splash = y
Поясню: до этого параметр splash в конфиге не был определён вообще, а image size был равен 236Мб с копейками. Как видно, я явно установил splash = y, для фидбэка в процессе слива образа RAM на диск и в процессе его считывания с диска, а image size в пол-гигабайта (объём RAM на моём ноутбуке - 512Мб). Как ни странно, но после этого всё заработало. Скажу честно, я исходил из ни на чём не обоснованного предположения, что всё дело именно в том, что на диск сливается не весь образ памяти. Это было довольно интуитивное решение. Экспериментировать со значением 0 я не стал, но в man uswsusp.conf написано, что при нулевом значении s2disk будет пытаться сделать образ как можно меньше, сохраняя только самый необходимый минимум.

Теперь s2disk работал. И работал прекрасно. Единственное, чего мне ещё не хватало, это чтобы он срабатывал автоматически при критическом уровне батареи (такая настройка у меня была и ранее - в целях предотвращения потерь данных. Вполне вероятна такая ситуация, когда Вы отойдёте, задержитесь где-то, а тем временем аккумулятор благополучно иссякнет и...). К сожалению, отрабатывало оно так же, как и усыпление через диалог KDE "Завершить сеанс" -> "Спящий режим", или точнее сказать, не работало вовсе. Linux просто гасил экран, блокировал сеанс KDE, а засыпать даже не думал. Я пользуюсь KLaptop. Так сложилось традиционно, да и просто, он субъективно нравится мне больше всяких там KPowersave. Там вполне можно было прописать, какую команду следует выполнить при критически малом уровне аккумулятора, но мне хотелось универсального решения, ведь для кнопки в KDE'шном завершении сеанса такой параметр не прописан. Механизм работы здесь однако был один. Это я знал по опыту - по одинаковому поведению при попытке усыпить ноутбук через KLaptop и "Завершение сеанса". Немного пошарив по google я нашёл решение. Оказалось, что hibernation здесь срабатывает через hal. Для того, чтобы исправить ситуацию, необходимо было заменить модуль hal, который отвечал за усыпление по требованию клиента. Этот модуль представляет собой нечто иное, как скрипт, лежащий в /usr/lib/halscripts/linux - hal-system-power-hibernate-linux. Моей целью было заставить этот скрипт отрабатывать через s2disk. Первоначально скрипт выглядёл следующим образом:

#!/bin/sh

POWERSAVED_SUSPEND2DISK="dbus-send --system --dest=com.novell.powersave \
                         --print-reply /com/novell/powersave \
                         com.novell.powersave.action.SuspendToDisk"

unsupported() {
 echo org.freedesktop.Hal.Device.SystemPowerManagement.NotSupported >&2
 echo No hibernate script found >&2
 exit 1
}

#SuSE and ALTLinux only support powersave
if [ -f /etc/altlinux-release ] || [ -f "/etc/SuSE-release" ] ; then
 if [ -x /usr/bin/powersave ] ; then
         $POWERSAVED_SUSPEND2DISK
  RET=$?
 else
  unsupported
 fi

#Mandriva support suspend-scripts 
elif [ -f /etc/mandriva-release ] ; then 
    if [ -x /usr/sbin/pmsuspend ] ; then 
 /usr/sbin/pmsuspend disk 
 RET=$? 
    else 
 unsupported 
    fi 

#RedHat/Fedora only support pm-utils
elif [ -f /etc/redhat-release ] || [ -f /etc/fedora-release ] ; then
 if [ -x /usr/sbin/pm-hibernate ] ; then
  /usr/sbin/pm-hibernate
  RET=$?
 else
  unsupported
 fi

#Other distros just need to have *any* tools installed
else
 if [ -x "/usr/bin/powersave" ] ; then
         $POWERSAVED_SUSPEND2DISK
  RET=$?
 elif [ -x "/usr/sbin/pmi" ] ; then
  /usr/sbin/pmi action hibernate force
  RET=$?
 elif [ -x "/usr/sbin/pm-hibernate" ] ; then
  /usr/sbin/pm-hibernate
  RET=$?
 elif [ -x "/usr/sbin/hibernate" ] ; then
  # Suspend2 tools installed
  /usr/sbin/hibernate --force
  RET=$?
 elif [ -x "/sbin/s2disk" ] ; then
  # uswsusp tools installed
  /sbin/s2disk
  RET=$?
 elif [ -x "/etc/acpi/hibernate.sh" ] ; then
  # acpi-support installed
  /etc/acpi/hibernate.sh force
  RET=$?
 elif [ -w "/sys/power/state" ] ; then
  # Use the raw kernel sysfs interface
  echo "disk" > /sys/power/state
  RET=$?
 else
  unsupported
  fi
 fi

#Refresh devices as a resume can do funny things
for type in button battery ac_adapter
do
 devices=`hal-find-by-capability --capability $type`
 for device in $devices
 do
  dbus-send --system --print-reply --dest=org.freedesktop.Hal \
     $device org.freedesktop.Hal.Device.Rescan
 done
done

exit $RET

Из приведённого листинга видно, что сперва скрипт пытается определить дистрибутив, а далее действует исходя из реалий данного дистрибутива. В моём случае hal пытался отработать через pmi - абстактный интерфейс управления энергосбережением. Мне же нужно было, чтобы первым и последним срабатывал s2disk. После исправления скрипт обрёл такой вид:

#!/bin/sh

[ SKIPPED ]

#Other distros just need to have *any* tools installed
else
 if [ -x "/sbin/s2disk" ] ; then
  # uswsusp tools installed
  /sbin/s2disk
  RET=$?
 elif [ -x "/usr/sbin/pmi" ] ; then
  /usr/sbin/pmi action hibernate force
  RET=$?
 elif [ -x "/usr/bin/powersave" ] ; then
         $POWERSAVED_SUSPEND2DISK
  RET=$?
 elif [ -x "/usr/sbin/pm-hibernate" ] ; then
  /usr/sbin/pm-hibernate
  RET=$?
 elif [ -x "/usr/sbin/hibernate" ] ; then
  # Suspend2 tools installed
  /usr/sbin/hibernate --force
  RET=$?
 elif [ -x "/etc/acpi/hibernate.sh" ] ; then
  # acpi-support installed
  /etc/acpi/hibernate.sh force
  RET=$?
 elif [ -w "/sys/power/state" ] ; then
  # Use the raw kernel sysfs interface
  echo "disk" > /sys/power/state
  RET=$?
 else
  unsupported
 fi
fi

[ SKIPPED ]
После этого и KLaptop и "Завершение сеанса" работают безупречно.

Кстати, режим suspend-to-ram у меня работал хорошо из коробки (после апгрейда до Feisty Fawn). s2ram напротив работать не хочет, ссылаясь на то, что модель ноута ему не знакома. Поэтому мои настройки энергосбережения закончились доведением до ума режима suspend-to-disk a.k.a. hibernation.

"Вот так мы победили сырость!" (с) Однако, есть ещё над чем поработать. Так, например, мне до сих пор не удалось заставить работать русский в консоли. Самое странное, что на десктопе с установленным Debian 4.0 Etch всё прекрасно работает (console-cyrillic не установлен). При такой же конфигурации на лаптопе оно не работает. Странность в том, что из коробки путём лишь небольших манипуляций после замены дистрибутивного ядра на собственное русский текст в UTF8 отображается в консоли без проблем, но нет возможности набирать русский текст. Вместо него не набирается вообще ничего. Одним словом, проблема всё ещё требует решения. В Edgy Eft в этом плане всё было отлично. Примечательно, что такие проблемы после перехода на Feisty Fawn наблюдаются не только у меня.

Вторая проблема более странная. С ней я пока не разбирался вплотную. Я наконец решил заставить работать встроенный модем. Поставил sl-modem-daemon, sl-modem-source для компиляции модулей под моё кастом-билт ядро. Но тут я столкнулся с тем, что sl-modem-source на уровне исходного кода не совместим с новыми ядрами. К примеру, в коде kernel-ver.c делается попытка подключить заголовок linux/version.h чтобы использовать макрос UTS_RELEASE, который в 2.6.20 (и не только в нём) уже определяется в другом файле - linux/utsrelease.h. Можно, конечно, и вручную подправить сырцы драйвера... Кстати, смешной момент, связанный с установкой драйвера. Если сказать apt-get install sl-modem-source, то в Вашем /usr/src появится тарбол sl-modem.tar.bz2 - это и есть сырцы драйвера. Далее, пытаемся ставить его с помощью module-assistant (хех, Debian-way): module-assistant prepare sl-modem; module-assistant install sl-modem и тут происходит ошибка. По вышеупомянутой причине. Правим сырцы, устраняем явные противоречия и снова пускаем m-a install sl-modem и... снова ошибка! В том же месте! :) Лезем в исходники и видим, наших изменений там нет! Так что, мораль в том, что если Вы собираетесь править сырцы sl-modem вручную, то Вам нужно будет из подправленных сырцов создать такой же тарбол с тем же именем, что и оригинал и заменить оригинал. Правда, я пошёл по другому пути - просто выкачал sl-modem-source из пула Debian Sid. Всё собралось без сучка и задоринки, но slmodemd всё равно не видит модули, хотя они и установлены в /lib/modules/`uname -r`/misc. Говорит: FATAL: Module slamr not found. Странно, короче. Можно было бы обойтись без модулей, используя alsa, но это, ИМХО - крайний случай.

Sunday, May 20, 2007

Toshiba Satellite A100-811: Ubuntu

Вот уже что-то около полтора месяца, как мой Toshiba Satellite A100-811 работает под Ubuntu Linux 6.10 (Edgy Eft). Всё началось с того, что мне надоело тянуть бэкпорты для Sarge и собирать софт, которого не было ни в репозитории, ни на бэкпортах вручную. Тем более, некоторые вещи, как например xorg собирать и ставить вручную было и вовсе неохота (заметный framedrop в mplayer'е, работающем через vesa, тоже порядком надоел). Нового Etch тогда под рукой у меня ещё не было, зато пришёл почтой заказанный на ubuntu.com Edgy Eft. Итак, решив, что в конце концов пролетариату нечего терять кроме своих цепей, я потёр систему, оставив снапшот /etc и домашний директорий. Надо сказать, что инсталлятор Ubuntu оказался очень симпатичным. Установка прошла без проблем. Т.к. версия для AMD64/EMT64 почему-то не хотела ставиться нормально, то я использовал обычный дистр для i386. Первое впечатление оказалось несколько неожиданным. С одной стороны, практически всё (за исключением злополучного bluetooth и модема, который я, правда, и не пытался использовать) работало из коробки. Даже Intel PRO/Wireless 3945ABG на драйвере ipw3945, о котором я здесь также уже упоминал. Но, Gnome в качестве десктоп-менеджера не сильно меня воодушевлял, а boot-сплэш вместо дампа ядра несколько дезориентировал :-). И с первым, и со вторым я справился. Gnome заменил на KDE, а к boot-сплэшу просто привык уже :-) Из коробки на штатном ядре (2.6.17) у меня работали даже suspend-to-ram и hibernate. Но, когда я налюбовавшись всеми этими прелестями начал окультуривать свежеустановленную систему, начали проявляться и побочные эффекты. 1-й и самый заментый - практически все пакеты надо тянуть из репозитория в сети, т.к. у меня был лишь установочный диск с базовыми компонентами (естественно, ядро, libc, xorg, Gnome, base-utils, net-utils и всё, по существу). Поэтому для людей, у которых нет толстого канала это может очень сильно омрачить первое впечатление. У меня толстого канала тоже нет, но зато есть работа, где, в свою очередь, есть приличный канал :-) Второй момент, вся система изначально заточена под Gnome в качестве десктоп-менеджера и это быстро проявляется не очень приятным образом. Так, например, после установки KDE у меня отказался нормально работать hibernate и suspend-to-ram. Система засыпала, но уже не просыпалась. Самое смешное, что происходило такое безобразие только при работе в KDE и никак не отражалось на gdm (я оставил его логин-менеджером - внешне он понравился мне больше, чем kdm). Немного порыскав по логам и конфигам я сделал такие правки в /etc/hibernate/common.conf:

### clock
SaveClock restore-only

### network
DownInterfaces eth0 eth1
UpInterfaces eth0 eth1

### vbetool
EnableVbetool yes
RestoreVbeStateFrom /var/lib/vbetool/vbestate
VbetoolPost yes
RestoreVCSAData yes

### xhacks
SwitchToTextMode yes
# UseDummyXServer yes
# DummyXServerConfig xorg-dummy.conf

### xstatus
## This can be set to gnome, kde or x:
XStatus kde
и такую в xorg.conf:
Section "Device"
 Identifier "Intel Corporation Mobile Integrated Graphics Controller"
 Driver  "i810"
 BusID  "PCI:0:2:0"
 Option  "VBERestore"  "true"
EndSection
(добавил опцию "VbeRestore" - для использования этого добра необходим пакет vbetool). Вот, собственно, и всё, что я сделал для восстановления работоспособности режимов hibernate и suspend-to-ram.

Ещё один неприятный момент выявился позже. Об этой ошибке уже писали на форуме ubuntu. При возврате из спящего режима, во время работы от аккумулятора, только одно ядро CPU работает на пониженной частоте, в то время, как другое процессорное ядро работает на полных оборотах, что нежелательно при работе от аккумулятора и вообще нарушает системную гармонию :-) Я быстро нашёл чьё-то решение: поместить следующий скрипт в /etc/acpi/resume.d

#!/bin/sh

SYS_DIR=/sys/devices/system/cpu
POLICY=ondemand

for CPU in `ls $SYS_DIR`
do
    echo -n "$POLICY" > $SYS_DIR/$CPU/cpufreq/scaling_governor
done
под именем, ну пусть хотя бы и 10-dualcore-freq (я обозвал его так, хотя у автора он назывался по-другому). Однако, это мне не помогло. Как было видно из лога /var/log/acpid, он почему-то не обрабатывал такие события, как suspend или resume. Зато он всё равно при пробуждении обрабатывал power, при котором, в свою очередь, выполнял скрипты из battery.d или ac.d, в зависимости от того, работает лаптоп от аккумулятора или сети. Поэтому сей скрипт я положил в /etc/acpi/battery.d, придя к выводу, что так даже лучше, потому что мне ни к чему экономить гигагерцы при работе от сетевого адаптера. Здесь, как не трудно догадаться по аналогии с boot-скриптами, префикс в имени файла "10-" - это приоритет. У меня, к примеру, этот скрипт имеет наивысший приоритет и выполняется самым первым.

Затем обнаружились проблемы с кард-ридером MMC/SD от Texas Instruments. Драйвер ядра просто не умел работать с некоторыми картами. Наконец я созрел для замены ядра! Я хотел заменить ядро в надежде, что новый драйвер, быть может, доработан в этом плане. Кроме того, я никогда не любил тайм-принты printk() :-) Итак, я решил поставить последнее ядро стабильной ветки, тарбол которого у меня было на тот момент - 2.6.20. Правда, я решился на замену ядра не сразу. Ведь драйвер для Intel PRO/Wireless 3945ABG тоже пришлось бы пересобирать. Но, в конце концов, я решился. Предвижу гневные возгласы, потому сразу скажу, там где мне проще работать по привычке, а не по-новому, я отдаю предпочтение привычке. У меня нет особого желания разучивать опции нового тула только потому, что его использование - это Debian-way. Поэтому, ядро я ставил вручную, как привык делать уже очень давно - без использования kernel-package:

cp /boot/config-<old-kernel-version> /usr/src/linux-2.6.20/.config
make oldconfig
make menuconfig
make -j 8 bzImage modules
make modules_install
update-initramfs -k 2.6.20 -c
cp ./System.map /boot/System.map-2.6.20
cp ./.config /boot/config-2.6.20
cp ./arch/i386/boot/bzImage /boot/vmlinuz-2.6.20
Правим /boot/grub/menu.lst соответственно - вот и вся премудрость. Затем, распаковав тарбол с модулем ipw3945 я попытался его собрать. Но... натолкнулся на глухое непонимание Makefile'а. Он кричал, что версия модулей подсистемы ieee80211 не соответствует карте символов ядра. Т.е., Makefile полагал, что я использую out-of-tree реализацию ieee80211, а не родную ядерную. Мне так и не удалось переубедить make. В сети я нашёл замечательное решение: пропатчить ядро на предмет ipw3945! Патч ipw3945-1.2.0_for_2.6.19.patch лёг на ядро без проблем. Правда, мне пришлось переконфигурировать ядро и пересобрать его, но зато - больше никаких проблем с компиляцией модуля вне дерева кода ядра. Ещё одна маленькая хитрость. Демон и микрокод для драйвера я не ставил - они уже были в системе. Но для того, чтобы всё заработало, надо сделать симлинк на демон ipw3945d-<old-kernel-version>, где бы вместо <old-kernel-version> стояла версия Вашего ядра (в моём случае, это 2.6.20, как не трудно догадаться :-) Кстати, в листинге ключ make -j определяет число параллельно выполняющихся экземпляров make при сборке. Если числовой параметр опущен, то make создаёт столько экземпляров самой себя, что в конце концов истощает все системные ресурсы. 8 - параллельных инстанций для моего Core 2 Duo было самое то. Если установить слишком большое число, система может перестать реагировать, будто она зависла, или начать сильно тормозить. Поэтому применяйте данный параметр разумно и без фанатизма. Кроме таймингов printk я убрал все ненужные модули, включил оптимизацию под Core 2 и включил в ядро драйвер консольного фрейм-буфера и vesa. Хотя, сперва я пытался заставить работать нативный драйвер intelfb. Драйвер имеет статус экспериментального и у меня так и не заработал нормально, поэтому я откатился к vesafb. Вот по сути и всё, что касается ядра.

Кстати, я долго пытался найти в KDE что-нибудь, что позволяло бы быстро переключаться между настройками сетевого интерфейса (домашняя сеть и сеть на работе), но так и не нашёл ничего работающего :-) В конечном счёте я решил воспользоваться способом, который видел когда-то в сети.

Суть этого нехитрого способа состоит в использовании разных схем в /etc/network/interfaces. Для его использования вносим изменения в сам файл /etc/network/interfaces:

auto lo
allow-hotplug eth1
iface lo inet loopback

mapping eth0
script /etc/network/map-scheme.sh
map home eth0-home
map work eth0-work

iface eth0-home inet static
 address xxx.xxx.xxx.xxx
 netmask xxx.xxx.xxx.xxx
 broadcast xxx.xxx.xxx.xxx
 network xxx.xx.xx.xxx
 gateway xxx.xxx.xxx.xxx

iface eth0-work inet static
 address yyy.yyy.yyy.yyy
 netmask yyy.yyy.yyy.yyy
 gateway yyy.yyy.yyy.yyy

auto eth0
Теперь мы имеем две схемы с разными настройками сетевого интерфейса: home - для домашней сети и work - для рабочей, соответственно. Там же, в /etc/network добавляем такой скрипт:
#!/bin/sh

iface="$1"
if [ "$iface" = "" ]; then iface="eth0"; fi
if [ -f /etc/network/scheme ]; then
 scheme=$(cat /etc/network/scheme)
 echo $iface-$scheme
else
 echo $iface
fi
exit 0
А в /usr/local/bin создаём такой скриптик:
#!/bin/sh

if [ $# -le 0 ]; then
 echo -n "Current scheme: "
 cat /etc/network/scheme
 exit 0
fi
echo "$1" > /etc/network/scheme
который у нас и будет переключать схемы и по совместительству показывать, какая схема активна в данный момент. Посмотреть её можно также в файле /etc/network/scheme, который используется нашим скриптом map-scheme.sh для получения имени активной схемы. Сама схема устанавливается скриптом netscheme. Опциональный параметр - имя схемы, на которую необходимо переключиться. Без параметра скрипт просто выдаёт имя активной схемы. После переключения схемы нужно ещё выполнить ifdown/ifup, чтобы изменение параметров вступили в силу.

Ну, что тут, в общем, скажешь, старый добрый shell script попрежнему даёт... эээ, фору разным там гуям ;-) Хотя, желающие вполне могут прикрутить к этому делу гуёвый фронт-энд, благо сделать это также не сложно.

В итоге, Edgy Eft мне понравилась. Я настроил под себя всё окружение. Свежий софт работает без нареканий. Хотя, кое-чего в репозитории не было и мне пришлось поставить недостающий софт из собственных запасов. Речь об Adobe Acrobat Reader и rar. Дистрибутивный KchmViewer меня приятно порадовал. Управление энергосбережением я настроил через KLaptop. К слову, влияние xfs на работу suspend установить больше не было возможности, так как я слегка реструктурировал разбиение диска. Теперь корневая ФС у меня на reiserfs, а /home - полностью на xfs. Я не вдавался в детальные исследования, но во всяком случае, ничто не мешает отмонтировать /home перед засыпанием и модуль xfs тут уже не может помешать, т.к. оказывается незадействованным. Действительно ли hibernate отмонтрирует /home, я не проверял - повторюсь ещё раз. Но, вполне вероятно, что проблему можно устранить, если использовать для root какую-нибудь другую ФС, кроме xfs. Драйвер i810 в xorg 7.1.1, как и ожидалось, прекрасно позволяет смотреть фильмы в полноэкранном режиме и играть в Quake III Arena. Правда, мне до сих пор не удалось заставить Q3A правильно масштабироваться. Но это уже мелочи :-)

Sunday, March 25, 2007

Toshiba Satellite A100-811: WiFi

В Toshiba Satellite A100-811 встроен WiFi-адаптер Intel PRO/Wireless 3945ABG. В принципе, поиск драйвера для этого устройства не представляет собой проблемы. Драйвер можно скачать с официального сайта intel. На данный момент доступны 2 разновидности драйвера: 1-я - это ipw3945. Строго говоря, драйвер состоит из трёх компонентов - собственно, модуль ядра ipw3945, бинарный образ микрокода (firmware) - ipw3945-ucode и демон, работающий в пространстве пользователя - ipw3945d (управляет драйвером).

2-я разновидность - экспериментальный драйвер iwlwifi: это собственно модуль ядра и образ firmware (iwlwifi-ucode). Управляющего демона здесь уже нет (к счастью). Этот драйвер также доступен для загрузки с сайта intel. Кстати, несмотря на предупреждение в документации, что драйвер имеет статус экспериментальный, у меня он работает без каких-либо нареканий.

Изначально, чтобы заставить работать адаптер, я использовал 1-й драйвер (ipw3945). Сложностей с его установкой в общем-то нет никаких (версия ядра, с которой согласен собираться модуль - 2.6.13+). Выкачал драйвер: ipw3945-1.2.0, демон ipw3945d-1.7.22 + firmware не-помню-какой-точно-версии (последняя). Собрал модуль (без проблем), положил firmware в /usr/lib/hotplug/firmware. Положил демон в куда-надо (/sbin), добавил в /etc/modprobe.d файлик, чтобы оно пускало демон при загрузке модуля (файл /etc/modprobe.d/ipw3945):

install ipw3945 /sbin/modprobe --ignore-install ipw3945 ; sleep 0.5 ; /sbin/ipw3945d-start 
remove ipw3945  /sbin/ipw3945d --kill ; /sbin/modprobe -r --ignore-remove ipw3945-stop
Скрипты взяты из тарбола с демоном - ipw3945d:
ipw3945-start
#!/usr/bin/env /bin/bash
# If no parameter is provided then set user to 'root'
[ "$1" ] && user=$1 || user=root

wait_for_cmd() {
 # First, wait for sysfs entry to show up, timing out after 10 seconds:
 count=0
 while [ ! -e /sys/bus/pci/drivers/ipw3945/*/cmd ]; do
  sleep 0.5
  count=$((count+1))
  ((count > 20)) && return 1
 done

 return 0
}

wait_for_cmd && {
 # Set ownership of sysfs entry:
 chown $user /sys/bus/pci/drivers/ipw3945/*/cmd
 chmod a-w,u+rw /sys/bus/pci/drivers/ipw3945/*/cmd

 # Verify/set up PID directory 
 [ ! -d /var/run/ipw3945d ] && {
  mkdir -m 0775 /var/run/ipw3945d
  chown $user /var/run/ipw3945d
 }

 # Launch the regulatory daemon:
 /sbin/ipw3945d --quiet --pid-file=/var/run/ipw3945d/ipw3945d.pid
}


ipw3945-stop
#!/usr/bin/env /bin/bash
PIDPATH=/var/run/ipw3945d
# Kill the regulatory daemon if it is running:
/sbin/ipw3945d --isrunning --pid-file=${PIDPATH}/ipw3945d.pid &&
 /sbin/ipw3945d --kill --pid-file=${PIDPATH}/ipw3945d.pid

# Remove the PID directory
[ -d ${PIDPATH} ] && rm -rf ${PIDPATH}

К слову, директорию /var/run/ipw3945d, где демон будет держать файл со своим PID, желательно создать вручную.

Сам модуль грузится нормально, тянет за собой ieee80211 (который должен быть включен в ядре - по идее, в документации требуется загрузить более новую версию ieee80211, но я не патчил ядро). Выделяется ирка (IRQ18). В общем, всё как-будто нормально. Но демон грузиться отказывается и вываливает сообщение: Could not find Intel PRO/Wireless connection. Модуль при загрузке рапортовар: Intel PRO/Wireless connection detected, но не показывал при этом Detected geography ... Как результат, нет второго eth-интерфейса и вообще, девайс неработает. Начал грешить на hotplug, а именно, что он не грузит firmware, но, как выяснилось позже, hotplug здесь совершенно ни при чём. В конечном счёте, виновным был признан демон. Согласно всё той же документации, демон сперва деинициализирует драйвер при загрузке, затем инициализирует по-новому и если его что-то не устраивает, просто отваливается. В противном случае, если всё нормально - производит калибровку.

Одним словом, я просто плюнул на всякие дальнейшие разбирательства. Тем более, что инстинктивно мне почему-то очень не нравилась такая модель драйвера, где присутствует управляющий user-space демон.

Итак, заменяем неблагонадёжный ipw3945 + ipw3945d на новый iwlwifi. В документах написано, что этот драйвер экспериментальный и может не работать. Но у меня он во всяком случае заработал. Для работы (и сборки?) понадобится подсистема mac80211 (по докам - это кучка фиксов для уже имеющейся в ванильном ядре подсистемы ieee80211 и "ставится" она без проблем на ядра 2.6.18+). Находим и сливаем по возможности последнюю версию, в моём случае это был тарбол mac80211-4.0.4. Распаковываем, говорим make. В ответ на это нам выплёвывают список изменений, которые будут предприняты в ядре. Затем, если всё удачно (этой приблуде может понадобиться rsync - мне пришлось доустанавливать его, т.к. до этого никакой потребности в нём я не испытывал и, соответственно, не ставил) говорим make patch_kernel. В каталоге, где распакован тарбол получаем несколько новых подкаталогов (некоторые были и до этого, но в них появилось новое содержимое): compatible, modified, origin, patches, stubs. В compatible лежат пропатченные куски кода. Копируем содержание compatible в /lib/modules/`uname -r`/build. Затем натравливаем на /lib/modules/`uname -r`/build скрипт ieee80211_ptr.sh из <unpacked_tarball>/scripts. Если этого не сделать, компилятор при пересборке ядра под конец начнёт вопеть как резанный, что structure has no member 'ieee80211_ptr' (На самом деле, странно, но в пропатченном коде просто не делается правильное приведение к типу). В любом случае, ядро придётся пересобирать полностью, естественно. Перед сборкой в конфиге должны быть определены символы CONFIG_CFG80211=m && CONFIG_MAC80211=m (Через menuconfig CONFIG_MAC80211 это Networking -> Generic IEEE 802.11 Networking Stack (dscape) где находится CONFIG_CFG80211, сейчас точно не вспомню, но при желании можно найти).

Если всё прошло успешно, ставим модули, ядро. Перезагружаемся. Тащим тарбол с интеловским firmware: iwlwifi-ucode-2.14.1 или выше. Кладём его в /usr/lib/hotplug/firmware (или /lib/firmware, в зависимости от того, где Ваш hotplug хотел бы всё это хозяйство видеть).

Тащим тарбол с самим драйвером: wlwifi-0.0.11 или выше. Распаковываем, говорим make, make install. После того, как модуль собран и установлен, проверяем:

 # ./load debug=0x43fff 
 
 <SKIPPED>

 выплёвывает кучу всякой полезной и не очень информации, главнй смысл в фразах, что соединение найдено, информация о каналах и тому подобное 
 </SKIPPED> 
 
 # iwconfig 
 wmaster0  IEEE 802.11a  Channel:0 
 
           RTS thr:off   Fragment thr=2346 B 
      
 wlan0     IEEE 802.11a  ESSID:"" 
    Mode:Managed  Channel:0  Access Point: 00:00:00:00:00:00 
    RTS thr:off   Fragment thr=2346 B 
    Encryption key:off 
    Link Quality:0  Signal level:0  Noise level:0 
    Rx invalid nwid:0  Rx invalid crypt:0  Rx invalid frag:0 
    Tx excessive retries:0  Invalid misc:0   Missed beacon:0
Что и требовалось доказать. Демон здесь не нужен, поэтому, установка практически завершена.

PS: последовательность моих действий несколько отличается от той, что описана в документации. Например, мне не удалось запустить даже make menuconfig после пропатчивания ядра на предмет mac80211, т.к. make ругался на отсутствие конфига в net/mac80211 (самого каталога тоже не было). Поэтому я просто порывшись определил, где и что находится и скопировал содержимое compatible /lib/modules/`uname -r`/build. Про то, что в пропатченных сырцах компилятор начнёт жаловаться на неприведение к нужному типу, тоже сказано ничего не было (по поводу ieee80211_ptr.sh) Ещё один момент: утилиты выдают предупреждения по поводу стека wireless-расширений:

devel:/home/f0x/tmp# iwconfig wlan0
Warning: Driver for device wlan0 has been compiled with version 21
of Wireless Extension, while this program supports up to version 17.
Some things may be broken...
Но у меня пока проблем не наблюдалось никаких. Возможно, этот чисто внешний дефект устраняется апгрейдом пакета утилит, но меня эти сообщение особо не смущают, так что я ничего не апгрейдил.

Friday, March 23, 2007

Toshiba Satellite A100-811: Bluetooth & suspend

Попутно ещё пара замечаний. В плане bluetooth в моём случае стоит ориентироваться на модуль toshiba_acpi. Отсюда можно почерпнуть косвенное подтверждение тому, что bluetooth активируется через ACPI. Очевидно, для моей модели нет поддержки. А может, здесь такой хитрый ACPI. В общем, я запасся последней спецификацией ACPI - попробую разобраться хоть частично, что к чему. Понять, по какой причине не грузится модуль сложно. Такой код ни к чему, в общем-то, не обязывает (drivers/acpi/toshiba_acpi.c):

static int __init toshiba_acpi_init(void)
{
 acpi_status status = AE_OK;
 u32 hci_result;
 int status2;

 if (acpi_disabled) {
  printk(MY_INFO"ACPI disabled, failed to initialize module\n");
  return -ENODEV;
 }

 if (!acpi_specific_hotkey_enabled) {
  printk(MY_INFO "Using generic hotkey driver\n");
  return -ENODEV;
 }
 /* simple device detection: look for HCI method */
 if (is_valid_acpi_path(METHOD_HCI_1))
  method_hci = METHOD_HCI_1;
 else if (is_valid_acpi_path(METHOD_HCI_2))
  method_hci = METHOD_HCI_2;
 else {
  printk(MY_INFO"device detection: no valid HCI method available\n");
  return -ENODEV;
 }
 ...

Так или иначе, но драйвер не очень многословен и максимум на что его хватает - возвратить ENODEV. Не знаю, насколько это удачная идея, но попробую поиграться с модулем. Для начала имеет смысл вставить в код формальные метки, чтобы было яснее, что может быть не так. Дальше, посмотрим по результатам изучения спецификации и наличного металлолома. К слову, в связи со всем этим, хотелось бы сказать своё "фи" в адрес конторы, носящей гордое название "Toshiba". Похоже, что фирме гораздо выгоднее "рекомендовать Windows Vista", чем поддерживать клиентов хотя бы более подробной технической информацией в интернет, я уж не говорю о том, что можно было собраться с силами и наваять-таки что-нибудь и для linux, хотя бы вот как Intel или nVidia. (Эмоции, однако).

В отношении suspend: как минимум два потока ядра (две подсистемы, два драйвера - называйте, в общем, как хотите) не дают перейти системе в suspend. А именно:

  1. fs/xfs/linux-2.6/xfs_super.c::xfssyncd()
  2. net/core/pktgen.c::pktgen_thread_worker()

1-й из них - поток ядра, создаваемый xfs для синхронизации файловой системы. Не совсем понятно, почему дизайн ФС именно такой, какой он есть, но здесь можно аргументировать отсутствие поддержки режима сна. Хотя, с другой стороны, вместе с тем, что нет вызова try_to_freeze(), нет и установки флага PF_NOFREEZE. Не уверен на все 100%, но вероятно, добавление строки current->flags |= PF_NOFREEZE; позволило бы обойти проблему. Правда, это по-своему рискованно. Должна быть гарантия, что поток не изменит уже сохранённый на свопе образ системы. Иными словами, нельзя, допустим, чтобы после выгрузки образа системы на устройство подкачки кто-то/что-то изменило данные на винчестере. Возникает несоответствие между актуальным состоянием файловой системы и тем, что было сохранено в дамп на свопе. Причина достаточно веская, чтобы не экспериментировать. Впрочем, мои предположения ещё нужно подтвердить. Проще всего это наверно сделать путём сбора статистики юзеров. Если всё же есть люди, у которых xfs уживаются в одном флаконе с suspend - то тем лучше, значит проблема лечится без лоботомии или ампутации.

По поводу 2-го, не совсем понятно, почему там нет поддержки спящего режима. pktgen_thread_worker() - он же наблюдаемый в ps ax, как kpktgend_[0|1] - не что иное, как генератор пакетов, предназначенный для тестирования сети. Совсем не понятно, почему генератор пакетов _не_должен_ замораживаться и что он будет делать, если сетевые интерфейсы будут отключены.

Строго говоря, список драйверов, не поддерживающих suspend этими двумя пунктами не исчерпывается. Кое-что по этому поводу написано здесь - http://suspend2.epfl.ch/status. Список довольно общего плана, но даёт некоторое представление о том, с чем suspend2 может не заработать (suspend это, кстати, касается тоже).

ПОСЕТИТЕЛИ

free counters