PCI总线推出以来,以其独有的特性受到众多厂商的青睐,已经成为计算机扩展总线的主流。目前,国内的许多技术人员已经具备开发PCI总线接口设备的能 力。但是PCI总线的编程技术,也就是对PCI总线设备的操作技术,一直是一件让技术人员感到头疼的事情。PCI总线编程的核心技术是对相应板卡配置空间 的理解和访问。一般软件编程人员基于对硬件设备原理的生疏,很难理解并操作配置空间,希望硬件开发人员直接告诉他们怎样操作;而PCI总线硬件开发人员虽 深刻地理解了其意义,在没有太多编程经验地前提下,也难于轻易地操作PCI板卡。结果大多是硬件技术人员花费大量时间和精力去学习DDK、 WINDRVER等驱动程序开发软件。

作者在开发PCI总线接口设备时,经过对PCI总线协议的深入研究,从协议本身的角度出发,找到一种方面而快捷的PCI配置空间操作方法,只使用简单的 I/O命令即可找到特定的PCI总线设备并对其所有的配置空间进行读写操作。一旦读得其配置空间的内容,即可中得到担任系统对该PCI总线设备的资源分 配。

1 PCI总线配置空间及配置机制

为避免各PCI设备在资源的占用上发生冲突,PCI总线采用即插即用协议。即在系统建立时由操作系统按照各设备的要求统一分配资源,资源分配的信息由系统 写入各PCI设备的配置空间寄存器,并在操作系统内部备份。各PCI设备有其独自的配置空间,设计者通过对积压设备(或插槽)的ISDEL引脚的驱动区分 不同设备的配置空间。配置空间的前64个字节称为配置空间的预定自区,它对每个设备都具有相同的定义且必须被支持;共后的空间称为设备关联区,由设备制造 商根据需要定义。与编程有关的配置空间信息主要有:

(1)设备号(Device ID)及销售商号(Vendor ID),配置空间偏移量为00h,用于对各PCI设备的区分和查找。为了保证其唯一性,Vendor ID应当向PCI特别兴趣小组(PCI SIG)申请而得到。

(2)PCI基地址(PCI Base Address),配置空间偏移量为10~24h,设备通过设定可读写的高位数值来向操作系统指示所需资源空间的大小。比如,某设备需要64K字节的内存 空间,可以将配置空间的某基地址寄存器的高16位设成可读写的,而将低16位置为0(只可读)。操作系统在建立时,先向所有位写1,实际上只有高16位被 接收而被置成了1,低16位仍为0.这样操作系统读取该寄存器时,返回值为FFFF0000h,据此操作系统可以断定其需要的空间大小是64K字节,然后 分配一段空闲的内存空间并向该寄存器的高16位填写其地址。

其它可能与编程有关的配置空间的定义及地址请参阅参考文献[1]。

由于PC-AT兼容系统CPU只有内存和I/O两种空间,没有专用的配置空间,PCI协议规定利用特定的I/O空间操作驱动PCI桥路转换成配置空间的操 作。目前存在两种转换机制,即配置机制1#和配置机制2#。配置机制2#在新的设计中将不再被采用,新的设计应使用配置机制1#来产生配置空间的物理操 作。这种机制使用了两个特定的32位I/O空间,即CF8h和CFCh。这两个空间对应于PCI桥路的两个寄存器,当桥路看到CPU在局部总线对这两个 I/O空间进行双字操作时,就将该I/O操作转变为PCI总线的配置操作。寄存器CF8h用于产生配置空间的地址(CONFIG-ADDRESS),寄存 器CFCh用于保存配置空间的读写数据(CONFIG-DATA)。

配置空间地址寄存器的格式如图1。


CF8H(局部总线):

当CPU发出对I/O空间CFCh的操作时,PCI桥路将检查配置空间地址寄存器CF8h的31位。如果为1,就在PCI总线上产生一个相应的配置空间读或写操作,其地址由PCI桥路根据配置空间地址寄存器的内容作如图2所示的转换。

CFCh (局部总线):

