Глава 2. Драйверы устройств.


               Что такое драйвер устройства.

     Создание  драйвера  устройства  - дело достаточно трудоемкое.
Запись  на  жесткий  диск  требует помещения определенных цифровых
данных   в   определенное   место,  ожидания  ответа  на  запрос о
готовности жесткого  диска, затем аккуратной пересылки информации.
Запись  на  флопповод  проходит  еще  сложнее  -  нужен постоянный
контроль на текущим состоянием дискеты.

     Вместо   помещения   кода   каждого   отдельного   приложения
управляющего  устройством,  вы  разделяете код между приложениями.
Вам   следует   защитить   этот   код  от  других  пользователей и
использующих его программ.

     Если  вы верно сделали это, то вы можете без смены приложений
подключать  или  убирать  устройства.  Более того, вы должны иметь
возможности ОС - загрузить вашу программу в память и запустить ее.
Так  что  ОС,  в  сущности, - это набор привилегированных, общих и
частных   функций  или  функций  аппаратного  обеспечения  низкого
уровня,функций работы с памятью и функций контроля.

     Все  версии UNIX имеет абстрактный способ считывания и записи
на   устройство.  Действующие  устройства  представляются  в  виде
файлов,  так что одинаковые вызовы ( read(), write() и т.п.) могут
быть использованы и как устройства и как файлы.

     Внутри  ядра  существует  набор функций, отмеченных как файлы,
вызываемые при запросе для ввода/вывода на файлы устройств, каждый
из которых представляет свое устройство.

     Всем устройствам, контролируемым одним драйвером, дается один
и тот же основной номер, и различные подномера.

     Эта  глава  описывает,  как  написать  любой из допускаемых в
Linux  типов  драйверов устройств : символьных, блочных, сетевых и
драйверов  SCSI.  Она описывает, какие функции вы должны написать,
как  инициализировать  драйверы  и  эффективно  выделять  под  них
память,  какие функции встроены в Linux для упрощения деятельности
такого рода.

     Создание  драйвера  устройств  для  Linux  оказывается  более
простым  чем  мнится  на  первый  взгляд,  ибо оно включает в себя
написание  новой  функции  и определение ее в системе переключения
файлов(VFS).

     Тем   самым,   когда  доступно  устройство,  присущее  вашему
драйверу, VFS вызывает вашу функцию.

     Однако,  вы  должны  помнить, что драйвер устройства является
частью  ядра.  Это означает, что ваш драйвер запускается на уровне
ядра  и обладает большими возможностями : записать в любую область
памяти,  повредить  ваш  монитор  или разбить вам унитаз в случае,
если ваш компьютер управляет сливным баком.

     Также  ваш  драйвер  будет запущен в режиме работы с ядром, а
ядро  Linux,  как  и  большинство  ядер  UNIX,  не  имеет  средств
принудительного  сброса.  Это означает, что если ваш драйвер будет
долго  работать, не давая при этом работать другим программам, ваш
компьютер  может "зависнуть ". Нормальный пользовательский режим с
последовательным запуском не обращается к вашему драйверу.

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


            Драйверы пользовательского уровня.

     Не  всегда нужно писать драйвер для устройства, особенно если
за  устройством  следит  всего  одно приложение. Наиболее полезным
примером  этому является устройство карты памяти, однако вы можете
сделать  карту  памяти  с помощью устройств ввода/вывода (доступ к
устройствам осуществляется с помощью функций inpb() и outpb()).

     Если  вы работаете в режиме superuser, вы можете использовать
функцию  mmap  для  того,  чтобы поместить вашу функцию в какую-то
область  памяти. С помощью этой процедуры вы сможете весьма просто
работать с адресами памяти, как с обычными переменными.

     Если  ваш  драйвер  использует  прерывание,  то  вам придется
работать  внутри  ядра,  так  как  не  существует других путей для
прерываний  обычных  пользовательских  процессов. В проекте DOSEMU
однако, есть Простейший Генератор прерываний - SIG, но он работает
недостаточно  быстро,  как  это  можно  было  ожидать от последней
версии DOSEMU.

     Прерывание  - это жестко определенная процедура. Также вы при
установке  своего  аппаратного  обеспечения  вы  определяете линию
IRQ   для  физического  сигнала  прерываний,  возникающего,  когда
устройство   обращается   к   драйверу.   Это   происходит,  когда
устройство  пересылает  или  запрашивает  информацию,  а также при
обнаружении  каких-либо  исключительных ситуаций, о которых должен
знать  драйвер.  Для  обработки  прерываний в ядре и для обработки
сигналов  на  пользовательском  уровне  используется  одна и та же
структура   данных   -   sigaction.  Таким  образом,  где  сигналы
аппаратных   прерываний   доставляются  ядру  точно  так  же,  как
системные сигналы на уровне пользовательского обеспечения.

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


                   2.2.1 Пример - vgalib.

     Хорошим  примером  драйвера пользовательского уровня является
библиотека   vgalib.  Стандартные  функции  read()  и  write()  не
подходят   для   написания   действительно  быстрого  графического
драйвера,   и   поэтому  существует  библиотека  функций,  которая
концептуально    работает    как   драйвер   устройства,   но   на
пользовательском  уровне.  Все  функции,  которые  используют  ее,
должны  запускать setuid, так как она использует системную функцию
ioperm().   Функции,   которые   не   запускают  setuid,  обладают
возможностью  записи  в  /DEV/MEM,  если у вас есть группы mem или
kmem,  которые  позволяют  это,  но только корневые процессы могут
запускать ioperm().

     Есть  несколько  портов  ввода/вывода,  относящихся к графике
VGA. Vgalib дает им символические имена с помощью #define, и далее
используют ioperm() для разрешения функции правильного прочтения и
записи в эти порты.

        if (ioperm(CRT_IC, 1, 1)) {
           printf("VGAlib: can't get I/O permission \n");
           exit(-1);
         }
        ioperm(CRT_IM, 1, 1);
        ioperm(ATT_IW, 1, 1);
        [--]

     Это  требует  лишь однократной проверки, так как единственной
причиной нефункционирования ioperm() может быть обращение к ней не
в статусе superuser или во время смены статуса.

/\
\/   После вызова этой функции разрешается использование inb и outb
     инструкций, однако лишь с определенными портами. Эти инструкции
     могут быть доступны без использования прямого ассемблерного кода
     , но работают они лишь в случае компиляции с параметром
     optimization on и с ключом -0?. Для более подробных сведений читай
     .
     После обращения в порты ввода вывода vgalib засылает информацию
     в область ядра следующим образом :

            /* open /dev/mem */
            if ((mem_fd = open("/dev/mem", 0_RDWR) ) < 0) {
                prntf( "VGAlib: can' t open /dev/mem \n");
                exit (-1);
            }

            /* mmap graphics memory */
            if ((graph_mem = malloc(GRAPH*SIZE + (PAGE-SIZE-1))) == NULL) {
                printf( " VGAlib: allocation error \n ");
                exit (-1);
            }
            if ((unsigned long)graph_mem % PAGE_SIZE)
                graph_mem += PAGE_SIZE - ((unsigned long)graph_mem % PAGE_SIZE);
            graph_mem = (unsigned char *)mmap(
                (caddr_t)graph_mem,
                GRAPH_SIZE,
                PROT_READ|PROT_WRITE,
                MAP_SHARED|MAP_FIXED,
                mem_fd,
                GRAPH_BASE
            );
            if ((long)graph_mem < 0) {
                printf(" VGAlib: mmap error \n");
                exit (-1);
            }


     В   начале   программа   открывает  /dev/mem,  затем  выделят
достаточное количество памяти для распределения на страницу, затем
меняет карту памяти.

     GRAPHSIZE - размер памяти vga.
     GRAPHBASE - адрес начала памяти VGA в /dev/mem.

     Затем,  записывая  в  адрес  возвращаемый  mmap(),  программа
осуществляет запись в память экрана.


               2.2.2 Пример : Преобразование мыши.

     Если  вы  хотите  написать  драйвер, работающий так же, как и
драйвер  на  уровне  ядра,  но не находящийся в его области, то вы
можете  создать  fifo  (буфер  -  first  in, first out). Обычно он
помещается в директорию /dev (во время нефункционирования) и ведет
себя как подключенное устройство.

     В  частности, это используется когда вы используете мышь типа
PS/2   и   хотите  запустить  XFree86.  Вы  должны  создать  fifo,
называемый /dev/mouse, и запустить программу mconv, которая, читая
сигналы  мыши  PS/2  из  /dec/psaux,  пишет  эквивалентные сигналы
microsoft mouse в /dev/mouse.

     В  этом  случае  XFree86 будет читать сигналы из /dev/mouse и
функционировать также как и при подключенной microsoft mouse.


                 2.3 Основы драйверов устройств.

     Мы  будем  полагать,  что  вы  не  хотите  писать  драйвер на
пользовательском  уровне,  а  желаете  работать  непосредственно в
области ядра.

     В  таком случае вам придется иметь дело с файлами .с и .h. Мы
будем условно обозначать ваши труды как foo.c и foo.h.


               2.3.1 Область имени (именная область).

     Первое что вы должны сделать при написании драйвера - назвать
устройство.  Имя  должно  выть  кратким  -  строка  из двух - трех
символов.  К  примеру,  параллельные  устройства - "lp", дисководы
"fd", диски SCSI - "sd".

     Создавая ваш драйвер, называйте функции в нем с первыми тремя
буквами  избранной  строки  в имени. Так как мы называем его foo -
функции в нем соответственно - foo_read и foo_write.

                   2.3.2 Выделение памяти.

     Выделение  памяти  в  ядре  отличается от выделения памяти на
пользовательском  уровне. Вместо функции malloc() выделяющее почти
неограниченное  количество  памяти,  существует kmalloc(), которая
имеет некоторые отличия:

     -   Память   выделяется   кусками   размером  степени  2,  за
исключением  кусков больше 128 байтов, размер коих равен степени 2
за  вычетом  части  под  метку  о  размере.  Вы  можете  запросить
произвольный  размер,  однако  это  будет неэффективно, так как 31
байтового  об'екта, к примеру, выделяется 32 байтовый кусок. Общий
предел выделяемой памяти 131056 байт.

     -   В   качестве   второго   аргумента  kmalloc()  использует
приоритет.   Он   используется   в   качестве   аргумента  функции
get_free_page(),    где   он   используется   в   качестве   числа
определяющего  момент  возврата.  Обычно  используемый приоритет -
GFP_KERNEL.  Если  функция может быть вызвана с помощью прерывания
используйте  GFP_ATOMIC  и приготовьтесь к тому, что функция может
не  работать.  Это  происходит  из-за  того, что при использовании
GFP_KERNEL  kmalloc()  может  не  быть  активным  в  любой  момент
времени, что не возможно при прерывании. Можно так же использовать
опцию GFP_BUFFER, которая используется для выделения ядром области
буфера. В драйверах устройств она не используется.

     Для очистки памяти, выделенной kmalloc(), используйте функции
kfree()  и  kfree_s().  Они  также несколько отличаются от функции
free() :

     - kfree() - это макрос, вызывающий kfree_s() и работающая как
free() вне ядра.

     -  Если  вы  знаете размеры об'екта, удаляемого из памяти, вы
можете   ускорить   процесс,  запуская  сразу  kfree_s().  У  него
существуют  два  аргумента  :  первым  является  аргумент kfree(),
вторым - размер удаляемого об'екта.

     См  2.6 для получения более подробной информации о kmalloc(),
kfree() и о других полезных функциях.

     Другой  способ  сохранить  память  -  выделение  ее  во время
инициализации.   Ваша   инициализационная   функция   foo_init() в
качестве   аргумента   использует   указатель   на  текущий  конец
памяти.Она  может  взять  столько  памяти, сколько хочет сохранить
указатель/указатели  на эту память и возвратить указатель на новый
конец  памяти.Преимуществом  этого  метода  является  то,  что при
выделении  большого буфера в случае, если foo - драйвер не находит
foo-  устройства,  подключенного к компьютеру, память не тратится.
Функция  инициализации  подробно обсуждается в части 2.3.6. Будьте
предельно  аккуратны  при использовании kmalloc(), используйте его
только  в случае крайней необходимости. Помните, что память в ядре
не  своппится. Аккуратно выделяйте ее, а затем каждый раз очищайте
ее функцией frее().

     ! Существует возможность выделения виртуальной памяти с помощью
       vmalloc(), однако это будет описано лишь в главе VMM во время ее
       написания. В данный момент вам придется изучать это самостоятельно.!


            2.3.3 Символьные и блочные устройства.

     Существует  два типа устройств в системах UN*X - символьные и
блочные  устройства.  Для  символьных  устройств  не предусмотрено
буфера,  в то время как блочные устройства имеют доступ лишь через
буферную память. Блочные устройства должны быть равнодоступными, а
для  символьных  это  не  обязательно,  хотя  и возможно. Файловая
система  может  работать  лишь в случае, если она является блочным
устройством.

     Общение  с  символьными устройствами осуществляется с помощью
