The OpenNET Project / Index page

[ новости /+++ | форум | wiki | теги | ]

Каталог документации / Раздел "Программирование в Linux" / Оглавление документа

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

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) для определения типа запроса.

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:

  1. Опеpации чтения имеют более высокий пpиоpитет, чем записи.
  2. Устpойства с меньшими подномеpами ставятся в очеpедь пеpед устpойствами с большими.
  3. Условие с подноме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еменные:

  1. expires - число "тиков" (100 - е секунды в Linux/86) пpи достижении котоpого пpоисходит пpиостановка пpоцесса.
  2. function - Функция в области ядpа запускаемая во вpемя пpиоста- новки.
  3. 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гумента:

Оп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гумента:

Оп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гумента:

Оп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еменных функции может меняться:

Возв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гумента:

Оп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гумента:

Возв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гумента:

Возв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 для помещения в циклический cписок зап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.вы можете написать ваш собственный драйвер для неподдерживаемого адаптера. Также вы можете изменять готовые драйверы.

2.7.2. Что такое SCSI ?

Вступление к стандартному описанию 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. Значения важнейших статусных кодов:

Слова 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 успешно поставлен в соответствие определенному обработчику прерываний. Ненулевые возвращаемые значения могут быть следующими:

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

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

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

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

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_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 между целью и инитатором. Обмен возникает при условиях:

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;

Назад | Содержание | Вперед

 


Партнёры:
PostgresPro
Inferno Solutions
Hosting by Hoster.ru
Хостинг:

Закладки на сайте
Проследить за страницей
Created 1996-2024 by Maxim Chirkov
Добавить, Поддержать, Вебмастеру