设备号被PCI桥路译码产生PCI总线地址的高位地址,它们被设计者用作IDSEL信号来区分相应的PCI设备。6位寄存器号用于寻址该PCI设备配置空 间62个双字的配置寄存器(256字节)。功能号用于区分多功能设备的某特定功能的配置空间,对常用的单功能设备为000。某中PCI插槽的总线号随系统 (主板)的不同稍有区别,大多数PC机为1,工控机可能为2或3。为了找到某设备,应在系统的各个总线号上查找,直到定位。如果在0~5号总线上不能发现 该设备,即可认为该设备不存在。

理解了上述PCI协议里的配置机制后,就可以直接对CF8h和CFCh两个双字的I/O空间进行操作,查找某个PCI设备并访问其配置空间,从而得到操作系统对该PCI设备的资源分配。

2 用I/O命令访问PCI总线配置空间



要访问PCI总线设备的配置空间,必须先查找该设备。查找的基本根据是各PCI设备的配置空间里都存有特定的设备号(Device ID)及销售商号(Vendor ID),它们占用配置空间的00h地址。而查找的目的是获得该设备的总线号和设备号。查找的基本过程如下:用I/O命令写配置空间的地址寄存器CF8h, 使其最高位为1,总线号及设备为0,功能号及寄存器号为0,即往I/O端口CF8h80000000h;然后用I/O命令读取配置空间的数据寄存器 CFCh。如果该寄存器值与该PCI设备的Device ID及Vendor ID不相符,则依次递增设备号/总线号,重复上述操作直到找到该设备为止。如果查完所有的设备号/总线号(1~5)仍不能找到该设备,则应当考虑硬件上的 问题。对于多功能设备,只要设备配置寄存器相应的功能号值,其余步骤与单功能设备一样。

如查找设备号为9054h,销售商号为10b5的单功能PCI设备,编写的程序如下:

CODE:
char bus;char device; 

unsigned int ioa0,iod; 

int scan( ) 



bus=0;device=0; 

for(char i=0;i<5;i++) { 

for(char j=0;j<32;j++) { 

bus=i; device=j; 

ioa0=0x80000000+bus*0x10000 

+(device*8)*0x100; 

_outpd(0xcf8,ioa0); 

iod=_inpd(0xcfc); 

if (iod0= =0x905410b5) return 0; 





retrn -1 

}

调用子程序scan( ),如果返回值为-1,则没有找到该PCI设备。如果返回值为0,则找到了该PCI设备。该设备的总线号和设备号分别在全局变量bus和device中, 利用这两个变量即可轻易对该设备的配置空间进行访问,从而得到分配的资源信息。假设该PCI设备占用了4个资源空间,分别对应于配置空间10h~1ch, 其中前两个为I/O空间,后两个为内存空间,若定义其基地址分别为ioaddr1,ioaddr2,memaddr1,memaddr2,相应的程序如 下:

CODE:
unsigned short ioaddr1,ioaddr2; 

unsigned int memaddr1,memaddr2; 

unsigned int iobase,ioa; 

void getbaseaddr(char bus,char device); 



iobase=0x80000000+bus*0x10000+(device*8)*0x100; 

ioa=iobase+0x10;/*寻址基地址寄存器0*/ 

_outpd(0xcf8,ioa); 

ioaddr1=(unsigned short)_inpd(0xcfc)&0xfffc; 

/*屏蔽低两位和高16位*/ 

ioa=iobase+0x14; /*寻址基地址寄存器1*/ 

_outpd(0xcf8,ioa); 

ioaddr2=(unsigned short)_inpd(0xcfc)&0xfffc; 

ioa=iobase+0x18;/*寻址基地寄存器2*/ 

_outpd(0xcf8,ioa); 

memaddr1=_inpd(0xcfc) & 0xfffffff0; 

/*屏蔽低4位*/ 

ioa=iobase+0x1c; /*寻址基地址寄存器3*/ 

_outpd(0xcf8,ioa); 

memaddr2=_inpd(0xcfc) & 0xfffffff0; 

}

对于I/O基地址,最低两位D0、D1固定为01,对地址本身无效,应当被屏蔽。对PC-AT兼容机,I/O有效地址为16位,因此高位也应被屏蔽。对于 内存地址,最低位D0固定为0,而D1~D3用于指示该地址的一些物理特性[1],因此其低4位地址应当被屏蔽。需要指出的是该内存地址是系统的物理地 址,在WINDOWS运行于保护模式时,需要经过转换得到相应的线性地址才能对该内存空间进行直接读写。介绍该转换方法的相关文章较为常见,此处不再赘 述。