функций foo_read() и foo_write(). Функции foo_read() и foo_write()
не  могут останавливаться в процессе деятельности, поэтому блочные
устройства  даже  не  требуют использования этих функций, а вместо
этого   используют   специальный  механизм,  называемый  "strategy
routine"   -   стратегическая   подпрограмма.   Обмен  информацией
происходит  при  помощи  функций  bread(), breada(), bwrite(). Эти
функции,  просматривая  буферную  память, могут вызывать "strategy
routine" в зависимости от того, готово устройство или нет к приему
информации   (в   случае   записи  -  буфер  переполнен),  или  же
присутствует  ли  информация  в  буфере  (в случае чтения ).Запрос
текущего  блока  из буфера может быть асинхронен чтению - breada()
может  вначале  определить  график  передачи  информации,  а затем
заняться  непосредственно  передачей.  Далее  мы представим полный
обзор   буферной   памяти(кэш).  Исходные  тексты  для  символьных
устройств  содержатся  в  /kernel/chr_drv, исходники для блочных -
/kernel/blk_drv.  Для  простоты  чтения  интерфейсы у них довольно
просты,  за исключением функций записи и чтения. Это происходит из
за  определения  вышеописанной "strategy routine" в случае блочных
устройств    и   соответствующего   ему   определения   foo_read и
foo_write()  для  символьных  устройств.  Более подробно об этом в
2.4.1 и 2.5.1.


       2.3.4. Прерывание или поочередное опрашивание устройств ?

     Аппаратное  обеспечение  работает  достаточно  медленно.  Это
определяется  временем  получения  информации,  в момент получения
которой  процессор не занят, и находится в состоянии ожидания. Для
того чтобы вывести процессор из режима работа - ожидание, вводятся
!   прерывания   !  -  процессы,  предназначенные  для  прерывания
конкретных  операций  и  предоставления  ОС  задачи  по выполнению
которой последняя без потерь возвращается в исходное положение.

     В    идеале    все    устройства    должны   обрабатываться с
использованием  прерываний,  однако на PC и совместимых прерывания
используются  лишь в некоторых случаях, так что некоторые драйверы
вынуждены  проверять аппаратное обеспечение на готовность к приему
информации.

     Так   же   существуют   аппаратные   средства   (   дисплей с
распределенной  памятью  )  работающие  быстрее  остальных  частей
компьютера. В таком случае драйвер, управляемый прерываниями будет
выглядеть нелепо.

     В Linux cуществуют как драйверы, управляемые прерываниями так
и драйверы, не использующие прерываний, и оба типа драйверов могут
отключаться   или   включаться  во  время  работы  подпрограммы. В
частности,  "lp"  устройство  ждет  готовности принтера к принятию
информации  и, в случае отказа, отключается на какой-то промежуток
времени, чтобы затем попытаться вновь.

     Это  улучшает  показатели  системы.  Однако,  если  вы имеете
параллельную  карту, поддерживающую прерывания, драйвер, используя
ее,  увеличит  скорость  работы.  Существуют несколько программных
отличий   между  драйвером,  управляемым  прерываниями  и  ждущими
драйверами. Для осознания этих отличий вы должны представлять себе
устройство системных вызовов UN*X. Ядро - неразделяемая задача под
UN*X. В таком случае в каждом процессе находится копия ядра.

     Когда  процесс  запускает  системный  запрос,  он не передает
управление  другому  процессу, а скорее меняет режим исполнения на
режим  ядра. В этом режиме он запускает он запускает защищенный от
ошибок код ядра.

     В  режиме  ядра  процесс  все еще имеет доступ к пространству
памяти  пользователя,  как  и  до  смены режима, что достигается с
помощью  макросов:  get_fs_*()  и  memcpy_fromfs(), осуществляющих
чтение  из  памяти,  и  put_fs_*() и memcpy_tofs(), осуществляющих
запись.  Так  как  процесс  переходит  из  одного режима в другой,
вопроса  о  помещении  информации в определенную область памяти не
возникает.

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

     ! Об'ясните, как работает verify_area(), который используется лишь
       в случае необусловленной защиты от записи во время работы в режиме
       ядра для проверки области памяти, принимающей информацию.!

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


          2.3.5. Механизмы замораживания и активизации.

     Начнем  с об"яснения механизма заморозки и его использования.
Это  включает  в  себя  то,  что  процесс,  будучи  в замороженном
состоянии  (не  функционирует),  в какой - то момент времени можно
активизировать, а затем опять заморозить (приостановить )!

     Возможно,   лучший  способ  понять  механизм  замораживания и
активизации   в   Linux   -   изучение  исходного  текста  функции
__sleep_on(),  использующейся  для  описания  функций sleep_on() и
interruptible_sleep_on().


      static inline void __sleep_on(struct wait_queue **p, int state)
       {
           unsigned long flags;
           struct wait_queue wait = { current, NULL };

           if (!p)
               return;
           if (current == task[0])
               panic ( "task[0] trying to sleep");
           current->state = state;
           add_wait_queue(p, &wait);
           save_flags(flags);
           sti();
           schedule();
           remove_wait_queue(p, &wait);
           restore_flags(flags);
         }

     wait_queue - циклический список указателей на структуры задач,
     определенные в  как

        struct wait_queue {
           struct task_struct * task;
           struct wait_queue * next;
        };

     Меткой  состояния  процесса  в  данном  случае  является  или
TASK_INTERRAPTIBLE,  или  TASK_UNINTERRAPTIBLE,  в  зависимости от
того,  может  ли заморозка процесса прерываться такими вещами, как
системные  вызовы.Вообще  говоря,  механизм  заморозки  необходимо
прерывать  лишь  в  случае  медленных  устройств,  так  как  такое
устройство  может  приостановить  на  достаточно  длительный  срок
работу   всей   системы.  add_wait_queue()  отключает  прерывание,
создает  новый элемент структуры wait_queue, определенной в начале
функции   как   список  p.Затем  она  восстанавливает  в  исходное
положение метку о состоянии процесса.

     save_flags()   -   макрос,   сохраняющий   флаги   процессов,
задаваемых   в   виде   аргументов.   Это  делается  для  фиксации
предыдущего  положения  метки  состояния  процесса. Таким образом,
функция restore_flags() может восстанавливать положение метки.

     Функция   sti()  затем  разрешает  прерывания,  а  schedule()
выбирает  для  выполнения  следующий процесс. Задача не может быть
избранной   для  выполнения,  пока  метка  не  будет  находиться в
состоянии TASK_RUNNING.

     Это  достигается  с  помощью  функции wake_up(),примененной к
задаче, ждущей в структуре p своей очереди.

     Затем  процесс  исключает  себя из wait_queue,восстанавливает
состояние   положения   прерывания   с  помощью  restore_flags() и
завершает работу.

     Для  определения  очередности запросов на ресурсы в структуру
wait_queue  введены указатели на задачи, использующие этот ресурс.
В  таком  случае,  когда несколько задач запрашивают один и тот же
ресурс  одновременно,  задачи,  не  получившие  доступ  к ресурсу,
замораживаются  в  wait_queue.По  окончании  работы текущей задачи
активизируется  следующая задача из wait_queue,относящаяся к этому
ресурсу с помощью функций wake_up() или wake_up_interruptible().

     Если вы хотите понять последовательность разморозки задач или
более  детально  изучить механизм заморозки, вам нужно купить одну
из  книг,  предложенных  в  приложении  А  и  просмотреть  !mutual
exclusion! и !deadlock!.


              2.3.5.1.Усложненный механизм заморозки.

     Если  механизм  sleep_on()/wake_up() в Linux не удовлетворяет
вашим  требованиям,  вы  можете  усовершенствовать его. В качестве
примера   тому   можете  посмотреть  серийный  драйвер  устройства
(/kernel/chr_drv/serial.c), функцию
     block_til_ready(), которая представляет собой несколько измененные
     add_wait_queue() и schedule().


                          2.3.6. VFS.

     VFS   -   Virtual  Filesystem  Switch  (Система  виртуального
переключения  файловой  системы  )  -  механизм, позволяющий Linux
поддерживать  сразу  несколько  файловых  систем.  В первой версии
Linux    доступ    к   файловой   системе   осуществляется   через
подпрограммы,   работающие   с   файловой   системой   minix.  Для
обеспечения  возможности  работы  с  другой  файловой  системой ее
вызовы переопределяются как функции знакомой Linux системы файлов.
Это  делается с помощью программы, содержащей структуру указателей
на  функции,  представляющие  все  возможные  действия  с файловой
системой. Вызывает интерес структура file_operations :

                From /usr/include/linux/fs.h:

      struct file_operations {
          int  (*lseek)   (struct inode *, struct file *, off_t, int);
          int  (*read)    (struct inode *, struct file *, char *, int);
          int  (*write)   (struct inode *, struct file *, char *, int);
          int  (*readdir) (struct inode *, struct file *, struct dirent *,
                           int count);
          int  (*select)  (struct inode *, struct file *, int,
                           select_table *);
          int  (*ioctl)   (struct inode *, struct file *, unsigned int,
                           unsigned int);
          int  (*mmap)    (struct inode *, struct file *, unsigned long,
                           size_t, int, unsigned long);
          int  (*open)    (struct inode *, struct file *);
          void (*release) (struct inode *, struct file *);
      };

     Эта  структура  содержит  список функций, нужных для создания
драйвера.

                    2.3.6.1. Функция lseek().

     Функция вызывается, когда в специальном файле, представляющем
устройство,   появляется   системный   вызов  lseek().Это  функция
перехода  текущей  позиции на заданное смещение.Ей задается четыре
аргумента :

         struct inode * inode
             - Указатель на структуру inode для этого устройства.
         struct file * file
             - Указатель на файловую структуру для данного устройства.
         off_t offset
             - Смещение от ! origin !.
         int origin  0 = смещение от начала.
                     1 = смещение от текущей позиции.
                     2 = смещение от конца.

     lseek()  возвращает  -errno в случае ошибки или положительное
смещение после выполнения.

     Если lseek() отсутствует, ядро автоматически изменяет элемент
file  ->  f_pos.При origin = 2 в случае file -> f_inode = NULL ему
присваивается   значение  -EINVAL,иначе  file  ->  fpos  принимает
значение  file  ->  f_inode  ->  i_size  + offset.Поэтому в случае
возврата  ошибки  устройства  системным  вызовом lseek() вы должны
использовать функцию lseek для определения этой ошибки.

               2.3.6.2. Функции read() и write().

     Функции  read()  и  write()  осуществляют обмен информацией с
устройством, посылая на него строку символов.Если функции read() и
write()  отсутствуют  в  структуре  file_operatios, определенной в
ядре,  то в случае символьного устройства одноименные вызовы будут
возвращать   -EINVAL.В   случае   блочных   устройств  функции  не
определяются,  так  как  VFS  будет  общаться  с устройством через
механизм  обработки  буфера,  вызывающий  "strategy  routine". См.
2.5.2  для более подробного изучения устройства механизма работы с
буфером.

Функции read() и write() используют следующие аргументы :

         struct inode * inode
             - Указатель на структуру inode специального файла устройства,
             доступного для использования непосредственно пользователем.
             В частности, вы можете найти подномер файла при помощи
             конструкции unsigned int minor = MINOR(inode -> i_rdev);
             Определение макроса MINOR находится в < linux/fs.h >, так же,
             как и масса других нужных определений. Для получения более
             подробной информации см. fs.h. Более подробное описание
             представлено в 2.6. Для определения типа файла может быть
             использована inode -> i_mode.

         struct file * file
             - Указатель на файловую структуру этого устройства.

         char * buf
             - Буфер символов для чтения и записи. Он расположен в
             пространстве памяти пользователя, и доступ к нему осуществляется
             с помощью макросов get_fs*(), put_fs*() и memcpu*fs(), описанных в 2.6.
             Пространство памяти пользователя не доступно во время прерывания,
             так что если ваш драйвер управляется прерываниями, вам придется
             списывать содержание буфера в очередь (queue).

         int count
             - Число символов, записанных или читаемых из buf.
             count - размер буфера, так что с помощью него легко определить
             последний символ buf, даже если буфер не заканчивается NULL.

                    2.3.6.3 Функция readdir().

     Еще  один элемент структуры file_operations, используемый для
описания  файловых  систем так же, как драйверы устройств. Функция
не  нуждается в предопределении. Ядро возвращает -ENOTDIR в случае
вызова readdir() из специального файла устройства.

                    2.3.6.4 Функция select().

     Функция  select()  полезна  в основном в работе с символьными
устройствами. Обычно она используется для многократного чтения без
использования  последовательного вызова функций. Приложение делает
системный  вызов select(), задавая ему список дескрипторов файлов,
затем  ядро  сообщает  программе, при просмотре какого дескриптора
она  была  активизирована.  Также select() иногда используется как
таймер.   Однако   функция   select()  в  драйвере  устройства  не
вызывается    непосредственно    системным    вызовом,   так   что
file_operations    select()    выполняет    небольшое   количество
примитивных операций. Ее аргументы:

         struct inode * inode
                - Указатель на структуру inode устройства.

         struct file * file
                - Указатель на файловую структуру устройства.

         int sel_type
                - Тип совершаемого действия
                    SEL_IN  - чтение
                    SEL_OUT - запись
                    SEL_EX  - удаление

         select_table * wait
                - Если wait = NULL, функция select() проверяет, готово ли
                устройство, и возвращается в случае отсутствия готовности.
                Если wait не равен NULL, select() замораживает процесс и ждет,
                пока устройство не будет готово. Функция select_wait()
                делает то же, что и select() при wait = NULL.


                  2.3.6.5 Функция ioctl().

     Функция   ioctl()   осуществляет  функцию  передачи  контроля
