原创 Linux下并口的访问★★★★★

2011-6-14 21:43 5828 13 8 分类: MCU/ 嵌入式

关于并口Linux系统中的应用

1、2-9脚为可用数据口(输出):inb(0x378)/outb(0x378),可以用来输出TTL电平
2、10-15为可用状态口(输入):inb(0x379)/outb(0x379),可以用来检测TTL电平或通断信号
=======================================================================
由于Linux的开放性和高度的用户自定义性,要让Linux支持并口,首先要让Linux的内核支持并口。  
  一般我们在从光盘安装Linux时,都会把并口支持功能放进内核当中。但如果是自己升级内核、编译内核时  
  就要注意了:不要把并口支持功能给去掉。不然,你会发现并口死活“不肯”产生信号。要让内核支持并口  
  (如果大家是从光盘安装Linux的话,则不要下面的步骤,因为系统会自动加载并口支持功能的),就要在  
  设置系统内核时选择上"Parallel   port   support"这个选项。这个选项可以在make   config或make   xconfig  
  或make   gconfig或make   menuconfig时找到。如果大家以上面几个命令不太了解,可以参考编译内核的文档。  
  这样,在编译内核时,我们就预定义了宏CONFIG_PARPORT,编译器就被通知到要把并口支持部分代码给编  
  进内核当中去。当然,我们也可以把并口支持功能编译成模块(Module)形式,这些模块的存放位置在  
  "/lib/module/内核版本号/kernel/drivers/parport"目录下,相应的文件是parport.o、parport_pc.o等。  
  在需要使用并口时,用命令insmod、rmmod来加载、去除并口支持功能。其中insmod是指"Insert   Module",  
  rmmod是指"Remove   Module"。  
   
          在确定Linux内核支持并口功能后,我们就可以进入下面的主题了。  
             
          访问Linux并口的函数,和前面介绍的Windows95/98下的_outp是极其类似的:outb、inb函数,当中  
  的参数含义也和前面讲到的_outp、_inp一样。  
   
          但需要指出的是,Linux也采用了与Windows   NT/2000相似的保护措施,禁止用户进程直接对硬件端口  
  访问(用户空间程序不允许访问内核空间中的内容)。为了简化并口访问,Linux又提供了一个Helper   Function,  
  这就是ioperm。这个函数可以告诉内核:给出一定的控制权与用户进程,当用户程序结束后,再把控制权给收  
  回来。如果我们不事先调用ioperm的话,访问并口的程序也可以编译成功,但在运行时会出错,一般会在屏幕  
  上出现"Segment   Failure"的出错提示。  
   
          使用到的两个函数原型如下:  
   
  #include   <sys/io.h>  
  int   ioperm(unsigned   long     from,   unsigned   long     num,   int   turn_on);  
  void   outb(unsigned   char   byte,   unsigned   port);  
   
  当中的ioperm是让从from开始,直到num个字节,这段范围内的I/O映射地址可以为普通用户程序访问。上  
  述行为是在当turn_on参数为"真"(非零)时允许的。当为"假"时,就禁止这种行为。这可以在程序完成,  
  不再要访问硬件端口时调用。  
   
              下面给出Linux下访问并口的示例代码:  
               
                ...   ...  
                ioperm(0x000,0x3FF,1);   //把0x000~0x3FF之间空间开放给用户程序  
                                                              //这段范围对访问并口而言,已经足够了。  
   
                outb(0xFF,0x378);             //向并口数据位写数据。这里是将8个数据位全置为1  
                ...     ...  
                ioperm(0x000,0x3FF,0);   //收回控制权,用户程序在这之后就不再可以  
                                                              //访问内核空间内容了。  
===============================================================
 

The Linux Parallel Port Programming HOWTO

Eugene Weiss

yossarian@users.sourceforge.net

Revision History
Revision v0.1 7 December, 2000

This document provides information on programming the PC parallel port for the Linux operating system.


Table of Contents
Introduction
Types of Parallel Ports
Accessing the Parallel Port
Selecting a Data Transfer Mode
SPP, Compatibility, Nibble and Byte Mode Transfers
EPP
ECP
Other Sources of Information
Copyright Information
Disclaimer
===========================================================================

Linux I/O port programming mini-HOWTO

Author: Riku Saikkonen <Riku.Saikkonen@hut.fi>

v3.0, 2000-12-13


This HOWTO document describes programming hardware I/O ports and waiting for small periods of time in user-mode Linux programs running on the Intel x86 architecture.

1. Introduction