上述程序给出了读取配置空间里的基地址的方法。另有相当多PCI设备通过配置空间的设备关联区来设置该设备的工作状态,可轻易地用I/O命令进行相应的设置,无须编写繁杂的驱动程序。在开发PCI视频图像采集卡的过程中,该方法得到了实际应用。

#define PCI_CFG_DATA    0xcfc
#define PCI_CFG_CTRL    0xcf8

void pci_read_config_byte(unsigned char bus, unsigned char dev, unsigned char offset, unsigned char *val)
{
    unsigned char fun = 0;

    outl((0x80000000 | ((bus)<<16) |((dev)<<11) | ((fun)<<8) | (offset & ~0x3)), PCI_CFG_CTRL);
    *val = inl(PCI_CFG_DATA) >> ((offset & 3) * 8);
}

void pci_read_config_word(unsigned char bus, unsigned char dev, unsigned char offset, unsigned short *val)
{
    unsigned char fun = 0;

    outl((0x80000000 | ((bus)<<16) |((dev)<<11) | ((fun)<<8) | (offset & ~0x3)), PCI_CFG_CTRL);
    *val = inl(PCI_CFG_DATA) >> ((offset & 3) * 8);
}

void pci_read_config_dword(unsigned char bus, unsigned char dev, unsigned char offset, unsigned int *val)
{
    unsigned char fun = 0;

    outl((0x80000000 | ((bus)<<16) |((dev)<<11) | ((fun)<<8) | (offset)), PCI_CFG_CTRL);
    *val = inl(PCI_CFG_DATA);
}
很明显就是先向控制寄存器写入综合地址,格式前面已经提到,对比一下是完全一样的。然后从数据寄存器读数据即可,由于数据寄存器是32位的,如果不是读取双字,需要做移位操作。
另外一定需要注意大小端问题,如需要就要进行大小端转换,下面写程序也一样。

5. 写程序
void pci_write_config_dword(unsigned char bus, unsigned char dev, unsigned char offset, unsigned int val)
{
    unsigned char fun = 0;

    outl((0x80000000 | ((bus)<<16) |((dev)<<11) | ((fun)<<8) | (offset)), PCI_CFG_CTRL);
    outl(val, PCI_CFG_DATA);
}

void pci_write_config_word(unsigned char bus, unsigned char dev, unsigned char offset, unsigned short val)
{
    unsigned long tmp;
    unsigned char fun = 0;

    outl((0x80000000 | ((bus)<<16) |((dev)<<11) | ((fun)<<8) | (offset & ~0x3)), PCI_CFG_CTRL);
    tmp = inl(PCI_CFG_DATA);
    tmp &= ~(0xffff << ((offset & 0x3) * 8));
    tmp |= (val << ((offset & 0x3) * 8));
    outl(tmp, PCI_CFG_DATA);
}

void pci_write_config_byte(unsigned char bus, unsigned char dev, unsigned char offset, unsigned short val)
{
    unsigned long tmp;
    unsigned char fun = 0;

    outl((0x80000000 | ((bus)<<16) |((dev)<<11) |((fun)<<8) | (offset & ~0x3)), PCI_CFG_CTRL);
    tmp = inl(PCI_CFG_DATA);
    tmp &= ~(0xff << ((offset & 0x3) * 8));
    tmp |= (val << ((offset & 0x3) * 8));
    outl(tmp, PCI_CFG_DATA);
}
写程序同读程序一样,先向控制寄存器写入综合地址,然后向数据寄存器写入数据。

6. 问题
上面的程序都是参考linux内核对pci空间的读写程序写的。但是在应用程序中读写pci空间和在内核中读写pci空间是完全不同的。在linux源代码中可以看到,在进行pci空间的读写操作都是在关闭中断的情况下进行的,而在用户程序空间就没有这个手段了。所以,读写可能会出错。
经过本人试验,读基本上没有出错过,而写有一定出错的概率,慎用!
有兴趣的,可以随便写个应用程序试试看。