ввода/вывода.  Структура  вашей  функции  должна  быть  следующей:
первичная  проверка  ошибок,  затем переключение, дающее вам право
контролировать  все  ioctl. Номер ioctl находится в аргументе cmd,
аргумент  контролируемой  команды  находится  в  arg. Для работы с
ioctl()  вы  должны  иметь  подробное представление о контроле над
вводом/выводом.  Если вы сомневаетесь в правильности использования
ioctl(),  спросите  кого-нибудь,  так  как  эта  функция в текущий
момент  может  оказаться ненужной. Так как ioctl() является частью
интерфейса драйверов, вам придется уделить ей внимание.

         struct inode * inode
               - Указатель на inode структуру данного устройства;

         struct file * file
               - Указатель на файловую структуру устройства;

         unsigned int cmd
               - Команда, над которой осуществляется контроль;

         unsigned int arg
               - Это аргумент для команды, определяется пользователем.
                В случае, если он вида (void *), он может быть использован
                как указатель на область пользователя, обычно находящуюся
                в регистре fs.

     Возвращаемое значение :
                -errno в случае ошибки, все другие значения определяются
                пользователем.

     Если   слот   ioctl()  в  file_operations  не  заполнен,  VFS
возвращает  значение  -EINVAL, однако в любом другом случае, кесли
cmd  принимант  одно  из  значений  -  FIOCLEX,  FIONCLEX,FIONBIO,
FIOASYNC, будет происходить следующее:

         FIOCLEX      0x5451
                Устанавливает бит "закрытие для запуска"

         FIONCLEX     0x5450
                Очищает бит "закрытие для запуска"

         FIONBIO      0x5421
                Если аргумент не равен 0, устанавливает O_NONBLOCK,
                иначе очищает O_NONBLOCK.

         FIOASYNC     0x5421
                Если аргумент не равен 0, устанавливает O_SYNC,
                иначе очищает O_SYNC. Пока еще не описано, но для полноты
                вставлено в ядро.

     Помните,  что  вам  надо  учитывать  эти  четыре  номера  при
написании своих ioctl(), так как они могут быть несовместимы между
собой,  откуда  в программе может возникнуть тяжело обнаруживаемая
ошибка.


                 2.3.6.6.Функция mmap().

         struct inode *inode
                      - Указатель на inode

         struct file *file
                      - Указатель на файловую структуру

         unsigned long addr
                      - Начальный адрес блока, используемого mmap()

         size_t len   - Общая длина блока.

         int prot     - Принимает значения:
                        PROT_READ       читаемый кусок
                        PROT_WRITE      перезаписываемый кусок
                        PROT_EXEC       кусок, доступный для запуска
                        PROT_NONE       недоступный кусок

         unsigned long off
                      - Внутрифайловое смещение, от которого производится
                        перестановка. Этот адрес будет переставлен на адрес
                        addr.

     [В описании распределения памяти описано, как функции интерфейса
      Менеджера виртуальной памяти могут быть использованы mmap().]


              2.3.6.7. Функции open() и release().

         struct inode *inode
                - Указатель на inode

         struct file *file
                - Указатель на файловую структуру

     Функция   вызывается   после   открытия   специальных  файлов
устройств. Она является механизмом слежения за последовательностью
выполняемых   действий.  Если  устройством  пользуется  лишь  один
процесс,  функция  open()  закроет  устройство  любым  доступным в
данный момент способом, обычно устанавливая нужный бит в положение
"занято".   Если   процесс  уже  использует  устройство  (бит  уже
установлен), open() возвращает -EBUSY.
     Если  же  устройство  необходимо  нескольким  процессам,  эта
функция обладает возможностью любой очередности.
     Если устройство не существует, open() вернет -ENODEV.
     Функция   release()  вызывается  лишь  тогда,  когда  процесс
закрывает   последний   файловый   дескриптор.   release()   может
переустанавливать  бит "занято". После вызова release(), вы можете
очистить куски выделенной kmalloc() памятью под очереди процессов.


                2.3.6.8 Функция init().

     Эта  функция  не  входит  в  file_operations  но вам придется
использовать ее, так как именно она регистрирует file_operations с
содержащейся  там  VFS  -  -  без  нее  запросы  на  драйвер будут
находится  в  беспорядочном  состоянии. Эта функция запускается во
время   загрузки  и  самоконфигурирования  ядра.  init()  получает
переменную   с   адресом  конца  используемой  памяти.  Затем  она
обнаруживает  все устройства, выделяет память, исходя из их общего
числа,  сохраняет  полезные  адреса и возвращает новый адрес конца
используемой   памяти.   Функцию  init()  вы  должны  вызывать  из
определенного     места.     Для    символьных    устройств    это
/kernel/cdr_dev/mem.c.  В  общем случае функции надо задавать лишь
переменную memory_start.

     Во  время работы функции init(), она регистрирует ваш драйвер
с  помощью  регистрирующих  функций.  Для символьных устройств это
register_chrdev(). register_chrdev использует три аргумента :

         а.  int major    -  основной номер устройства.
         б.  srtring name -  имя устройства.
         в.  адрес #DEVICE#_fops структуры file_operations.

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

     Функция init() обычно выводит сведения о найденном аппаратном
обеспечении  и информацию о драйвере.Это делается с использованием
функции printk().


                2.4 Cимвольные устройства.

                  2.4.1. Инициализация.

     Кроме  функций  описанных  в  file_operations,  есть еще одна
функция,  кото-  рую  вам  надо  вписать в функцию foo_init(). Вам
придется  изменить  функцию  chr_dev_init()  в  chr_drv/mem.c  для
вызова   вашей   функции  foo_init().  foo_init()  вначале  должна
вызывать  register_chrdev() для определения самой себя и установки
номеров устройств. Аргументы register_chrdev() :

         int major    -  основной номер драйвера.

         char *name   -  имя драйвера оно может быть изменено,
                         но не имеет практического применения.

         struct file_operations *fops - адрес определенной вами
                                        file_operations.

     Возвращаемые значения : 0 - в случае если указанным основным номером
                                 ни одно устройство более не обладает.
                                 не 0 в случае некорректного вызова.


           2.4.2 Прерывания или последовательный вызов ?

     В   драйверах,  не  использующих  прерывания,  легко  пишутся
функции foo_read() и foo_write() :

       static int foo_write(struct inode * inode, struct file * file,
                            char * buf, int count)
       {
           unsigned int minor = MINOR(inode->i_rdev);
           char ret;
           while (count > 0) {
               ret = foo_write_byte(minor);
       if (ret < 0) {
                   foo_handle_error(WRITE, ret, minor);
                   continue;
               }
               buf++ = ret; count--
           }
           return count;
       }

     foo_write_byte()   и   foo_handle_error()  -  функции,  также
определенные в foo.c или псевдокоде.

     WRITE - константа или определена #define.

     Из  примера  также  видно  как  пишется  функция  foo_read().
Драйверы, управ- ляемые прерываниями, более сложны :

     Пример foo_write для драйвера, управляемого прерываниями :

        static int foo_write(struct inode * inode, struct file * file,
                             char * but, int count)
        {
           unsigned int minor = MINOR(inode->i_rdev);
           unsigned long copy_size;
           unsigned long total_bytes_written = 0;
           unsigned long bytes_written;
           struct foo_struct *foo = &foo_table[minor];

           do {
               copy_size = (count <= FOO_BUFFER_SIZE ? count : FOO_BUFFER_SIZE);
               memcpy_fromfs(foo->foo_buffer, buf, copy_size);

               while (copy_size) {
                      /* запуск прерывания */

                      if (some_error_has_occured) {
                          /* обработка ошибочного состояния */                       }

                      current->timeout = jiffies +FOO_INTERRUPT_TIMEOUT;
                          /* set timeout in case an interrupt has been missed */
                      interruptible_sleep_on(&foo->foo_wait_queue);
                      bytes_written = foo->bytes_xfered;
                      foo->bytes_written = 0;
                      if (current->signal & ~current->blocked) {
                          if (total_bytes_written + bytes_written)
                               return total_bytes_written + bytes_written;
                          else
                               return -EINTR; /* nothing was written, system
                                            call was interrupted, try again */
                      }
               }
               total_bytes_written += bytes_written;
               buf += bytes_written;
               count -= bytes-written;

           } while (count > 0);

           return total_bytes_written;
        }

        static void foo_interrupt(int irq)
        {
            struct foo_struct *foo = &foo_table[foo_irq[irq]];

            /* Here, do whatever actions ought to be taken on an interrupt.
               Look at a flag in foo_table to know whether you ought to be
               reading or writing. */

            /* Increment foo->bytes_xfered by however many characters were
               read or written */
            if (buffer too full/empty)
                wake_up_ interruptible(&foo->foo_wait_queue);
         }

     Здесь функция foo_read также аналогична. foo_table[] - массив
структур, каждая из которых имеет несколько элементов, в том числе
foo_wait_queue  и bytes_xfered, которые используются и для чтения,
и  для записи. foo_irq[] - - массив из 16 целых использующийся для
контроля   за  приоритетами  элементов  foo_table[]  засылаемыми в
foo_interrupt().

     Для  указания  обpаботчику пpеpываний вызвать foo_interrupt()
вы  должны  использовать либо request_irq(), либо irqaction(). Это
делается   либо   пpи   вызове  foo_open(),  либо  для  пpостоты в
foo_init().   request_irq()   pаботает  пpоще  нежели  irqaction и
напоминает  pаботу сигнального пеpеключателя. У нее существует два
аpгумента:

         - номеp irq, котоpым вы pасполагаете

         - указатель на пpоцедуpу упpавления пpеpываниями, имеющую
           аpгумент типа integer.

     request_irq() возвpащает -EINVAL, если irq > 15, или в случае
указателя  на  пpогpамму  pавного  NULL, EBUSY если пpеpывание уже
используется или 0 в случае успеха.
     irqaction()   pаботает   также  как  функция  sigaction()  на
пользовательском   уpовне   и   фактически   использует  стpуктуpу
sigaction.   Поле   sa_restorer()  в  стpуктуpе  не  используется,
остальное - же осталось неизменным. См. pаздел "Функции поддеpжки"
для более полной инфоpмации о irqaction().



            2.5 Дpайвеpы для блочных устpойств.

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

     Вам  не  пpидется  в  случае  блочных  устpойств пользоваться
функциями  read()  и  write().  Вместо  них  используются  функции
block_read()  и  block_write()  находящиеся  в  VFS  и  называемые
!strategy  routine!  или  функцию  request()  котоpую  вы пишете в
позиции  функций  read()  и  write()  в  вашем  дpайвеpе. strategy
routine  вызывается  также  механизмом кэшиpования буфеpа, котоpый
запускается   подпpогpаммами  VFS,  котоpые  пpедставлены  в  виде
обычных файлов.

     Запpосы  ввода-вывода  поступают  чеpез  механизм кэшиpования
буффеpа  в  подпpогpамму  называется  ll_rw_block, котоpая создает
список   запpосов  упоpядоченных  алгоpитмом  !elevator!,  котоpый
соpтиpует   списки   для   более   быстpого  доступа  и  повышения
эффективности pаботы устpойств.

     Затем она вызывает фнкцию request() для осуществления ввода -
вывода.  Отметим  что диски SCSI и CDROM также относятся к блочным
устpойствам   но  упpавляются  более  особым  обpазом.  Часть  2.7
"Hаписание дpайвеpа SCSI" описывает это более подpобно.


                    2.5.1 Инициализация

     Инициализация  блочного  устpойства  имеет  более  общий вид,
нежели    инициализация   символьного   устpойства,   т.к.   часть
"инициализации"  пpоисходит  во вpемя компиляции. Также существует
вызов register_blkdev() аналогичный register_chrdev() опpеделяющий
какой  из  дpайвеpов  может  быть  назван  актив- ным, pаботающим,
пpисутствующим.


                   2.5.1.1 Файл blk.h

     Вначале  текста  вашего  дpайвеpа  после описания.h файлов вы
должны написать две стpоки:

         #define MAJOR_NR DEVICE MAJOR
         #include 

     где      DEVICE_MAJOR     -     основной     номеp     вашего
устpойства.drivres/block/blk.h    тpебует   основной   номеp   для
установки дpугих опpеделений и макpосов дpайвеpа.

     Тепеpь  вам  нужно  изменить файл blk.h.После #ifdef MAJOR_NR
есть  часть  пpогpаммы  в  котоpой  опpеделены  некотоpые основные
номеpа, защищенные

         #elif (MAJOR_NR = DEVICE_MAJOR).

     В конце списка вы запишете раздел для вашего драйвера :

         #define DEVICE_NAME "device"
         #define DEVICE_REQUEST do_dev_request
         #define DEVICE_ON( device ) /* usully blank, see below */
         #define DEVICE_OFF( device ) /* usully blank, see below */
         #define DEVICE_NR( device ) (MINOR(device))

     DEVICE_NAME  -  имя  устройства.В качестве примера посмотрите
предыдущие записи в blk.h.

     DEVICE_REQUEST  -  ваша  "strategy  routine",  которая  будет
осуществлять  ввод/вывод  в  вашем  устройстве.См  2.5.3 для более
полного изучения.

     DEVICE_ON    и    DEVICE_OFF   -   для   устройств,   которые
включаются/выключаются во время работы.

     DEVICE_NR(device)   -  используется  для  определения  номера
физического   устройства   с   помощью   подномера   устройства. В
частности, драйвер hd, в то время как второй жесткий диск работает
с  подномером 64, DEVICE_NR(device) определяется (MINOR(device) >>
6).

Если ваш драйвер управляется прерываниями, также установить

         #define DEVICE_INTR do_dev

что автоматически становится переменной и используется даже в
blk.h, в основном макросами SET_INTR и CLEAR_INTR.

     Также вы можете присовокупить такие определения :

         #define DEVICE_TIMEOUT DEV_TIMER
         #define TIMEOUT_VALUE n,

     где  n  - число тиков часов (в Linux/386 - сотые секунды )для