2. Using I/O ports in C programs

3. Interrupts (IRQs) and DMA access

4. High-resolution timing

5. Other programming languages

6. Some useful ports

7. Hints

8. Troubleshooting

9. Example code

10. Credits

===========================================

linux并口驱动代码有问题,帮忙看看。

http://www.unixresources.net/linux/clf/driver/archive/00/00/46/79/467905.html

重新启动系统以后加载概模块,提示说0x378端口已经被占用,所以我卸载了lp 和 parport_pc模块,可以加载成功,可是我卸载以后再重新加载不成功,又说资源被占用。可能是卸载模块时没有释放资源,可是代码里面明明有这一部分代码阿???郁闷中。。。。。。。

卸载模块时提示:
Trying to free nonexistent resource <00000378-0000037f>


代码如下:

#ifndef __KERNEL__
# define __KERNEL__
#endif
#ifndef MODULE
# define MODULE
#endif
//#include <linux/malloc.h>
#include <linux/config.h>
#include <linux/module.h>
#include <linux/version.h>
#include <asm/uaccess.h>
#include <linux/sched.h>
#include <linux/kernel.h> /* printk() */
#include <linux/fs.h> /* everything... */
#include <linux/errno.h> /* error codes */
#include <linux/delay.h> /* udelay */
//#include </usr/src/linux-2.4/include/linux/malloc.h>
#include <linux/slab.h>

#include <linux/mm.h>
#include <linux/ioport.h>
#include <linux/interrupt.h>
#include <linux/tqueue.h>
#include <asm/io.h>

#ifdef MODULE_LICENSE
MODULE_LICENSE ("GPL");
#endif

static unsigned long base = 0x378;
unsigned long short_base = 0x378;
static unsigned long major = 0;
static int portnum = 8;

int
short_open (struct inode *inode, struct file *filp)
{
MOD_INC_USE_COUNT;
return 0;
}

int
short_release (struct inode *inode, struct file *filp)
{
MOD_DEC_USE_COUNT;
return 0;
}

ssize_t
do_short_write (struct inode * inode, struct file * filp, const char *buf,
size_t count, loff_t * f_pos)
{
int retval = count;
unsigned long address = short_base + 2;

unsigned char *kbuf = kmalloc (count, GFP_KERNEL), *ptr;

if (!kbuf)
return -ENOMEM;
if (copy_from_user (kbuf, buf, count))
return -EFAULT;
ptr = kbuf;

while (count--)
{
outb (*(ptr++), address);
wmb ();
}

kfree (kbuf);
return retval;
}

ssize_t
do_short_read (struct inode * inode,
struct file * filp, char *buf, size_t count, loff_t * f_pos)
{
int retval = count;
unsigned long address = short_base + 2;
unsigned char *kbuf = kmalloc (count, GFP_KERNEL), *ptr;

if (!kbuf)
return -ENOMEM;
ptr = kbuf;

while (count--)
{
*(ptr++) = inb (address);
rmb ();
}

if ((retval > 0) && copy_to_user (buf, kbuf, retval))
retval = -EFAULT;
kfree (kbuf);
return retval;
}

ssize_t
short_read (struct file * filp, char *buf, size_t count, loff_t * f_pos)
{
return do_short_read (filp->f_dentry->d_inode, filp, buf, count, f_pos);
}

ssize_t
short_write (struct file * filp, const char *buf, size_t count, loff_t * f_pos)
{
return do_short_write (filp->f_dentry->d_inode, filp, buf, count, f_pos);
}


struct file_operations short_fops = {
read:short_read,
write:short_write,
open:short_open,
release:short_release,
};

int
init_module (void)
{
int portnum = 1;
int result;

SET_MODULE_OWNER (&short_fops);

result = check_region (base, portnum);

if (result)
{
printk (KERN_INFO "short: can't get I/O port address 0x%lx ",
short_base);
return result;
}
request_region (base, portnum, "short");

result = register_chrdev (major, "short", &short_fops);
if (result < 0)
{
printk (KERN_INFO "short: can't get major number ");
release_region (short_base, portnum);
return result;
}
if (major == 0)
major = result;
return 0;
}

void
cleanup_module (void)
{
printk ("major = 0x%lx ", major);
unregister_chrdev (major, "short");
release_region (base, portnum);
}


编译:
gcc -D__KERNEL__ -O2 -Wall -I/usr/src/linux-$(shell uname -r)/include -c pell.c

------------------------

已经找到原因,这是你的一个小小的笔误,就是你可以查看一下你在
init_module中有这么一句话