7. 源代码
附上一份源代码,可以直接编译运行。
#include <stdio.h>
#include <stdlib.h>
#include <sys/io.h>
static unsigned int read_pci_config_32(unsigned char bus, unsigned char slot, unsigned char func, unsigned char offset)
{
 unsigned int v;
 outl(0x80000000 | (bus<<16) | (slot<<11) | (func<<8) | offset, 0xcf8);
 v = inl(0xcfc);
 return v;
}
unsigned char read_pci_config_8(unsigned char bus, unsigned char slot, unsigned char func, unsigned char offset)
{
 unsigned char v;
 outl(0x80000000 | (bus<<16) | (slot<<11) | (func<<8) | offset, 0xcf8);
 v = inb(0xcfc + (offset&3));
 return v;
}
unsigned short read_pci_config_16(unsigned char bus, unsigned char slot, unsigned char func, unsigned char offset)
{
 unsigned short v;
 outl(0x80000000 | (bus<<16) | (slot<<11) | (func<<8) | offset, 0xcf8);
 v = inw(0xcfc + (offset&2));
 return v;
}
void write_pci_config_32(unsigned char bus, unsigned char slot, unsigned char func, unsigned char offset, unsigned int val)
{
 outl(0x80000000 | (bus<<16) | (slot<<11) | (func<<8) | offset, 0xcf8);
 outl(val, 0xcfc);
}
void write_pci_config_8(unsigned char bus,unsigned char slot, unsigned char func, unsigned char offset, unsigned char val)
{
 outl(0x80000000 | (bus<<16) | (slot<<11) | (func<<8) | offset, 0xcf8);
 outb(val, 0xcfc + (offset&3));
}
void write_pci_config_16(unsigned char bus,unsigned char slot, unsigned char func, unsigned char offset, unsigned char val)
{
 outl(0x80000000 | (bus<<16) | (slot<<11) | (func<<8) | offset, 0xcf8);
 outw(val, 0xcfc + (offset&2));
}
int main(void)
{
 iopl(3);

 printf("0 0 0 0 = %x\n", read_pci_config_16(0, 0 , 0, 0));
 printf("0 0 0 2 = %x\n", read_pci_config_16(0, 0 , 0, 2));
 printf("0 1 0 0 = %x\n", read_pci_config_16(0, 1 , 0, 0));
 printf("0 1 0 2 = %x\n", read_pci_config_16(0, 1 , 0, 2));

 printf("0 7 1 0 = %x\n", read_pci_config_16(0, 7 , 1, 0));
 printf("0 7 1 2 = %x\n", read_pci_config_16(0, 7 , 1, 2));
 return 0;
}

//

/////////////////////////////////////////////////////////
#include <sys/io.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>  

unsigned char bus;
unsigned char device,i,j;
unsigned int pcicfgaddr=0;