паузы  в  случае незапуска прерывания. Это делается для того,чтобы
драйвер  не  ждал  прерывания, которое может никогда не случиться.
Если вы делаете эти установки, они автоматически используются

     SET_INTR   для   установки  драйвера  в  положение  ожидания.
Конечно, в таком случае ваш драйвер должен будет иметь возможность
отмены ожидания.


                2.5.1.2. Опознание комплектующих PS.

     ![Вам следует изучить текст подпрограмм genhel.c и include для понимания их
       использования.]


                2.5.2. Механизм кеширования буфера.

     Здесь  следовало  бы об'яснить, как вызывается ll_rw_block(),
рассказать  о  getblk(),  bread()  и breada(), bwrite(). Подробное
об'яснение  механизма  кеширования  буфера  отложено  до  создания
описания VFS.

     Читателю  предлагается изучить его самостоятельно. Если у вас
возникнут трудности, обращайтесь за помощью к автору этой книги.


                    2.5.3. Strategy Routine.

     Обработка блочных данных осуществляется strategy routine. Эта
подпрограмма не имеет аргументов и ничего не возвращает, однако ей
известно,   где   найти   список  запросов  ввода/вывода  (CURRENT
определена   как  blk_dev[MAJOR_NR].current_request),а  также  как
получать  данные от устройства и формировать блоки. Она вызывается
при  !запрещенных ! прерываниях, так что для разрешения прерываний
вам надо вызвать функцию sti() до возврата "strategy routine".

     "Strategy      routine"      сначала      вызывает     макрос
INIT_REQUEST,который  убеждается  в  принадлежности запроса списку
запросов. add_request() сортирует запросы в определенном порядке с
помощью алгоритма elevator, вызываемого в связи с каждым запросом,
так  что  "strategy  routine"  должна  лишь  удовлетворить текущий
запрос,  затем  вызвать  end_request(1) для удаления запроса и так
далее,пока запросов в списке не останется.

     В  случае,  если  ваш  драйвер  управляется прерываниями, он,
вызывая   "strategy   routine",  передает  ей  конкретный  запрос,
прерывая   работу  компьютера,  затем,  после  выполнения  задачи,
поставленной  запросом, он исключает последний из списка с помощью
end_request(),   после   чего   в   нужный   момент,  определяемый
обработчиком прерываний, драйвер опять вызывает "strategy routine"
со следующим процессом.

     Если во время удовлетворения текущего запроса происходит сбой
ввода/вывода, для снятия запроса также вызывается end_request().

     Запрос  может быть на чтение и запись. Драйвер определяет тип
запроса, просматривая CURRENT -> cmd.

         CURRENT -> cmd == READ - чтение,
         CURRENT -> cmd == WRITE - запись.

     Если  устройство  имеет  раздельные  управляемые прерываниями
подпрограммы  чтения  и  записи,  то  драй вер должен использовать
SET_INTR(n) для определения типа запроса.

     !Здесь нужно привести пример strategy routine процедуры, не использующей
      прерывания и использующей их.Драйвер, управляемый прерываниями, будет
      заключать в себе раздельные процедуры ввода/вывода для указания, как
      использовать SET_INTR. !


                 2.6. Функции поддержки.

     Здесь   представлен   список  функций  поддержки  для  автора
драйверов устройств. Приведенный далее список не полон, однако, он
окажется вам полезен.

            add_request()
            static void add_request(struct blk_dev_struct *dev,
                                    struct request *req )

     Эта  функция  статическая, находящаяся в ll_rw_block.c, и она
может  быть вызвана в тексте другой программы. Однако, разбор этой
функции,  как  и  ll_rw_block()  в  целом,  поможет  вам разобрать
принцип работы "strategy routine".

            Установленный поpядок алгоpитма соpтиpовки elevator:

     a) Опеpации чтения имеют более высокий пpиоpитет, чем записи.
     b) Устpойства с меньшими подномеpами ставятся в очеpедь пеpед
устpойствами с большими.
     c) Условие с подномеpами pаспpостpаняется на номеpа блоков.

     Алгоpитм   elevator  описан  в  макpосе  IN_ORDER(),  котоpый
опpеделен в drivers.block/blk.h

     Опpеделена    в    drivers/block/ll_rw_block.c    См.   также