int portnum = 1;

而你在前面定义了一个全局变量

static int portnum = 8;


这样在编译器处理的时候就会把portnum写成1,这样当你用release_region释放资源的时候就会发现没有 合适的资源要释放
这是因为你这时要释放的是8个字节长的resource,而你前面申请的只有1个byte长的resource ,这样就会造成资源泄漏.


谢谢你提供了源代码,不然,真的没有人能够找出这其中的毛病,


------------------开放就意味着找出更多的不足,得到更大的改进.

============================================

两种方式

一  驱动程序执行方式
  1.申请I/O端口
     A 直接端口方式
        check_region
        request_region
     B内存映射方式
        check_mem_region
        request_mem_retion
       然后队端口地址映射
        ioremap
  2.注册驱动
    register_chrdev  申请主设备号,注册驱动名,相关的操作.
  3.探测中断
      A 内核探测
      B 定制探测
      C 直接根据I/O地址,分配相应的IRQ号
  4.安装中断相应处理函数
       request_irq(irq,(*)Handler()...)
      A 共享中断的处理函数
           端口有中断到,则判断时不时本端口的中断.若是.则填充缓冲区.同时wake_up_interruptible,唤起等待序列.
          在read操作中的队列由signal_pending唤醒,拷贝数据到用户空间.
      B 下半部中断的处理函数
           通过queue_task,执行队列处理函数唤起中断.
      C  小任务中断处理函数
          通过tasklet_schedule执行小任务处理函数唤起中断.
二   应用程序执行方式. 

  • 通过 ioperm 命令,例如,ioperm ( BASE, range , 1),调用内核,得到 I/O 地址空间的使用权;
  • 通过一个发送请求指令,例如, outb(1, BASE )
  • 等待足够的时间让咖啡煮好,让时间参数在命令行中被读取是一件很好的事情
  • 然后发送 out(0, BASE) 指令关掉咖啡机
  • 在结束之前还应归还并口 I/O 地址的使用权, ioperm(BASE,range,0) .
     程序:/* coffee.c */
#include <asm/io.h> /* linux-specific */
#ifdef __GLIBC__
#  include <sys/perm.h>
#endif
int main(int argc, char **argv)
{   
    setuid(0); /* if we're setuid, force it on */

   if(ioperm(0x378,1,1))
      printf("error,we can't  ioperm our ox378 port\n");
   outb(0xff,0x378);

   sleep(5);
   outb(0,0x378);
   if(ioperm(0x378,1,0))
      printf("error,we can't  ioperm our ox378 port\n"); 

    exit(1);
}

三   在驱动程序中如何取得被其它驱动程序使用的并口
     首先   cat   /proc/ioports  看端口地址是分配给谁了.
             然后看该名字的 ls -l  /dev/port_name
              找到主设备号和次设备号
     然后  cat   /proc/devices 看主设备好对应的驱动
             然后rmmod 该驱动.
     如果是编译到了内核的驱动则只需在/lib/module/`uname   -r`/kernel/drivers/ 删除该名字的驱动,
            则重起后,该驱动就不会暂用该端口了,但这个驱动的名字不一定在/proc/devices 中出现
=============================================

Linux并口网络解决方案(转贴)
2003-04-10 nesta1

大家知道,在DOS环境下,我们可以用并口或串口将两台PC连接起来,一台充当服务器,另一台充当客户,但充当服务器的机器不能做其它操作,只能为 Client服务。虽然在方便上和速度均不如网卡,但它提供了一个“穷人”的解决方案。如果仅拷贝少量数据,它还是可以满足一般人的需求。并口的速度要远远比串口快。

在Linux内核中,网络设备中有一个叫PLIP (Parallel Line Internet Protocol). 它提供了并口的网络支持,并将并口映射成网络设备。它支持标准并口,扩展并口的支持。传送速度依赖于并口线的质量和机器的配置。
下面,我将系统的配置作简要介绍。

1. 在内核中支持PLIP
cd /usr/src/linux
make menuconfig
# select Network device support
select PLIP as modules

2. 编译内核
cd /usr/src/linux
make dep; make bzImage; make modules; make modules_install;

3. 将新内核配置到Lilo中去。

4. 重新启动

5. 打开Ipforward.
echo 1 > /proc/sys/net/ipv4/ip_forward

6. 运行网络配置工具,配置PLIP
turbonetcfg
# add new interface
# select PLIP
# add the ipaddress and mask to that
# save & exit