static unsigned int read_pci_config_32(unsigned char bus, unsigned char slot, unsigned char func, unsigned char offset)
{
 unsigned int v;
 outl(0x80000000 | (bus<<16) | (slot<<11) | (func<<8) | offset, 0xcf8);
 v = inl(0xcfc);
 return v;
}
void write_pci_config_32(unsigned char bus, unsigned char slot, unsigned char func, unsigned char offset, unsigned int val)
{
 outl(0x80000000 | (bus<<16) | (slot<<11) | (func<<8) | offset, 0xcf8);
 outl(val, 0xcfc);
}
int scan()
{//list pci device
   bus=0;device=0;
	  for( i=0;i<5;i++)
    {
			for( j=0;j<32;j++)
	    {
				bus=i; device=j;
	      pcicfgaddr=read_pci_config_32(bus,device,0,0);
	  //       printf("%d %d 0x%x\n",bus,device, pcicfgaddr);
				if (pcicfgaddr==0x27201103)//deviceID&vendorID
	      {
	         pcicfgaddr=read_pci_config_32(bus,device,0,0x18)&0xfffffff0;
	         printf("%d %d 0x%x\n",bus,device,pcicfgaddr );
	         return pcicfgaddr;
	      }
			} 

	  } 

	return 0 ;
}
int main(int argc, char *argv[])
{
	int i;
	unsigned char * base2addr;
	int  fd;
  iopl(3);
  if(scan()==0)
  {
      printf("no card\n");
      return 0;
  }

	if ( (argc < 2) || (1 != strlen(argv[1])))
	{
	    printf("1: set raid to sata1.0(1.5Gbps)\n");
	    printf("2: set raid to sata2.0(3Gbps)\n");
	    printf("3: set raid to sata3.0(6Gbps)\n");
	    return -1;
	}      

	fd = open ("/dev/mem", O_RDWR);
	if (fd < 0)
	{
	   printf("cannot open /dev/mem.\n");
	   return -1;
	}
	//map raid card bar2 physical addr
	base2addr = (unsigned char *)mmap(0, 0x40000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, pcicfgaddr);

	if(base2addr < 0)
	{
	  printf("base2addr mmap failed.\n");
	  return -1;

	}

	switch (*argv[1])
	{
		case '1':
				//sata1.0(1.5Gbps)
				for(i=0; i<4; i++)
				{
					*(unsigned int *)(base2addr+0x20000+0x250+i*8) = 0x08;
					*(unsigned int *)(base2addr+0x20000+0x254+i*8) = ((*(unsigned int *)(base2addr+0x20000+0x254+i*8))&0xffffff8f)|0x00000011;

					*(unsigned int *)(base2addr+0x24000+0x250+i*8) = 0x08;
					*(unsigned int *)(base2addr+0x24000+0x254+i*8) = ((*(unsigned int *)(base2addr+0x24000+0x254+i*8))&0xffffff8f)|0x00000011;
				}
				printf("1: set raid to sata1.0(1.5Gbps)\n");
				break;
		case '2':
				//sata2.0(3Gbps)
				for(i=0; i<4; i++)
				{
					*(unsigned int *)(base2addr+0x20000+0x250+i*8) = 0x08;
					*(unsigned int *)(base2addr+0x20000+0x254+i*8) = ((*(unsigned int *)(base2addr+0x20000+0x254+i*8))&0xffffff8f)|0x00000031;

					*(unsigned int *)(base2addr+0x24000+0x250+i*8) = 0x08;
					*(unsigned int *)(base2addr+0x24000+0x254+i*8) = ((*(unsigned int *)(base2addr+0x24000+0x254+i*8))&0xffffff8f)|0x00000031;
				}
				printf("2: set raid to sata2.0(3Gbps)\n");
				break;
		case '3':
				//sata3.0(6Gbps)
				for(i=0; i<4; i++)
				{
					*(unsigned int *)(base2addr+0x20000+0x250+i*8) = 0x08;
					*(unsigned int *)(base2addr+0x20000+0x254+i*8) = ((*(unsigned int *)(base2addr+0x20000+0x254+i*8))&0xffffff8f)|0x00000071;

					*(unsigned int *)(base2addr+0x24000+0x250+i*8) = 0x08;
					*(unsigned int *)(base2addr+0x24000+0x254+i*8) = ((*(unsigned int *)(base2addr+0x24000+0x254+i*8))&0xffffff8f)|0x00000071;
				}
				printf("3: set raid to sata3.0(6Gbps)\n");
				break;
	}

	/*
 ((*(unsigned int *)(base2addr+0x20258))) = 0xc;
 sleep(1);
 printf("base2addr mmap 0x%x.\n",((*(unsigned int *)(base2addr+0x2025c))));
 sleep(1);
 ((*(unsigned int *)(base2addr+0x24258))) = 0xc;
sleep(1);
 printf("base2addr mmap 0x%x.\n",((*(unsigned int *)(base2addr+0x2425c))));
*/
	munmap(base2addr, 0x40000); //destroy map memory
	close(fd);  //close

	return 0;
}