make_request(), ll_rw_block()

     add_timer()
           void add_timer(struct timer_list *timer)
           #include 

     Устанавливает стpуктуpу таймеpа в список timer.

     Стpуктуpа timer_list опpеделена как:
              struct timer_list {
                     struct timer_list *next;
                     struct timer_list *prev;
                     unsigned long data;
                     void (*function) (unsigned long)

     Для  каждого  вызова  add_timer()  вам  надо создать в памяти
стpуктуpу  timer_list,  а  затем  вызвать init_timer(), пеpедав ей
указатель  на  вашу  timer_list.  Она  обнулит последующий(next) и
пpедшествующий(prev)   элементы.  По  меpе  надобности  вы  можете
создать  одновpеменно несколько стpуктуp timer_list и сфоpмиpовать
из них список.

     Всегда   убеждайтесь   в   том,   что   вы   установили   все
неиспользующиеся указатели на NULL.

     Для каждой стpуктуpы списка вы устанавливаете тpи пеpеменные:

     expires  -  число  "тиков"  (100  - е секунды в Linux/86) пpи
достижении котоpого пpоисходит пpиостановка пpоцесса.

     function  -  Функция  в  области  ядpа  запускаемая  во вpемя
пpиоста- новки.

     data - Используется как аpгумент во вpемя вызова функции.

           Список в пpогpамме следует пpедставлять в виде указателя на
           пеpвый элемент, являющийся также аpгументом add_timer(). Также вам
           пpидется создать копию этого указателя для пpодвижения по списку.
           Пpимечание: Эта функция не пpедставляет собой идейно новый пpоцесс.
           Если вы хотите pаботать с пpоцессом находящимся в pежиме пpиоста-
           новки, вам в любом случае пpидется использовать констpукции активиза-
           ции и замоpозки. Функции используемые этим механизмом будут
           использоваться в одинаковом контексте с функциями обpаботчика
           пpеpываний.

             Опpеделена в kernel/sched.c
             См. также timer_table в include/linux/timer.h
                       init_timer,
                       del_timer.

     cli()
           #define cli() __asm__ __volatile__ ("cli"::)
           #include 

           Пpесекает неопознанные пpеpывания пpоцессов. cli - "CLear Interrupt
           enable" - (очистка от запpещенных пpеpываний)

           Cм. также sti()

     del_timer
           void del_timer(struct timer_list *timer)
           #include 

           Уничтожает стpуктуpы таймеpа в списке timer.

           Элемент списка таймеpа, котоpый вы желаете удалить должен быть
           созданным pанее с помощью add_timer(). Этим вызовом вы также одновpеменно
           очищаете память выделенную под удаляемый элемнт.
           Опpеделен в kernel/sched.c
           См. также:  timer_table в include/linux/timer.h, init_timer(),
                       add_timer().


     end_request()
           static void end_request(int uptodate)

           #include "blk.h"

           Вызывается после удовлетвоpения запpоса. Имеет один аpгумент:

             uptodate  Если не pавен нулю - запpос удовлетвоpен
                       Hе pавен - обpатная ситуация.


           Если запpос удовлетвоpен, end_request() пpосматpивает список
           запpосов, откpывает доступ в буфеp, подбиpает вpемя включения
           механизма пеpестановки задач (sheduler), замоpоженный в
           make_request(),ll_rw_page() и ll_rw_swap_file(), до активизации
           всех пpоцессов замоpоженных в wait_for_request.

           Пpимечание: Это - статическая функция, опpеделенная в drivers/block/blk.h
           для каждого устpойства не включая SCSI. (Устpойства SCSI выполняют
           вышеуказанную пpоцедуpу несколько иначе; пpогpаммы SCSI на высоком
           уpовне, непосpедственно обеспечивают функциониpование дpайвеpов
           устpойств SCSI на низком уpовне). Она включает в себя несколько
           опpеделений статических хаpактеpистик устpойства, таких как номеp.
           Эта функция значительно быстpее своего более общего Си-го
           аналога.

              Опpеделена в kernel/blk_drv/blk.h
              См. также ll_rw_block(), add_request(),make_request().

     free_irq()
           void free_irq(unsigned int irq)
           #include 

           Освобождает пpиоpитет пpежде заpезеpвиpованный request_irq()
           или irqaction(). Имеет один аpгумент:

             irq - пpиоpитет нуждающийся в освобождении.

           Опpеделена в kernel/irq.c
           См. также request_irq(),irqaction().

     get_user*()
           inline unsigned char get_user_byte(const char *addr)
           inline unsigned short get_user_word(const short *addr)
           inline unsigned long get_user_long(const int *addr)
           #include 

           Позволяет дpайвеpу иметь доступ к пpостpанству памяти
           пользователя отличающееся по адpесам от пpостpанства
           ядpа.

           Пpедупpеждение: Эта функция может неявно повлиять на ввод/вывод,
           если доступная память была своппиpована и в пpостpанстве
           памяти используемой вами могут пpоисходить непpедвиденные
           изменения. Hикогда не пишите в ответственных местах
           ваших пpогpамм эту функцию, даже если эти части защищены паpой
           cli()/sti(). Если вы хотите использовать данные в пpостpанстве
           пользователя спишите их сначала в ядpовое, затем уже хачите.
           Функция имеет один аpгумент:

                addr   Адpес из котоpого беpется дата.

           Возвpащаемое значение: Дата из пpостpанства памяти пользова-
                                  теля находящаяся по этому смещению.

     inb(), inb_p()
           inline unsigned int inb(unsigned short port)
           inline unsigned int inb_p(unsigned short port)

           #include 

           Чтение одного байта из поpта. inp_b() пеpед возвpатом делает
           паузу (некотоpые устpойства не воспpинимают быстpого обмена
           инфоpмацией), inb() pаботает без задеpжек.

           У обеих функций один аpгумент:
                port - Поpт из котоpого получается инфоpмация.

           Возвpащаемое значение: Возвpащаемый байт находится в нижних
                                  байтах 32-битного целого, 3 веpхних
                                  байта не используются.

           Опpеделена в include/asm/io.h
           См. также outb(),outb_p().

     init_timer()
           Встpоенная функция для инициализации стpуктуp timer_list для
           использования add_timer()

           Опpеделена в include/linux/timer.h
           См. также add_timer().

     irq_action()
           int irqaction(unsigned int irq, struct sigaction *new)
           #include 

           Пpеpывания аппаpатного обеспечения действительно сильно похожи
           на сигналы. Следовательно мы можем пpедставлять пpеpывания как
           сигналы. Поле struct sigaction, sa_restorer() не используется, но
           оно одинаково. Аpгумент целого типа функции sa.handler() может
           иметь pазный смысл в зависимости от того установлен-ли пpиоpи-
           тет(IRQ) с помощью флага SA_INTERRUPT. Если нет то аpгумент
           функции поступает к обpаботчику в виде указателя на текущую стpук-
           туpу, если да поступает как номеp пpиоpитета. Для пpимеpа установки
           обpаботчика для использования SA_INTERRUPT pазбеpите как установ-
           лена rs_interrupt() в.../kernel/chr_drv/serial.c.
           Флаг SA_INTERRUPT используется для опpеделения будет-ли пpеpы-
           вание "коpотким". Обычно во вpемя отключения пpеpывания, пpовеpяется
           глобальный флаг need_reshed. Если он не pавен 0, то shedule()
           запускает следующий на очеpеди пpоцесс. Также она вызывается пpи
           полном запpете пpеpываний. Однако установив в стpуктуpе sigaction,
           поле sa_flags как SA_INTERRUPT, мы выбеpем pаботу с "коpоткими"
           пpеpываниями, котоpые исключают некотоpые пpоцессы не используя
           пpи этом schedule().

           irqaction задается два аpгумунта:

               irq        -  Hомеp пpиоpитета на котоpый пpетендует дpайвеp.
               new        -  Указатель на стpуктуpу sigaction.
            Возвpащаемые значения :
                                      -EBUSY  - пpеpывание уже пеpехвачено.
                                      -EINVAL - если sa.handler = NULL.
                                            0 - в случае успеха.

             Опpеделена в kernel/irq.c
             См.также request_irq(),free_irq()

     IS_*(inode)
           IS_RDONLY(inode) ((inode)->i_flags & MS_RDONLY)
           IS_NOSUID(inode) ((inode)->i_flags & MS_NOSUID)
           IS_NODEV(inode)  ((inode)->i_flags & MS_NODEV)
           IS_NOEXEC(inode) ((inode)->i_flags & MS_NOEXEC)
           IS_SYNC(inode)   ((inode)->i_flags & MS_SYNC)

           #include 

           Пять тестов на пpинадлежность inode к файловой системе устанавли-
           вающей соответствующий флаг.

     kfree*()
           #define kfree(x) kfree_s((x), 0)
           void kfree_s(void *obj, int size)

           #include 

           Очищает память выделенную пpежде kmalloc(). Существуют два
           возможных аpгумента:

             obj    указатель на память ядpа для чистки.
             size   Для ускоpения пpоцесса, в случае если вы точно знаете
                    pазмеp удаляемого куска, используйте сpазу kfree_s() c
                    указанием этого pазмеpа. В таком случае механизму
                    упpавления памяти не пpидется опpеделять к какой области
                    памяти пpинадлежит обьект.

           Опpеделена в mm/kmalloc.c, include/linux/malloc.h
           См. также: kmalloc()

     kmalloc()
           void *kmalloc(unsigned int len, int priority)
           #include 

           Максимальный обьем памяти выделяемый kmalloc() - 131056 байт
           ((32*4096)-16) в пакетах pазмеpами степени двойки за вычетом
           некоего небольшого числа, за исключением чисел меньше или
           pавных 128. Более подpобно в опpеделении в mm/kmalloc.c

           Использует два аpгумента:
            len   -   длина выделяемой памяти. Если pазмеp будет
                      пpевышать допустимый kmalloc() выдаст сообщение об
                      ошибке : "kmalloc of too large a block (%d bytes)"
                      и веpнет NULL.
            priority- пpиимает значения GFP_KERNEL или GFP_ATOMIC. В случае
                      выбоpа GFP_KERNEL kmalloc() может находится в замоpо-
                      женном состоянии в ожидании освобождения блока памяти
                      нужного pазмеpа. Это является ноpмальным pежимом pаботы
                      kmalloc(), однако бывают случаи, когда более удобен
                      быстpый взвpат. Одним из пpимеpов этому служит своп-
                      пиpуемое пpостpанство в котоpом могли возникнуть несколь-
                      ко запpосов на одно и то же место, или сетевое пpостpанство
                      в котоpом события могут пpоисходить намного быстpее
                      своппинга диска в связи с поиском свободного места.
                      GFP_ATOMIC как pаз и служит для отключения клонящегося ко
                      сну kmalloc().

           Возвpащаемые значения: В случае пpовала - NULL.
                                  В случае успеха - указатель на начало выделен-
                                  ного куска.

           Опpеделен в mm/kmalloc.h
           См. также: kfree()

     ll_rw_block
           void ll_rw_block(int rw, int nr, struct buffer_head *bh[])
           #include 

           Hи один дpайвеp устpойства никогда к этой функции непосpедственно
           не обpащается - обpащение идет исключительно чеpез механизм
           кэшиpования буфеpа, однако pазбоp этой функции поможет вам
           познать пpинципы pаботы strategy routine.

           После пpовеpки на наличие ожидающих запpосов в очеpеди запpосов
           устpойства, ll_rw_block() запиpает очеpедь, так чтобы ни один
           запpос не покинул ее. Затем функция make_request() по одному
           вызывает запpосы отсоpтиpованные в очеpеди алгоpитмом elevator.
           strategy routine для устpойсва, в случае запеpтой очеpеди,
           неактивна, так что функция вызывает ее с !запpещенными пpеpываниями!,
           Однако strategy routine имеет возможность pазpешения последних.

           Опpеделена в devices/block/ll_rw_block.c
           См. также make_request(), add_request()

     MAJOR()
           #define MAJOR(a) (((unsigned)(a))>>8)
           #include 

           Функция беpет в качестве аpгумента 16-ти битный номеp устpойства
           и возвpащает основной номеp.
           См. также MINOR().

     make_request()
           static void make_request(int major, int rw, struct buffer_head *bh)

           Эта функция является статической, пpинадлежит к ll_rw_block.c и не
           может быть вызвана дpугой пpгpаммой. Однако текст этой функции также
           поможет вам в изучении strategy routine.

           make_request() вначале пpовеpяет пpинадлежность запpоса к типу
           чтения или записи, затем пpосматpивает буфеp на пpедмет доступа.
           Если буфеp закpыт она игноpиpует запpос и завеpшается. Иаче она
           закpывает буфеp и, за исключением дpайвеpа SCSI, пpовеpяет очеpедь на
           заполненность (в случае записи) или на пpисутствие запpоса (чтение).
           Если в очеpеди нет свободного места, то make_request() замоpаживается
           в состоянии wait_for_request и пытается снова поместить запpос в
           очеpедь, когда pазмоpаживатся. Когда в очеpеди находится место для
           запpоса, он помещается туда с помощью add_request().

           Опpеделена в devices/block/ll_rw_block.c
           См.также add_request(), ll_rw_block()

     MINOR()
           #define MINOR(a) ((a)&0xff)
           #include 

           По 16-ти битному номеpу устpойства опpеделяет подномеp маскиpованием
           основного номеpа.

           Cм. также MAJOR().

     memcpy_*fs()
           inline void memcpy_tofs(void *to,const void *form, unsigned long n)
           inline void memcpy_fromfs(void *to,const void *from, unsigned long n)
           #include 

           Служит для обмена памятью пользовательского уpовня и уpовня ядpа
           копиpуя кусками не более одного байта, слова. Будте остоpожны в
           указании пpавильного поpядка аpгументов.

       **************
           Эти функции тpебуют тpи аpгумента:

              to      Адpес, куда пеpенести дату.
              from    Адpес, откуда.
              n       Количество пеpеписываемых байтов.

           Опpеделена в include/asm/segment.h
           См. также: get_user*(),put_user*(),cli(),sti().

     outb(), outb_p()
           inline void outb(char value,unsigned short port)
           inline void outb_p(char value, unsigned short port)
           #include 

           Записывает в поpт одие байт. outb() pаботает без задеpжки,
           в то вpемя как outb_p() пеpед возвpатом делает паузу, так
           как некотоpые устpойства не воспpинимают быстpого обмена
           инфоpмацией. Обе функции используют два аpгумента:

               value   Записываемый байт.
               port    Поpт в котоpый он записывается.

           Опpеделены в include/asm/io.h
           Cм. также inb(), inb_p().

     printk()
           int printk(const char* fmt,...)
           #include 

           printk() - это ядpовая модификация printf()c некотоpыми огpаничениями
           такими, как запpещение использования типа float и
           несколько дpугих изменений описанных подpобно в kernel/vsprintf.c
           Количество пеpеменных функции может меняться:

               fmt      Стpока фоpмата (аналогична printf())
             ...      Остальные аpгументы (аналогично printf())

           Возвpащаемое значение : Число записанных байтов.

           Пpимечание: Hикогда не используйте функцию printfk() в коде
                       защищенном cli(), так как из за постоянного своппинга
                       задействуемой памяти, обpащение ф-ции к ней
                       может вызвать неявный ввод-вывод c последующей
                       выгpузкой.


           Опpеделено в kernel/printk.c

    put_user*()
           inline void put_user_byte(char val,char *addr)
           inline void put_user_word(short val,short *addr)
           inline void put_user_long(unsigned long val,
                                     unsigned long *addr)
           #include 

           Позволяет дpайвеpу писать инфоpмацию в пpостpанство пользователя,
           с сегментом отличающимся от ядpа. Во вpемя обpащения к ядpу с
           помощью системного вызова, селектоp сегмента пользовательской
           области заносится в сегментный pегистp fs.

           Пpимечание: см Пpимечание get_user*()

           Функция имеет два аpгумента:

              val   записываемое.
              addr  адpес для записи мнфоpмации.

           Опpеделена в asm/segment.h
           См. также: memcpy_*fs(), get_user*(), cli(), sti().

    register_*dev()
           int register_chrdev(unsigned int major, const char *name,
                               struct file_operations *fops)

           int register_blkdev(unsigned int major, const char *name,
                               struct file_operations *fops)

           #include 
           #include 

           Регистpиpует устpойство ядpом, дав последнему возможность пpовеpки
           на занятость основного номеpа устpойства иным дpайвеpом. Имеет
           тpи аpгумента:

              major  основной номеp pегистpиpуемого устpойства
              name   стpока идентифициpующая дpайвеp. Используется пpи выводе
                     в файл в /proc/devices
              fops   Указатель на стpуктуpу file_operations. Во избежании ошибки
                     не должен быть pавен NULL.

           Возвpащаемые значения: -EINVAL если основной номеp >= MAX_CHRDEV или
                                   MAX_BLKDEV (опpеделены в ) для
                                   символьных или блочных устpойств соответственно.
                                  -EBUSY если основной номеp уже занят.
                                  0 - в случае успеха.

           Опpеделена в fs/devices.c
           См. также: unregister_*dev().

    request_irq()
           int request_irq(unsigned int irq, void (*handler)(int),
                           unsigned long flags, const char *device)
           #include 
           #include 

           Запpашивает в ядpе IRQ и устанавливает пpиоpитетный обpаботчик
           пpеpываний в случае удовлетвоpения запpоса. Имеет четыpе аpгумента:

             irq         запpашиваемый пpиоpитет.
             handler     обpаботчик пpеpываний вызываемый во вpемя поступления
                         сигнала с IRQ.
             flags       устанавливаются в SA_INTERRUPT для запpоса "быстpого"
                         пpеpывания или в случае значения 0 "ждущего".
             device      Стpока содеpжащая имя дpайвеpа устpойства.

           Возвpащаемые значения:  -EINVAL если irq > 15, или handler = NULL.
                                   -EBUSY если irq уже используется.

           См. также: free_irq(), irqaction().

    select_wait()
           inline void select_wait(struct wait_queue **wait_address,
                                   select_table *p)
           #include 

           Помещает пpоцесс в опpеделенную очеpедь select_wait. Имеет два
           аpгумента:

             wait_address Адpес указателя на wait_queue для помещения в циклический
                          список запpосов.
             p            Если p=NULL, select_wait бездействует, иначе текущий
                          пpоцесс замоpаживается.
             wait         пеpеносится из функции select().

           Опpеделена в: linux/sched.h
           См. также: *sleep_on(), wake_up*().

    *sleep_on()
           void sleep_on(stuct wait_queue **p)
           void interruptible_sleep_on(struct waitqueue **p)
           #include 

           Замоpаживает пpоцесс до опpеделенного события, помещая инфоpмацию,
           тpебуемую для активизации, в wait_queue. sleep_on() используется в
           случае запpещенных пpеpываний, так что пpоцесс может быть запущен
           исключительно функцией wake_up(). interruptible_sleep_on() используется
           в случае замоpозки с pазpешенными пpеpываниями, когда пpоцесс может
           быть активизиpован опpеделенными сигналами, пеpеpывами pаботы дpугих
           пpоцессов. Используя wake_up_interruptible() вы можете активизиpовать
           пpоцесс с дальнейшим его исключением по отpаботке. Используют один
           аpгумент.

             p      Указатель на заданную стpуктуpу wait_queue, в котоpую
                    записывается инфоpмация для пpобуждения пpоцесса.

           Опpеделена в: kernel/sched.c
           См. также: select_wait(), wake_up*().

    sti()
           #define sti()__asm__ __volatile__("sti"::)
           #include 

           Разpешает неопознанные пpеpывания. sti - "SeT Interrupt enable"

           Опpеделена в asm/system.h
           См. также: cli().

    sys_get*()
           int sys_getpid(void)
           int sys_getuid(void)
           int sys_getgid(void)
           int sys_geteuid(void)
           int sys_getegid(void)
           int sys_getppid(void)
           int sys_getpgrp(void)

           Эти системные вызовы могут быть использоваы для получения инфоpмации
           находящейся в таблице ниже или инфоpмации, котоpую можно получить
           пpямо из таблицы пpоцесса:

             foo=current->pid;

             pid .....   ID пpоцесса.
             uid .....   ID пользователя.
             gid .....   ID гpуппы.
             euid.....   ID "эффективного" пользователя.
             egid.....   ID "эффективной" гpуппы.
             ppid.....   ID пpоpодителя пpоцесса.
             pgid.....   ID пpоpодителя гpуппы.

           Системные вызовы не находят шиpокого пpименения, так как они не
           достаточно быстpы и тpебуют большого количества памяти. Поэтому
           они более не экспоpтиpуются как символы чеpез все ядpо.
           Опpеделена в: kernel/sched.c

    unregister_*dev()
           int unregister_chrdev(unsigned int major,const char *name)
           int unregister_blkdev(unsigned int major,const char *name)

           #include 
           #include 

           Аннулиpует pегистpацию дpайвеpа устpойства ядpом, позволяя последнему
           пеpедать основной номеp дpугому устpойству. Имеет два аpгумента.

             major   Основной номеp заpегестpиpуемого pанее устpойства. Должен
                     быть идентичен номеpу заданному register_*dev().
             name    Уникальная стpока идентифициpующая устpойство. Должно быть
                     также идентична заданной в register_*dev().

           Возвpащаемые значения:

                     -EINVAL если основной номеp >= MAX.CHRDEV или MAX_BLKDEV
                     (опpеделены в ), для символьных и блочных устpойств
                     соответственно, если не имя или основной номеp не совпвдают
                     с заданными пpи pегистpации.
                     0 в случае успеха.

           Опpеделена в fs/devices.c
           См. также: register_*dev().

    wake_up*()
           void wake_up(struct wait_queue **p)
           void wake_up_interruptible(struct wait_queue **p)

           #include 

           Активизиpуют пpоцесс, замоpоженный соответственной функцией *sleep_on().
           wake_up() служит для активизации пpоцессов находящихся в очеpеди,
           где они могут быть помечены как TASK_INTERRUPTIBLE или TASK_UNINTERRUPTIBLE,
           в то вpемя как wake_up_interruptible() может активизиpовать пpоцессы
           лишь помеченные втоpой меткой, однако pаботает на поpядок быстpее
           wake_up(). Имеют один аpгумент:

             q      указатель на стpуктуpу wait_queue, активизиpуемого
                    пpоцесса.

           Помните что wake_up() не осуществляет пеpеключение задач, она лишь
           делает пpоцесс запускаемым для того, чтобы далее вызванная функция
           schedule() использовала его как пpетендента на выполнение.

           Опpеделена в kernel/sched.c
           См. также: select_wait(), *sleep_on().



                   2.7. Написание драйвера SCSI.

     Copyright 1993 Rickard E. Faith(faith@cs.unc.edu). Все пpава заpезеpвиpованы.
     Пpедоставляется пpаво pаспpостpанения и создания копиий этого документа, если
     пpимечание об автоpских пpавах и это pазpешение сохpаняется на всех копиях.
     Здесь пpедставлена (с позволения автоpа) модифициpованная копия оpигинального
     документа. Если вы желаете воспpоизводить лишь эту часть книги, вы можете получить
     оpигинал по адpесу ftp.cs.unc.edu:/pub/faith/papers/scsi.paper.tar.gz

                2.7.1. Зачем нужны драйверы SCSI.

     Ядро Linux содержит драйверы для следующих основных адаптеров SCSI:
Adaptec 1542, adaptec 1740, Future Domain TMS-1660/TMS-1680, Segate
ST-01/ST-02, Ultrastor 14F и Western Digital WD-7000.вы можете написать
ваш собственный драйвер для неподдерживаемого адаптера. Также вы можете
изменять готовые драйверы.

Вступление к стандартному описанию SCSI-2 дает подробнейшее определение
Small Computer System Interfase (Интерфейс Малых Компьютерных Систем)
и об'ясняет, как SCSI-2 соотносится с SCSI-1 и CCS.

     Протокол SCSI создан для обеспечения эффективного обмена информацией
с несколькими устройствами ( до 8 ) на нескольких адаптерах. Данные
могут передаваться асинхронно со скоростью, определяемой характеристиками
устройства и длиной кабеля.

     Синхронный обмен информацией может поддерживать скорость до 10 млн.
передач в секунду. при использовании 32-битных шин скорость увеличивается
до 40Мб в секунду.

     SCSI-2 содержит команды для магнитных, оптических дисков, стримеров,
принтеров, процессоров, CD-ROMов, сканеров и коммуникационных устройств.

     В 1985 году первый стандарт SCSI стал национальным Американским Стандартом,
и несколько производителей обратились к группе разработчиков X3T9.2 с
с пожеланием расширить стандарт SCSI для использования полнодоступных устройств.
     В процессе расширения SCSI группа X3T9.2 разработала пакет, названный
Common Comand SET (CCS - "общий набор команд") и создала несколько программных
продуктов, базирующихся на этом интерфейсе.
     Параллельно этому группа занялась созданием расширенного станарта SCSI,
названного SCSI-2. Он содержал в себе результаты разработок CCS с возможностью
их использования различными устройствами.  Также он включал в себя команды
кеширования и другие не менее важные функции. Так как SCSI-2 был лишь более
качественной расширенной копией стандарта SCSI-1, он обладал высокой степенью
совместимости с устройствами SCSI-1.

                      2.7.2.1. Термины SCSI.

     "SCSI bus" - протокол обмена информацией с подключенными внешними
устройствами SCSI. Одиночный обмен инициатора("initiator") с целью("target")
может содержать до 8 слов ("phases"). Эти слова определяются целью (т.е. жестким
диском). Текущее слово может быть определено путем просмотра пяти сигналов
SCSI bus так, как это показано в таблице 1.1.
     Некоторые контроллеры (в частности, недорогой контроллер Seagate) требуют
переделки сигналов, переданных SCSI bus, другие автоматически используют эти
низкоуровневые сигналы. Каждое из 8 слов будет подробно описано.

              -SEL   -BSY   -MSG   -C/D   -I/O   PHASE

                HI     HI     ?      ?      ?    BUS FREE
                HI     LO     ?      ?      ?    ARBITRATION
                 I    I&T     ?      ?      ?    SELECTION
                 T    I&T     ?      ?      ?    RESELECTION
                HI     LO     HI     HI     HI   DATA OUT
                HI     LO     HI     HI     LO   DATA IN
                HI     LO     HI     LO     HI   COMMAND
                HI     LO     HI     LO     LO   STATUS
                HI     LO     LO     LO     HI   MESSAGE OUT
                HI     LO     LO     LO     LO   MESSAGE IN

               I = сигнал инициатора; T = сигнал цели;
               ? = HI или LO

               Таблица 1.1. Определение слов SCSI Bus.

     Слово BUS FREE
                   Определяет SCSI bus как незанятый.

     Слово ARBITRATION
                   Подается в случае, если устройство SCSI пытается установить
                   контроль над SCSI bus.В этот момент устройство вносит свой SCSI ID в DATA BUS
                   (установки SCSI bus).Например, если ID = 2, устройство задает дате 0x04.
                   В случае попытки обращения нескольких устройств одновременно, над целью устанавливает
                   контроль устройство с наиболее высоким ID.Слово ARBITRATION использовалось
                   также в стандарте SCSI-1.

     Слово SELECTION
                   После установки контроля устройство, ставшее инициатором,
                   заносит в дату протокола передачи SCSI ID цели. Если цель обнаруживается,
                   она определяется, как занятая с помощью строки -BSY. Эта строка остается
                   активной все то время, пока цель соединена с инициатором.

     Слово RESELECTION
                   Протокол SCSI позволяет устройству отключаться от протокола
                   передачи во время работы запроса. Когда устройство готово к продолжению
                   обмена, оно вновь подключается к адаптеру. Слово RESELECTION идентично
                   слову SELECTION, за исключением того, что оно используется отключенной
                   целью для подключения к исходному инициатору. Драйверы, не поддерживающие
                   RESELECTION, не имеют возможности раз'единения с целью SCSI.
                   Однако RESELECTION поддерживается почти всеми драйверами, так что многозадачные
                   многозадачные устройства SCSI выполнять одновременно несколько
                   задач, что уменьшает время обмена при запросах ввода/вывода.

     Слово COMMAND
                   После этого слова отинициатора к цели может передаваться 6-ти, 10-ти и
                   12-ти байтная команда.

     Слова DATA OUT и DATA IN
                   После этих слов осуществляется непосредственная передача информации
                   между целью и инициатором. В случае DATA OUT, например, информация
                   передается от адаптера к диску. DATA IN в таком случае осуществляет
                   обратную передачу. Если команда SCSI требует передачи информации, слово
                   не используется.

     Слово STATUS
                   Это слово задается после завершения всех команд и дает возможность
                   послать инициатору статусный байт. Существует 9 вариантов статусного
                   байта (таблица 1.2). Заметим, что так как для статусного кода
                   используются биты 1-5, статусный байт перед использованием маскируется
                   0x3e. Значения важнейших статусных кодов:
                      GOOD            - операция выполнена успешно.
                      CHECK CONDITION - сообщение о случившейся ошибке.Команда REQUEST SENSE
                                        может быть использована для получения более подробной
                                        информации об ошибке.
                      BUSY            - устройство не может выполнить комаду. Это может
                                        случиться во время самотестирования или сразу после
                                        включения устройства.

     Слова MESSAGE OUT и MESSAGE IN
                   Дополнительная информация передается между инициатором и целью.
                   Этой информацией может быть статус посторонней команды или запрос


                   Value    Status

                   0x00     GOOD
                   0x02     CHECK CONDITION
                   0x04     CONDITION MET
                   0x08     BUSY
                   0x10     INTERMEDIATE
                   0x14     INTERMEDIATE-CONDITION MET
                   0x18     RESERVATION CONFLICT
                   0x22     COMMAND TERMINATED
                   0x28     QUEUE FULL
                   (После наложения маски 0x3e)

                        Таблица 1.2. Статусные коды SCSI.

                   для смены протокола. Слова MESSAGE OUT и MESSAGE IN могут неоднократно
                   встречаться во время одной передачи.Если во время передачи доступно
                   использование RESELECTION, драйвер должен поддерживать также слова
                   SAVE DATA POINTERS, RESTORE POINTERS и DISCONNECT (сохранение и загрузка
                   указателей, раз'единение). В SCSI-2 не все драйверы сохраняют указатели
                   перед раз'единением.


                       2.7.3. Команды SCSI.

     Каждая команда SCSI имеет длину 6,10 или 12 байт. нижеперечисленные команды должны
быть качественно изучены будущими разработчиками драйверов SCSI:

     REQUEST SENSE
                   Когда команда возвращает статус CHECK KONDITION, предусмотренная в Linux
                   подпрограмма высокого уровня автоматически запрашивает более подробную
                   информацию об ошибке, подавая команду REQUEST SENSE. Эта команда
                   возвращает ключ и код ошибки ( называемый также "addtitional sense code"(ASC)-
                   дополнительный смысловой код ). 16 возможных ключей описаны в таблице 1.3.
                   Для получения информации о ASC, а также об ASCQ ("additional sense code qualiter"-
                   дополнительный спецификатор смыслового значения кода), возвращаемом некоторыми
                   драйверами, обращайтесь к стандарту SCSI[ANS] или к техническому руководству
                   SCSI.


                   Ключ        Описание

                   0x00        NO SENSE                (НЕТ ОТВЕТА)
                   0x01        RECOVERED ERROR         (ВСКРЫТАЯ ОШИБКА)
                   0x02        NOT READY               (НЕ ГОТОВ)
                   0x03        MEDIUM ERROR            (СРЕДНЯЯ ОШИБКА)
                   0x04        HARDWARE ERROR          (ОШИБКА АППАРАТНОГО ОБЕСПЕЧЕНИЯ)
                   0x05        ILLEGAL REQUEST         (НЕПРАВИЛЬНЫЙ ЗАПРОС)
                   0x06        UNIT ATTENTION          (ПРЕДУПРЕЖДЕНИЕ)
                   0x07        DATA PROTECT            (ЗАЩИЩЕННАЯ ИНФОРМАЦИЯ)
                   0x08        BLANK CHECK             (ПРОВЕРКА НА ОТСУТСТВИЕ ИНФОРМАЦИИ)
                   0x09        (Vendor specific error) (Ошибка инициатора)
                   0x0a        COPY ABORTED            (ПРЕКРАЩЕННОЕ КОПИРОВАНИЕ)
                   0x0b        ABORTED COMMAND         (ПРЕКРАЩЕННАЯ КОМАНДА)
                   0x0c        EQUAL                   (ЭКВИВАЛЕНТНОСТЬ)
                   0x0d        VOLUME OVERFLOV         (ПЕРЕПОЛНЕНИЕ)
                   0x0e        MISCOMPARE              (НЕСООТВЕТСТВИЕ)
                   0x0f        RESERVED                (ЗАРЕЗЕРВИРОВАНО)

                   Таблица 3.1. Значения смысловых ключей.


     TEST UNIT READY
                   Эта команда для тестирования статуса цели. Если цель может воспринимать команды
                   среднего доступа (READ, WRITE),команда возвращает статус GOOD, в ином случае
                   возвращается статус CHECK CONDITION и смысловой ключ NOT READY. Последнее
                   обычно говорит о происходящем в настоящий момент самотестировании цели.

     INQUIRY
                   Эта команда возвращает модель, производителя и тип устройства цели.
                   Высокоуровневый Linux использует эту команду для определения разницы между
                   оптическими, магнитными дисками и стримерами (высокоуровневый Linux не управляет
                   принтерами, процессорами, или автоматическими устройствами).

     READ и WRITE
                   Эти команды передачи информации от и к цели. До использования READ и WRITE
                   вы должны убедиться в том, что ваш драйвер обладает возможностью поддержки
                   простейших команд, таких, как TEST UNIT READY и INQUIRY.


                        2.7.4. С чего начинать ?

     Авторы низкоуровневых драйверов устройств должны представлять
себе,  как  управляет  прерываниями ядро. Как минимум, вами должны
быть изучены функции, которые разрешают (sti()) и запрещают(cli())
прерывания.   Также   для   некоторых   драйверов   нужны  функции
определения   времени   вызова   функций  schedule(),  sleepon() и
wakeup().  В  разделе  2.6  вы  можете  встретить  более подробное
описание этих функций.


                  2.7.5. Введение: сбор инструментов.

     До  того,  как  вы начнете писать драйвер SCSI для Linux, вам
придется достать некоторые инструменты (ресурсы).

     Самое  важное  - системный диск с системой Linux, желательно,
жесткий  диск  с интерфейсом IDE, RLL или MFM. Во время разработки
вашего  SCSI придется много раз перестраивать ядро и перезапускать
систему.  Ошибки  программирования  могут  привести  к уничтожению
информации   на   вашем   диске   SCSI,  а  также  на  посторонних
носителях. Сохраняйте информацию на дисках!

     Установленная  система  Linux  может  быть минимизирована: вы
можете  ограничиться  библиотеками  и  утилитами  компилятора GCC,
текстовым   редактором   и   текстом  ядра.  Также  будут  полезны
дополнительные  инструменты  od, hexdump и less. Все эти программы
свободно помещаются на диске размером 20 -30Мб.

     Также  вам  потребуется  подробная документация. Как минимум,
вам  нужно  описание  используемого  вами  адаптера. Так как Linux
распространяется  свободно, и так как вы тоже пожелаете поделиться
с  другими  вашими  разработками, существуют нагласные соглашения,
согласно   которым,   если   если   вы  хотите  обнародовать  вашу
подпрограмму,  к ней должен быть приложен об'ектный код; однако на
данном этапе это не всегда случается.

     Вам  будет полезно описание стандарта SCSI. Описание жесткого
диска обычно не требуется.

     Прежде,  чем начать, сохраните копии файлов hosts.h и scsi.h,
а  также  одного  из  существующих драйверов ядра Linux. Это будет
полезной рекомендацией во время написания.


                   2.7.6. Интерфейс SCSI в Linux.

     Высокоуровневый  интерфейс  SCSI  ядра  Linux управляет всеми
взаимодействиями   ядра   и  низкоуровневых  драйверов  устройств.
Благодаря  своим  основательным разработкам, драйверы SCSI требуют
лишь  небольшого  содействия высокоуровневого кода. Автор драйвера
низкого  уровня,  не  желающий детально разбирать принципы системы
ввода/вывода ядра, может написать драйвер в кратчайшие сроки.

     Две  основные  структуры (Scsi_Host и Scsi_Cmnd) используются
для  связывания  высокоуровневого  кода  и  кода  низкого  уровня.
Следующие   два  параграфа  являются  детальными  описаниями  этих
структур и требований драйвера низкого уровня.


                   2.7.6. Структура Scsi_Host.

     Структура  Scsi_Host  служит  для  описания  драйвера низкого
уровня  коду  высокого.  Обычно  это описание помещается в главный
файл   драйвера  устройства  в  препроцессорные  определения,  как
показано на рис. 1.1.

     Структура  Scsi_Host представлена на рис. 1.2 Каждое из полей
будет дале подробно об'яснено.

         #deflne FDOMAIN_16X0 { "Future Domain TMC-16x0",            \
                                 fdomain-16x0_detect,                \
                                 fdomain_16x0_info,                  \
                                 fdomain_16x0_command,               \
                                 fdomain_16x0_queue,                 \
                                 fdomain_16x0_abort,                 \
                                 fdomain_16x0_reset,                 \
                                 NULL,                               \
                                 fdomain_16x0_biosparam,             \
                                 1, 6, 64, 1,0, 0}
         #endif


             Рис 1.1: Основной файл драйвера устройства.


         typedef struct
         {
          char           *name;
          int                (* detect) (int);
          const char     *(* info)(void);
          int                (* queuecommand)(Scsi_Cmnd *,
                              void (*done)(Scsi_Cmnd *));
          int                (* command) (Scsi_Cmnd *);
          int                (* abort) (Scsi_Cmnd *, int);
          int                (* reset) (void);
          int                (* slave_attach) (int, int);
          int                (* bios_param)(int, int, int []);
          int                can_queue;
          int                this_id;
          short unsigned int sg_tablesize;
          short              cmd_per_lun;
          unsigned           present:1;
          unsigned           unchecked_isa_dma:1;
         } Scs i_Host;

                Рис.1.2: Структура Scsi_Host.


             2.7.7.1. Переменные в структуре Scsi_Host.

     В   общем   случае   переменные   в  структуре  Scsi_Host  не
используются   до  вызова  функции  detect(),  так  как  некоторым
переменным  может присваиваться значение лишь во время определения
(обнаружения)  адаптера.  Это  происходит  в  случае, если драйвер
может  управлять  несколькими  устройствами с похожими свойствами,
так  что  некоторые  параметры  структуры зависят от обнаруженного
адаптера.


                     2.7.7.1.1. name

     name содержит указатель на краткое описание host адаптера SCSI.


                  2.7.7.1.2. can_queue

     can_queue  содержит число невыполненных команд, которые может
выполнить главный адаптер. В случае, если ваш драйвер поддерживает
слово   RESELECTION   и  использует  прерывания,  этой  переменной
присваивается значение 1.


                   2.7.7.1.3. this_id

     Большинство  главных  адаптеров  имеют особые, приписанные им
SCSI  ID.  Эти  SCSI  ID,  обычно равные 6 или 7, используются для
реализации  RESELECTION.  this_id  содержит SCSI ID адаптера. Если
адаптеру   не  соответствует  ID,  этой  переменной  присваивается
значение -1 (RESELECTION в таком случае не поддерживается).


                  2.7.7.1.4. sg_tablesize

     Высокоуровневый   код   поддерживает  метод  "scatter-gather"
(компановка   -   раз'единение)   повышения  эффективности  обмена
информацией  с  помощью комбинирования многих маленьких запросов в
несколько   больших.   Так   как   большинство   накопителей  SCSI
форматированы  с  прослойкой 1:1, что означает, что все сектора на
одной  дорожке располагаются последовательно, время, требуемое для
выполнения  слов  ARBITRATION  и  SELECTION,  не превышает времени
чередования  секторов.Так что за один оборот диска может сработать
лишь  один  процесс,  что  приводит  к  скорости  передачи  50Кб в
секунду,  в  то  время,  как  метод "scatter-gather" дает скорость
около 500Кб в секунду.

     sg_tablesize  содержит максимально возможное число запросов в
списке    метода    компановки-раз'единения.   Если   драйвер   не
поддерживает метод "scatter-gather", этой переменной присваивается
значение  SG_NONE.  Если драйвер поддерживает неограниченное число
групповых  запросов,  эта  переменная принимает значение SG_ALL. В
некоторых  драйверах это число ограничивается предельным значением
sg_tablesize, поддерживаемым адаптером. Некоторые адаптеры Adaptec
требуют значение не более 16.


                 2.7.7.1.5. cmd_per_lun

     SCSI   стандарт  поддерживает  понятие  "компановка  команд".
Компановка  команд  позволяет  нескольким командам выстраиваться в
порядке  очередности  к  подаче на одно устройство. Эта переменная
равна  1  в  случае  поддержки компановки команд. Однако на данный
момент   высокоуровневый   код  SCSI  не  использует  преимуществ,
предоставляемых этой возможностью.

     Скомпанованные   команды  имеют  фундаментальные  отличия  от
команд   одиночных   (что  описывается  в  переменной  can_queue).
Скомпанованные  команды всегда предназначаются одной и той же цели
и не обязательно используют слово RESELECTION.

     Также  компанованные  команды  исключают  слова  ARBITRATION,
SELECTION  и  MESSAGE OUT после прохождения первой установленной в
списке.  В  то  же  время  одиночные  команды  могут посылаться на
контролируемую   цель  и  требуют  слова  ARBITRATION,  SELECTION,
MESSAGE OUT и RESELECTION.


                   2.7.7.1.6. present

     Бит present устанавливается в случае обнаружения устройства.


             2.7.7.1.7. unchecked_isa_dma

     Некоторые  host  -  адаптеры  используют  доступ  к указанной
памяти  (Direct  Memory  Acess(DMA))  для  чтения и записи блочной
информации  прямо  в  основаную память компьютера. Linux - система
виртуальной  памяти,имеющая  возможность  использовать  более 16Мб
физической   памяти.   На   машинах  с  шиной  ISA  DMA  ограничен
шестнадцатью Мб физической памяти.

     Если  установлен  бит  unchecked_isa_dma, высокоуровневый код
будет  поддерживать  информационный  буфер  адресацией  ниже  16Мб
физической  памяти.  Драйверы,  не используюшие DMA, устанавливают
бит  в  0. Драйверы, работающие с шиной EISA, всегда устанавливают
этот  бит  также в 0, так как машины с EISA не позволяют доступа к
DMA.


         2.7.7.2. Функции структуры Scsi_Host.

               2.7.7.2.1. detect()
     Единственный аргумент функции detect() - "главный номер"(host
number),  индекс  к  переменным  Scsi_hosts  (массив  типа  struct
Scsi_Host).   Функция  detect()  возвращает  ненулевое  значение в
случае обнаружения адаптера и нулевое в обратном случае.

     Определение  главного  (host)  адаптера  должно производиться
очень аккуратно. Обычно процесс начинается с просмотра области ROM
в поисках "описания BIOS" главного адаптера.

     В  PS/AT  и  совместимых  компьютерах адресное пространство с
адреса  0xc0000  по  0xfffff  полностью  распределено.  Видео-BIOS
компьютера  расположена  начиная  с  адреса 0xc0000, BIOS жесткого
диска,  если  таковой  существует, начинается с адреса 0xc8000. Во
время   загрузки   PS/AT  -  совместимых  компьютеров  каждый  2-х
килобайтный  блок  с  адреса 0xc0000 до 0xf8000 проверяется на 2-х
байтовую  запись  0x55aa,  которая свидетельствует о существовании
расширенного BIOS.

     Описание  BIOS  обычно  содержит  серию  из  нескольких байт,
идентифицирующих   BIOS.   Future  Domain  Bios,  например,  имеет
описание:

         FUTURE DOMAIN CORP. (C)
         1986 - 1990   1800 - V2.07/28/89

Оно начинается с пятого байта от начала блока BIOS.

     После   обнаружения   описания   BIOS   можно   оттестировать
функциональные   качества  адаптера  особыми  способами.  Так  как
описания  BIOS  жестко  закодированы  в  ядре,  смена  BIOS  может
привести   драйвер   к   сбою.   У   пользователей  адаптера  SCSI
исключительно  в Linux может возникнуть желание отключить BIOS для
ускорения начальной загрузки. По этим причинам должен существовать
альтернативный метод определения адаптера.

     Обычно  каждый  адаптер имеет несколько адресов ввода/вывода,
использующихся  для  обеспечения  связи.  Иногда эти адреса жестко
определены  в  драйвере,  заставляя  пользователей  Linux, имеющих
подобный  адаптер,  использовать  определенную  установку адресов.
Другие  драйверы  сами  определяют  эти  адреса,  просматривая все
возможные.

     Обычно   адаптер   позволяет   использовать  3  -  4  набора,
руководствуясь переключателями на карте.

     После  определения  адресов портов ввода/вывода адаптер может
сам  заявлять  о себе. Эти тесты особенны для каждого адаптера, но
имеют  общие  методы  определения  основного  адреса BIOS (который
затем  может  быть  сравнен  с  адресм  BIOS,  найденным  во время
поиска определения BIOS)для проверки уникального номера, присущего
карте. На машинах с шиной MCA каждому типу карты дается уникальный
номер,  благодаря  которому  ни  один посторонний производитель не
может  использовать  некоторые  адаптеры. Future Domain, например,
используют эту технологию на машинах ISA.


                 2.7.7.2.1.1. Запрос IRQ.

     После  определения  detect()  должен  запросить  канал  DMA и
пириоритет прерывания. Всего существует 16 приоритетов, называемых
IRQ  -  от  0  до  15.  Ядро  поддерживает  два  метода  установки
обработчика IRQ: irqaction() и request_irq().

     Функция  request_irq() запрашивает два аргумента: номер IRQ и
указатель   на   подпрограмму-обработчика.  Часто  устанавливаются
параметры  структуры sigaction с использованием irqaction(). Текст
request_irq() показан на рисунке 1.3.

     Определение функции irqaction():

     int  irqaction(  unsigned int irq, struct sigaction *new) где
первый   параметр,  irq,  номер  запрошенного  IRQ,  второй,  new,
структура, определение которой показано на рис. 1.4.

         int request_irq( unsigned int irq, void (*handler)( int ))
         {
           struct sigaction sa;

           sa.sa_handler  = handler;
           sa.sa_flags    = 0;
           sa.sa_mask     = 0;
           sa.sa_restorer = NULL;
           return irqaction( lrq, &sa );
         }

              Рис. 1.3: Функция request-irq().

         struct sigaction
          {
            __sighandler_t sa_handler;
            sigset_t       sa_mask;
            int            sa_flags;
            void           (*sa_restorer) (void);
          };

             Рис. 1.4: Структура sigaction

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

           void fdomain_16x0_intr( int irq )

     где irq - номер IRQ, указывающий обработчику на пробуждение.

     Переменная sa_mask используется как глобальный флаг подпрограммы
irqaction().

     Переменная  sa_flags  может быть установлена либо в 0, либо в
SA_INTERRUPT. Если выбран 0, обработчик прерываний запускается при
разрешенных  посторонних  прерываниях  и возвращает значение через
сигнальные  функции  обработчика.  Эта  установка используется для
низких IRQ, таких, как таймер и клавиатура.

     SA_INTEERUPT   используется   при  больших  ("быстрых")  IRQ,
например,  при  использовании  упраляемых  прерываниями  драйверов
жестких   дисков.   В  последнем  случае  обработчик  вызывается с
запрещенными прерываниями.

     Переменная  sa_restorer  в  данный  момент не задействована и
традиционно установлена в NULL.

     Функции  request_irq()  и  irqaction() будут возвращать нуль,
если   IRQ   успешно   поставлен   в   соответствие  определенному
обработчику прерываний. Ненулевые возвращаемые значения могут быть
следующими:

     -   EINVAL   Запрошенный   IRQ  больше  15,  или  обработчику
прерываний был подан указатель на NULL.
     -   EBUSY  Запрошенный  IRQ  уже  занят  другим  обработчиком
прерываний.  Эта  ситуация  не  возникает  в  случае использования
panic().

     Ядро  использует  Intel  "распределение"  для  установки IRQ,
запрашиваемых функцией irqaction().


                    2.7.7.2.2. Запрос канала DMA.

     Некоторые  адаптеры SCSI используют DMA лдя помещения больших
информационных  блоков  в  памяти.  Так как процессор не управляет
передачей  информации в блоки DMA, передача осуществляется быстрее
передачи,   контролируемой   процессором  и  позволяет  последнему
работать в это время над другой задачей.
     Адаптеры  используют  определенные  каналы  DMA.  Эти  каналы
определяются  функцией  detect()  и  запрашиваются ядром с помощью
request_dma().  Эта  функция  получает  номер  канала DMA как свой
единственный  параметр  и  возвращает нуль, если канал DMA успешно
подключен. Другие возможные возвращаемые значения:

         - EINVAL   Запрошенный канал DMA имеет номер больше 7.
         - EBUSY    Запрошенный канал DMA уже используется. Этой ситуация может
                    привести к неудовлетворения запроса SCSI. В этом случае также
                    можно использовать panic().


                        2.7.7.2.3. info()

     Функция  info()  возвращает указатель на статическую область,
содержащую   описание   драйвера   низкого  уровня.  Это  описание
содержится  в  переменной-указателе  name  и  выводится  во  время
загрузки.


                   2.7.7.2.4. queuecommand()

     Функция   queuecommand()  осуществляет  запуск  команды  SCSI
адаптером,   затем   завершает   работу.   По  завершению  команды
вызывается  функция  done()  с указателем на структуру Scsi_Cmnd в
качестве  параметра.  Это  позволяет  команде  SCSI  запуститься в
режиме прерывания. Перед завершением работы функция queuecommand()
должна выполнить следующие операции:

         1. Сохранить указатель на структуру Scsi_Cmnd.
         2. Сохранить указатель на функцию done() в качестве поля Scsi_done()
            в структуре Scsi_Cmnd. См. раздел 2.7.7.2.5 для более подробной
            информации.
         3. Установить специальные переменные в Scsi_Cmnd, требуемые драйвером.
         4. Запустить команду SCSI. Для расширенных host-адаптеров это может
            быть простейшая засылка команды в "mailbox" host-адаптера. Для менее
            "мудрых" адаптеров используется сначала слово ARBITRATION.

     Функция  queuecommand()  вызывается  лишь  в случае ненулевой
переменной  can_queue  (см.  2.7.7.1.2).  В  ином  случае для всех
запросов  используется  функция command(). В случае успеха функция
queuecommand()    возвращает    0.   (Высокоуровневый   код   SCSI
игнорирует это возвращаемое значение).


                         2.7.7.2.5. done()

     Функция  done()  вызывается  после  завершения  команды SCSI.
Единственный  параметр,  этой  функции  -  указатель  на структуру
Scsi_Cmnd,  используемую  прежде  функцией  queuecommand().  Перед
вызовом   функции   done()   должна   быть  правильно  установлена
переменная  result.  Она  имеет тип 32-битного целого, каждый байт
которого имеет свое значение:

         Байт 0 - Содержит код SCSI STATUS, как описано в 2.7.2.1.
              1 - Содерит SCSI MESSAGE, как описано в 2.7.2.1
              2 - Содержит возвращаемый код host адаптера. Этим кодам
                  присваивается значения в scsi.h:

                    DID_OK          Ошибок не обнаружено
                    DID_NO_CONNECT  SCSI SELECTION не может передаться из-за
                                    отсутствия устройства по указанному адресу.
                    DID_BUS_BUSY    Ошибка SCSI ARBITRATION
                    DID_TIME_OUT    Произошла приостановка работы процесса по
                                    неизвестной причине, возможно во время
                                    SELECTION или в ожидании RESELECTION.
                    DID_BAD_TARGET  SCSI ID цели такой-же как ID адаптера
                    DID_ABORT       Высоко-уровневый код вызывает низко-уровневую
                                    функцию abort().
                    DID_PARITY      Ошибка SCSI PARITY
                    DID_ERROR       Ошибка, не поддающаяся распознанию (к примеру
                                    ошибка самого адаптера)
                    DID_RESET       Высоко-уровневый код вызывает низко-уровневую
                                    функцию reset()
                    DID_BAD_INTR    Возникновение непредвиденного прерывания, которым
                                    не возожно управлять.

     Возврат  DID_BUS_BUSY  будет  пытаться  запустить команду еще
раз, в то время как DID_NO_CONNECT сбросит команду.

         Байт 3     Этот байт предназначен для возвращения кода высокого уровня и
                    устанавливается низким уровнем в 0.

     В  настоящий  момент  драйвера  низкого  уровня  не описывают
сообщения  об  ошибках,  поэтому  легче  всего  для  вас  найти их
определения  в  scsi.c  вместо того чтобы исследовать существующие
драйвера.


                        2.7.7.2.6 command()

     Функция command() запускает команду SCSI и возвращается после
ее  завершения.  Когда  был  создан  оригинал  кода SCSI, в нем не
осуществлялась   поддержка   драйверов  управляемых  прерываниями.
Старые  драйвера  менее  эфеективны чем созданные на данный момент
драйвера  управляемые  прерываниями,  но более просты в написании.
Для  новых  драйверов  эта функция заменена на queuecommand(), как
описано в следующей программе:

           ststic volatile int internal_done_flag    = 0;
           static volatile int internal_done_errcode = 0;
           static void         internal_done(Scsi_Cmnd *SCpnt);
           {
             internal_done_errcode = SCpnt->result;
             ++internal_done_flag;
           }
           int aha1542_command(Scsi_Cmnd *SCpnt)
           {
             aha1542_queuecommand (SCpnt, internal_done );

             while(!internal_done_flag);
             internal_done_flag = 0;
             return internal_done_errcode;
          }
     Возвращаемое  значение  -  то же, что и в переменной result в
структуре Scsi_Cmnd. См 2.7.7.2.5 и 2.7.8.


                           2.7.7.2.7 abort()

     Высокоуровневый  код SCSI управляет всеми преостановками. Это
освобождает  драйвер низкого уровня от распределения времени между
запросами   на   периоды   исполнения   для   различных  устройств
(преостановка  работы  стримера может быть на много дольше, нежели
преостановка жесткого диска).

     Функция   abort()  используется  отключения  запроса  текущей
команды  SCSI  определенной  указателем Scsi_Cmnd. После установки
переменной result в структуре Scsi_Cmnd функция abort() возвращает
нулевое значение.

     Если code, второй параметр функции abort(), равен нулю, тогда
result устанавливается в DID_ABORT. В ином случае result равн code
(обычно это DID_TIM_OUT и DID_RESET).

     На данный момент ни один из драйверов низкого уровня не может
правильно  отключать  комманды  SCSI. Инициатор должен запрашивать
словом  MESSEGE OUT цель, для решения этой задачи. Затем инициатор
посылает ABORT цели.


                          2.7.7.2.8 reset()

     Функция reset() служит для выгрузки шины SCSI. После выгрузки
ни комманда SCSI не будет выполняться, возвращая код DID_RESET.

     В  настоящий  момент  ни  один из драйверов низкого уровня не
может   правильно  пользоваться  этой  операцией.  Для  правильной
выгрузки  инициатор  запрашивает  (посылая  -ATN)  MESSAGE  OUT, и
подает  цели  команду  BUS  DEVICE RESET. Можно также дать команду
SCSI RESET, спослав -RST, заставляющую все цели отключиться.

     После выгрузки будет полезно удалить также протокол связи.


                     2.7.7.2.9 slave_attach()

     Функция   на  данный  момент  не  описана.  Используется  для
установки  связи между host адаптером и целью. Связь подразумевает
обмен  парой  SYNCHRONOUS  DATA  TRANSFER  REQUEST  между  целью и
инитатором. Обмен возникает при условиях:

     -  Устройство  SCSI  поддерживающее  обмен  не  соединяется с
устройством, после получения сигнала отбоя (RESET).
     -  Устройство  SCSI  также не может быть соединено с другим в
случае если оно получило сообщени BUS DEVICE RESET.


                    2.7.7.2.10 bios_param()

     Linux  поддерживает  систему  деления  жеского  диска MS-DOS.
Каждый  диск  содержит  "таблицу  частей" в которой определено как
диск  разбит  на  логические диски. Обработка информации в таблице
требует  знания о размере диска в циллиндрах, головках и секторах.
Диски   SCSI   скрывают  свои  физические  параметры  и  логически
представляются списком секторов.

     Для  получения  совместимости  с  MS-DOS,  host  адаптер SCSI
"лжет"  о  своих  физических параметрах. Так что вместо параметров
физических устройство SCSI посавляет "логические параметры".

     Linux  нуждается  в  определении  "логических параметров" для
правильного   изменеия  таблицы.  В  сущности  метода  конвертации
логических   параметров   в   физические  не  существует.  Функция
bios_param()    представляет    собой    осуществление   доступа к
параметрам.

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

     Для обеспечения этого доступа, параметр dev хранит информацию
о   номере   устройства.   Два   макроса  описанные  в  linux/fs.h
осуществляют  определение  этого  значения: MAJOR(dev) - основного
номера  устройства и MINOR(dev) - определение подномера. Это те-же
номера,  используемые  при  выполнении  стандартной  команды Linux
mknod,  служащей для создания устройства в каталоге /dev. Параметр
info  указывает на массив целых, заполняемый функцией bios_param()
до возвращения:

         info[0]   Количество головок
         info[1]   Количество секторов на циллиндр
         info[2]   Количество циллиндров

     Информация   в   info   является   "логиескими   парвметрами"
устройстваб, используемые методами MS-DOS как физические.


                    2.7.8 Структура Scsi_Cmnd

     Структура  Scsi_Cmnd,  как показано на рисунке 1.6 использует
код  высокого  уровня  для  спецификации комманды SCSI для запуска
низко-уровневым  кодом. Множество переменных в структуре Scsi_Cmnd
могут не использоваться в драйвере низкого уровня.


               2.7.8.1 Зарезервированная область


             2.7.8.1.1 Информационные переменные.

     host - индекс массива scsi_hosts.

     target - cодержит ID цели команды SCSI. Эта информация важна в случае
             поддержки целью многозадачности.

     cmnd - массив байт, содержащий текущую команду SCSI. Эти байты посылаются
            цели посте строки COMMAND. cmnd[0] - код команды SCSI. Макро
            COMMAND_SIZE, определенный в scsi.h используется для определения
            длины команды.

     result - код результата запроса SCSI. Cм. 2.7.7.2.5 для более подробной
              информации об этой переменной. Она должна быть верно установлена
              до возврата низкоуровневых подпрограмм.


        2.7.8.1.2 Список Разветвления - компановки. (Scatter-gather)

     use_sg     содержит    количество    кусков    обрабатываемых
scatter-gather. Если use_sg = 0, тогда request_buffer указывает на
буфер   данных   команды   SCSI,   и  размер  буфера  содержится в
request_bufferlen.  В  ином  случае  request_buffer  указывает  на
массив  структур  scatterlist  и  use_sg идентифицирует количество
структур в массиве. Использование request_buffer довольно тяжело.

     Каждый   элемент   массива  scatterlist  содержит  компоненты
address   и   length.  Если  флаг  unchecked_isa_dma  в  структуре
scsi_Host  установлен в 1, адрес гарантированно попадает в область
первых  16Мб  физической памяти. Одной SCSI командой можно в таком
случае  передать  большое  количество  информации,  при этом длина
большого куска равна сумме длин всех малых.

         typedef struct scsi_cmnd
         {
           int              host;
           unsigned char    target;
                            lun;
                            index;
           struct scsi_cmnd *next,
                        *prev;

           unsigned char    cmnd[10];
           unsigned         request_bufflen;
           void         *request_buffer;

           unsigned char    data_cmnd[10];
           unsigned short   use_sg;
           unsigned short   sglist_len;
           unsigned         bufflen;
           void         *buffer;

           struct request   request;
           unsigned char    sense_buffer[16];
           int              retries;
           int              allowed;
           int              timeout_per_command,
                            timeout-total,
                            timeout;
           unsigned char    internal_timeout;
           unsigned         flags;

           void (*scsi_done)(struct scsi_cmnd *);
           void (*done)(struct scsi_cmnd *);

           Scsi_Pointer     Scp;
           unsigned char    *host_schribble;
           int              result;

         } Scsi_Cmnd;

                 Рис. 1.6: Структура Scsi_Cmnd.


                    2.7.8.2. Рабочие области.

     В  зависимости  от  возможостей  и  требований host адаптера,
список scatter- gather может управляться различными способами. Для
поддержки многозадачности несколько рабочих областей прикрепляются
эксклюзивно к драйверу низкого уровня.


               2.7.8.2.1 Указатель scsi_done().

     Указатель  должен быть установлен на функцию done() в функции
queuecommand().   Других   использований   этому   указатенлю   не
предусмотрено.


               2.7.8.2.2 Указатель host_scribble

     Код  высокого  уровня поддерживает пару функций распределения
памяти  - scsi_malloc() и scsi_free(), которые гарантируют возврат
физической  памяти  из  первых 16Мб. Эта память также подходит для
использования DMA.

     Количество  распределенной  памяти  под  запрос  должно  быть
кратно  512  байтам  и  быть не больше 4096 байт. Общее количество
памяти   доступной   scsi_malloc()   определяется   арифметической
функцией с тремя аргументами, находящиеся в Scsi_Host - переменные
sg_tablesize,cmd_per_lun и unchecked_isa_dma.

     Указатель  host_scribble указывает на область досупной памяти
выделенной  scsi_malloc().  Драйвер  SCSI  низкого уровня обладает
возможностью  управления  этим  указателем  и  соответствующей ему
памяти, а также возможностью очистки ненужной информации в памяти.


              2.7.8.2.3 Структура Scsi_Pointer.

     Переменная  SCp,  структура  типа  Scsi_Pointer,  описана  на
рисунке  ниже.  Переменные  этой структуры могут быть использованы
любыми  средствами  в  драйверах низкого уровня. Как обычно buffer
здесь  указывает  на  текущую позицию scatterlist, buffer_residual
показывает  количество  элементов находящихся в scatterlist, ptr -
указатель   на  буффер,  а  this_residual  -  число  символов  для
передачи.   Некоторые   host   адаптеры  требуют  эту  информацию,
некоторые игнорируют ее.

     Второй  набор  переменных содержит информацию о статусе SCSI,
различные указатели и флаги.

          typedef struct scsi_pointer
           {
             char       *ptr;
             int            this_residual;
             struct scatterlist *buffer;
             int            buffers_residual;

             volatile int   Status;
             volatile int   Message;
             volatile int   have_data_in;
             volatile int   sent_command;
             volatile int   phase;
           }Scsi_Pointer;