7. 启动PLIP
modprobe plip
# if it does not work
# echo 7 > /proc/parport/0/irq
# modprobe plip
ifup plip0

8. 配置网关。
# eg. Machine A:
# PLIP0 -- 10.0.0.1
#B -- gateway
# PLIP0 -- 10.0.0.2
# eth0 -- 172.16.69.12
# in B Machine, we setup ipchains
/sbin/ipchains -A forward -j MASQ -s 10.0.0.0/255.255.255.0 -d 0.0.0.0/0

通过我们的试验,使用WWW, TELNET 与普通网卡没有区别,但当使用FTP时,速度稍慢,大约35K/s
左右,并且在FTP的同时,我们也发现系统偶尔出现Timeout, 并且当你作其它事情时,感到系统很慢。说
明PLIP的驱动程序还需要改进。

参考资料:/usr/src/linux/Documentation/networking/PLIP.txt

===============================================

9.3. 一个 I/O 端口例子

我们用来展示一个设备驱动内的端口 I/O 的例子代码, 操作通用的数字 I/O 端口; 这样的端口在大部分计算机系统中找到.

一个数字 I/O 端口, 在它的大部分的普通的化身中, 是一个字节宽的 I/O 位置, 或者内存映射的或者端口映射的. 当你写一个值到一个输出位置, 在输出管脚上见到的电信号根据写入的单个位而改变. 当你从一个输入位置读取一个值, 输入管脚上所见的当前逻辑电平作为单个位的值被返回.

这样的 I/O 端口的实际实现和软件接口各个系统不同. 大部分时间, I/O 管脚由 2 个 I/O 位置控制: 一个允许选择使用那些位作为输入, 哪些位作为输出, 以及一个可以实际读或写逻辑电平的. 有时, 但是, 事情可能更简单, 并且这些位是硬连线为输入或输出(但是, 在这个情况下, 它们不再是所谓的"通用 I/O"); 在所有个人计算机上出现的并口是这样一个非通用 I/O 端口. 任一方式, I/O 管脚对我们马上介绍的例子代码是可用的.

9.3.1. 并口纵览

因为我们期望大部分读者以所谓的"个人计算机"的形式使用一个 x86 平台, 我们觉得值得解释一下 PC 并口如何设计的. 并口是在个人计算机上运行数字 I/O 例子代码的外设接口选择. 尽管大部分读者可能有并口规范用, 为你的方便, 我们在这里总结一下它们.

并口, 在它的最小配置中 ( 我们浏览一下 ECP 和 EPP 模式) 由 3 个 8-位端口组成. PC 标准在 0x378 开始第一个并口的 I/O 端口并且第 2 个在 0x278. 第一个端口是一个双向数据寄存器; 它直接连接到物理连接器的管脚 2 - 9. 第 2 个端口是一个只读状态寄存器; 当并口为打印机使用, 这个寄存器报告打印机状态的几个方面, 例如正在线, 缺纸, 或者忙. 第 3 个端口是一个只出控制寄存器, 它, 在其他东西中, 控制是否中断使能.

并口通讯中使用的信号电平是标准的 TTL 电平: 0 和 5 伏特, 逻辑门限在大概 1.2 伏特. 你可依靠端口至少符合标准 TTL LS 电流规格, 尽管大部分现代并口在电流和电压额定值都工作的好.

并口连接器和计算机内部电路不隔离, 当你想直接连接逻辑门到这个端口是有用的. 但是你不得不小心地正确连接线; 并口电路当你使用你自己的定制电路时容易损坏, 除非你给你的电路增加绝缘. 你可以选择使用插座并口如果你害怕会损坏你的主板.

位的规范在图 并口的管脚 中概述. 你可以存取 12 个输出位和 5 个输入位, 有些是在它们地信号路径上逻辑地翻转了. 唯一的没有关联信号管脚的位是端口 2 的位 4 (0x10), 它使能来自并口的中断. 我们使用这个位作为我们的在第 10 章中的中断处理的实现的一部分.

图 9.1. 并口的管脚

并口的管脚

9.3.2. 一个例子驱动

我们介绍的驱动称为 short (Simple Hardware Operations and Raw Tests). 所有它做的是读和写几个 8-位 端口, 从你在加载时选择的开始. 缺省地, 它使用分配给 PC 并口的端口范围. 每个设备节点(有一个独特的次编号)存取一个不同的端口. short 驱动不做任何有用的事情; 它只是隔离来作为操作端口的单个指令给外部使用. 如果你习惯端口 I/O, 你可以使用 short 来熟悉它; 你能够测量它花费来通过端口传送数据的时间或者其他游戏的时间.