linux驱动---用I/O命令访问PCI总线设备配置空间的更多相关文章

  1. Linux PCI/PCI-E设备配置空间读取与修改

    Linux PCI/PCI-E设备配置空间读取与修改 1 前言 PCI和PCI Express,是计算机常使用的一种高速总线.操作系统中的PCI/PCI-E设备驱动以及操作系统内核,都需要访问PCI及 ...

  2. Linux驱动设计——内存与IO访问

    名词解释 内存空间与IO空间 内存空间是计算机系统里面非系统内存区域的地址空间,现在的通用X86体系提供32位地址,寻址4G字节的内存空间,但一般的计算机只安装256M字节或者更少的内存,剩下的高位内 ...

  3. 007 PCI总线的桥与配置(二)

    一.PCI桥与PCI设备的配置空间 PCI设备都有独立的配置空间,HOST主桥通过配置读写总线事务访问这段空间.PCI总线规定了三种类型的PCI配置空间,分别是PCI Agent设备使用的配置空间,P ...

  4. 3.1 PCI设备BAR空间的初始化

    在PCI Agent设备进行数据传送之前,系统软件需要初始化PCI Agent设备的BAR0~5寄存器和PCI桥的Base.Limit寄存器.系统软件使用DFS算法对PCI总线进行遍历时,完成这些寄存 ...

  5. 2.1 存储器域与PCI总线域

    HOST主桥的实现因处理器系统而异.PowerPC处理器和x86处理器的HOST主桥除了集成方式不同之外,其实现机制也有较大差异.但是这些HOST主桥所完成的最基本功能依然是分离存储器域与PCI总线域 ...

  6. PCI总线学习

    PCI总线概述: 因为不实际操作了,所以就写一些方法论上的东西,纪念一下. PCI总线有三个非常显著的优点: 1. 在计算机和外设传输数据时具有更好的性能. 2. 能够尽量独立于具体的平台. 3. 可 ...

  7. 006 PCI总线的桥与配置(一)

    在PCI体系结构中,含有两类桥片,一个是HOST主桥,另一个是PCI桥.在每一个PCI设备中(包括PCI桥)都含有一个配置空间.这个配置空间由HOST主桥管理,而PCI桥可以转发来自HOST主桥的配置 ...

  8. 1.1 PCI总线的组成结构

    如上文所述,PCI总线作为处理器系统的局部总线,是处理器系统的一个组成部件,讲述PCI总线的组成结构不能离开处理器系统这个大环境.在一个处理器系统中,与PCI总线相关的模块如图1?1所示. 如图1?1 ...

  9. 2.4 PCI总线的配置

    PCI总线定义了两类配置请求,一个是Type 00h配置请求,另一个是Type 01h配置请求.PCI总线使用这些配置请求访问PCI总线树上的设备配置空间,包括PCI桥和PCI Agent设备的配置空 ...

随机推荐

  1. Electron 打包Mac安装包代码签名问题解决方案Could not get code signature for running application

    最近一直在做electron应用的打包,集成mac版本的自动更新时出现了问题. Error: Could not get code signature for running application ...

  2. C#基础(七)虚函数

    若一个实例方法声明前带有virtual关键字,那么这个方法就是虚方法.虚方法与非虚方法的最大不同是,虚方法的实现可以由派生类所取代,这种取代是通过方法的重写实现的(以后再讲)虚方法的特点:虚方法前不允 ...

  3. spring mvc 中自定义404页面在IE中无法显示favicon.ico问题的解决方法。

    此处用的是jsp,控制层用的是ModelAndView, 具体解决方法如下: @RequestMapping(value = "notfound", method = Reques ...

  4. androidstudio canary5一个bug

    Android Studio最新开发版一直跟着升级.到Canary5的时候出了一个bug. app build.gradle添加自己编译的aar库,原本一直使用: compile(name:'ijkp ...

  5. 如何让oracle DB、监听和oem开机启动(dbstart)

    如何让oracle DB.监听和oem开机启动(dbstart) 让oracle DB.监听和oem开机启动(dbstart) Oracle提供了伴随操作系统自动重启的功能,在Windows中,可以修 ...

  6. java多线程之内存可见性-synchronized、volatile

    1.JMM:Java Memory Model(Java内存模型) 关于synchronized的两条规定: 1.线程解锁前,必须把共享变量的最新值刷新到主内存中 2.线程加锁时,将清空工作内存中共享 ...

  7. Java设计模式——代理模式

    public interface People { public void work(); } public class RealPeople implements People { public v ...

  8. HDU 5608 function [杜教筛]

    HDU 5608 function 题意:数论函数满足\(N^2-3N+2=\sum_{d|N} f(d)\),求前缀和 裸题-连卷上\(1\)都告诉你了 预处理\(S(n)\)的话反演一下用枚举倍数 ...

  9. oneNote总结

    22.添加附加文件删除后,文件大小没有发生改变的(优化文件和清空回收站)

  10. Getting the pixel coordinates of text or ticks in matplotlib

    The exact pixel coordinates of title, labels, legends or ticks are important information for the tra ...