Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология Образование Политология Производство Психология Стандартизация Технологии |
Программное обеспечение параллельного порта
Нижеследующее обсуждение посвящено соответствующей функции для этого проекта. Полный листинг программы для parll. с вместе с файлом parll. h приведен в конце этой книги. Настройка файловых операций (fops) Как говорилось ранее, этот модуль использует open (), close () и ioctl (), как и описанные ранее init и cleanup. Первым шагом является настройка структуры файловых операций. Эта структура определена в /linux/f s.h, перечисляющем все функции, которые можно реализовать в нашем модуле. Нам не нужно использовать все операции, достаточно только самых необходимых. Поиск в сети по запросу С99 или linux module даст вам больше информации об этих методах. Используя эту структуру, мы сообщаем ядру о местонахождении наших реализаций (или точках вхождения) open, release и iotcl. par11.с struct file_operations parlport_fops = {.open = parlport_open, .ioctl = parlport_ioctl, .release = parlport_close }; Далее мы создаем функции open () и close (). Данные функции-пустышки используются для сигнализации об открытии и закрытии: parll.с static int parlport_open(struct inode *ino, struct file *filp) { printk(" \n parlport open function" ); return 0; } static int parlport_close(struct inode *ino, struct file *filp) { printk(" \n parlport close function" ); return 0; } Глава 5* Ввод-Вывод Создадим функцию ioctl (). Обратите внимание, что определение функции делается в начале parll. с: #define MODULEJNAME " parll" static int base = 0x378; parll.с static int parlport_ioctl(struct inode *ino, struct file *filp, unsigned int ioctl_cmd, unsigned long parm) { printk(" \n parlport ioctl function" ); if(_IOC_TYPE(ioctl_cmd)! = IOCTL_TYPE) { printk(" \n%s wrong ioctl type", MODULE_NAME); return -1; > switch(ioctl_cmd) { case DATA_OUT: printk(" \n%s ioctl data out=%x", MODULE_NAME, (unsigned int)parm); outb(parm & Oxff, base+0); return (parm & Oxff); case GET.JSTATUS: parm = inb(base+l); printk(" \n%s ioctl get status=%x", MODULE_NAME, (unsigned int)parm); return parm; case CTRL_OUT: printk(" \n%s ioctl Ctrl out=%x", MODULE_NAME, (unsigned int)parm); outbfparm & & Oxff, base+2); return 0; } //end switch return 0; } //end ioctl Функция ioctl () делает возможной обработку любых определенных пользователем команд. В нашем модуле мы используем три регистра, связанные с параллельным портом пользователя. Команда DATA_OUT посылает значение из регистра data, команда GET_STATUS читает из регистра status, и, наконец, команда CTRL_OUT позволяет установить сигнал для порта. Несмотря на то что лучше было бы скрыть специфические для устройства операции внутри функций read() и write (), этот модуль, работоспособен, так как служит только для экспериментов с вводом-выводом, а не для реального применения. Резюме Эти три команды определены в заголовочном файле parll. h. Они создаются с применением вспомогательных функций IOCTL для проверки типов. Вместо использования целого для представления функции IOCTL мы используем IOCTL-макрос проверки типов 10 (type, number), где параметр type определен как р (для параллельного порта), a number как текущий номер IOCTL, используемый в выражении. В начале parlport_ ioctl () мы проверяем тип, которым должен быть р. Так как код приложения использует тот же заголовочный файл, что и драйвер, интерфейс будет согласованным. Настройка функции инициализации модуля Модуль инициализации используется для связи модуля с операционной системой. Он может применяться для ранней инициализации необходимых структур данных. Так как драйверу параллельного порта не требуется сложных структур данных, мы просто регистрируем модуль. parll.с static int parll_init (void) { int retval; retval= register_chrdev(Major, MODULE_NAME, & parlport_fops); if(retval < 0) { printk( " \n%s: can't register", MODULE_NAME); return retval; } else { Maj or=retval; printk(" \n%s: registered, Major=%d", MODULE_NAME, Major); if(request_region(base, 3, MODULE_NAME)) printk(" \n%s: I/0 region busy.", MODULE_NAME); } return 0; } Функция init_module() отвечает за регистрацию модуля в ядре. Функция regis ter_chrdev () получает старший номер запроса (описывается в разд. 5.2 и далее в гл. 10; если 0, ядро назначает ее модулю). Вспомните, что старший номер хранится в структуре inode, на которую указывает структура dentry, на которую указывает структура файла. Вторым параметром является имя устройства, отображаемое в /ргос/ devices. Третьим параметром является только что описанная структура операций. Глава 5 • Ввод-Вывод В случае успешной регистрации функция init вызывает request_region() с базовым адресом параллельного порта и длины диапазона (в байтах) вставляемых регистров. Функция init_module () возвращает отрицательное число в случае неудачи. 3) Настройка функции очистки модуля Функция cleanup__module () отвечает за отмену регистрации модуля и освобождение запрошенного ранее диапазона ввода-вывода: parll.с static void parll_cleanup( void ) { printk(и\n%s: cleanup H, MODULE_NAME); release_region(base/3); unregis ter_chrdev (Maj or, MODULE_NAME); } И наконец, мы помещаем запрашиваемый init и точку очистки: parll.с module_init (parll_init); module_exit(parll_cleanup); 4) Вставка модуля Теперь мы вставляем наш модуль в ядро, как в предыдущем проекте, с помощью Lkp: ~# insmod parll.ko Загляните в /var/ log/messages, где отображается вывод нашей функции init () и уделите особое внимание отображающимся там старшим возвращаемым номерам. Как в предыдущем проекте, мы просто вставляем наш модуль в ядро и удаляем его оттуда. Теперь нам нужно связать наш модуль с файловой системой с помощью команды mknod. Введите в командной строке следующее: Lkp: ~# znknode /dev/parll с < ХХХ> О Параметры: • с. Создается символьный специальный файл (в отличие от блочного). • /dev/parll. Путь к нашему устройству (для открытого вызова). • XXX. Старший номер, возвращаемый во время init (из var/log/messages). Резюме • 0. Младший номер нашего устройства (в данном примере не используется). Например, если вы увидите старший номер 254 в /var/log/messages, команда будет выглядеть следующим образом: Lkp: ~# mknode /dev/parll с 254 О Код приложения Мы создаем простое приложение, которое открывает наш модуль и начинает бинарный отчет на штырьках с DO до D7. Этот код компилируется с помощью дсс арр. с. По умолчанию программа собирается в а. out. арр. с 000 //Приложение, использующее драйвер параллельного порта tinclude < fcntl.h> tinclude < linux/ioctl.h> 004 #include " parll.h" main() { int fptr; int i, retval, parm =0; printf(H\nopening driver now" ); 012 if((fptr = open(-/dev/parll-, 0_WR0NLY))< 0) { printf (" \nopen failed, returned=? %d-, fptr); exit(l); } 018 for(i=0; i< 0xff; i++) 20 system(" sleep.2" ); 21 retval=ioctl(fptr, DATA_OUT, parm); 22 retval=ioctl(fptr, GET_STATUS, parm); 024 if(! (retval & 0x80)) printf(H\nBusy signal count=%xH, parm); if(retval & 0x40) 027 printf(-\nAck signal count=%x", parm); 028 // if(retval & 0x20) // printf(" \nPaper end signal count=%x", parm); // if(retval & 0x10) // printf(-\nSelect signal count=%x-, parm); // if(retval & 0x08) Глава 5* Ввод-Вывод 033 // printf(" \nError signal count=%x", parm); parm++; } 038 close(fptr); } Строка 4 Общий для приложения и драйвера заголовочный файл, содержащий главные макросы IOCTL для проверки типов. Строка 12 Открытие драйвера для получения файлового описателя нашего модуля. Строка 18 Вход в цикл. Строка 20 Замедление цикла, чтобы мы могли увидеть огоньки и отсчет. Строка 21 Используя файловый указатель, посылаем команду DATA_OUT в модуль, который, в свою очередь, использует outb () для записи последних значащих 8 бит параметров для порта данных. Строка 22 Чтение байта состояния с помощью ioctl с команды GET__STATUS. Строки 24-27 Смотрим интересующие нас биты. Обратите внимание, что Busy — это низкий активный сигнал, поэтому, когда ввода-вывода нет, мы читаем его как true. Строки 28-33 Эти строки вы сможете раскомментировать, когда захотите усовершенствовать дизайн. Строка 38 Закрытие модуля Если вы собрали разъем, как показано на рис. 5.5, сигналы busy и аск посылаются, когда два значащих бита счетчика включены. Код приложения считывает эти биты и производит соответствующий вывод. Мы осветили только основные элементы драйвера символьного устройства. Зная эти функции, легко проследить работу кода или создать собственный драйвер. Добавление Резюме
V V V V Рис. 5.5. Собранный разъем в этот модуль обработчика прерывания породит вызов к request_irq () и передачу номера желаемого IRQ и имени обработчика. Его нужно добавить в init_module (). Вот несколько возможных способов усовершенствования драйвера: • Заставить модуль параллельного порта обрабатывать прерывания таймера в качестве ввода. • Как можно превратить 8 бит ввода-вывода в 16, 32, 64? Чем мы жертвуем? • Посылать символы из параллельного порта с помощью функций записи модуля. • Добавить функцию прерывания для использования сигнала аск. Глава 5 • Ввод-Вывод Упражнения 1. Загрузите модуль. В качестве какого файла модуль отобразится в файловой системе? Популярное:
|
Последнее изменение этой страницы: 2016-03-25; Просмотров: 805; Нарушение авторского права страницы