为 short 在你的系统上运行, 必须有存取底层硬件设备的自由(缺省地, 并口); 因此, 不能有其他驱动已经分配了它. 大部分现代发布设置并口驱动作为只在需要时加载的模块, 因此对 I/O 地址的竞争常常不是个问题. 如果, 但是, 你从 short 得到一个"无法获得 I/O 地址" 错误(在控制台上或者在系统 log 文件), 一些其他的驱动可能已经获得这个端口. 一个快速浏览 /proc/ioports 常常告诉你哪个驱动在捣乱. 同样的告诫应用于另外 I/O 设备如果你没有在使用并口.

从现在开始, 我们只是用"并口"来简化讨论. 但是, 你能够设置基本的模块参数在加载时来重定向 short 到其他 I/O 设备. 这个特性允许例子代码在任何 Linux 平台上运行, 这里你对一个数字 I/O 接口有权限通过 outb 和 inb 存取( 尽管实际的硬件是内存映射的, 除 x86 外的所有平台). 后面, 在"使用 I/O 内存"的一节, 我们展示 short 如何用来使用通用的内存映射数字 I/O.

为观察在并口上发生了什么以及如果你有使用硬件的爱好, 你可以焊接尽管 LED 到输出管脚. 每个 LED 应当串连一个 1-K 电阻导向一个地引脚(除非, 当然, 你的 LED 有内嵌的电阻). 如果你连接一个输出引脚到一个输入管脚, 你会产生你自己的输入能够从输入端口读到.

注意, 你无法只连接一个打印机到并口并且看到数据发向 short. 这个驱动实现简单的对 I/O 端口的存取, 并且没有进行与打印机需要的来操作数据的握手; 在下一章, 我们展示了一个例子驱动(称为 shortprint ), 它能够驱动并口打印机; 这个驱动使用中断, 但是, 因此我们还是不能到这一点.

如果你要查看并口数据通过焊接 LED 到一个 D-型 连接器, 我们建议你不要使用管脚 9 和管脚 10, 因为我们之后连接它们在一起来运行第 10 章展示的例子代码.

只考虑到 short, /dev/short0 写到和读自位于 I/O 基地址的 8-bit 端口( 0x378, 除非在加载时间改变). /dev/short1 写到位于基址 + 1 的 8-位, 等等直到基址 + 7.

/dev/short0 进行的实际输出操作是基于使用 outb 的一个紧凑循环. 一个内存屏障指令用来保证输出操作实际发生并且不被优化掉:

while (count--) {
 outb(*(ptr++), port);
    wmb(); 
} 

你可以运行下列命令来点亮你的 LED:

echo -n "any string" > /dev/short0 

每个 LED 监视一个单个的输出端口位. 记住只有最后写入的字符, 保持稳定在输出管脚上足够长时间你的眼睛能感觉到. 因此, 我们建议你阻止自动插入一个结尾新行, 通过传递一个 -n 选项给 echo.

读是通过一个类似的函数, 围绕 inb 而不是 outb 建立的. 为了从并口读"有意义的"值, 你需要某个硬件连接到连接器的输入管脚来产生信号. 如果没有信号, 你会读到一个相同字节的无结尾的流. 如果你选择从一个输出端口读取, 你极可能得到写到端口的最后的值(这适用于并口和普通使用的其他数字 I/O 电路). 因此, 那些不喜欢拿出他们的烙铁的人可以读取当前的输出值在端口 0x378, 通过运行这样一个命令:

dd if=/dev/short0 bs=1 count=1 | od -t x1 

为演示所有 I/O 指令的使用, 每个 short 设备有 3 个变形: /dev/short0 进行刚刚展示的循环, /dev/short0p 使用 outb_p 和 inb_p 代替"快速"函数, 并且 /dev/short0s 使用字串指令. 有 8 个这样的设备, 从 short0 到 short7. 尽管 PC 并口只有 3 个端口, 你可能需要它们更多如果使用不同的 I/O 设备来运行你的测试.

short 驱动进行一个非常少的硬件控制, 但是足够来展示如何使用 I/O 端口指令. 感兴趣的读者可能想看看 parpor 和 parport_pc 模块的源码, 来知道这个设备在真实生活中能有多复杂来支持一系列并口上的设备(打印机, 磁带备份, 网络接口)

 

文章评论0条评论)

登录后参与讨论
我要评论
0
13
关闭 站长推荐上一条 /2 下一条