2. 游戏端口驱动程序编程¶
2.1. 一个基本的经典游戏端口¶
如果游戏端口不提供超过 inb()/outb() 功能,则将它注册到操纵杆驱动程序所需的代码很简单
struct gameport gameport;
gameport.io = MY_IO_ADDRESS;
gameport_register_port(&gameport);
确保 struct gameport 中的所有其他字段都初始化为 0。游戏端口通用代码将处理其余部分。
如果您的硬件支持多个 io 地址,并且您的驱动程序可以选择要将硬件编程到哪个地址,那么首选从更异构的地址开始,因为与标准 0x201 地址冲突的可能性较小。
例如,如果您的驱动程序支持地址 0x200、0x208、0x210 和 0x218,那么 0x218 将是首选地址。
如果您的硬件支持未映射到 ISA io 空间(高于 0x1000)的游戏端口地址,请使用该地址,并且不要映射 ISA 镜像。
此外,始终对游戏端口占用的整个 io 空间执行 request_region()。虽然只有一个 ioport 真正被使用,但游戏端口通常占用 io 空间中的一个到十六个地址。
还请考虑在 ->open() 回调中启用卡上的游戏端口(如果 io 映射到 ISA 空间)- 这样它只会在真正使用时才占用 io 空间。在 ->close() 回调中再次禁用它。您还可以在 ->open() 回调中选择 io 地址,这样如果某些可能的地址已经被其他游戏端口占用,它就不会失败。
2.2. 内存映射游戏端口¶
当可以通过 MMIO 访问游戏端口时,首选这种方式,因为它更快,允许每秒进行更多读取。注册这样的游戏端口不像基本的 IO 那样容易,但也不是很复杂
struct gameport gameport;
void my_trigger(struct gameport *gameport)
{
my_mmio = 0xff;
}
unsigned char my_read(struct gameport *gameport)
{
return my_mmio;
}
gameport.read = my_read;
gameport.trigger = my_trigger;
gameport_register_port(&gameport);
2.3. Cooked 模式游戏端口¶
有一些游戏端口可以将轴值报告为数字,这意味着驱动程序不必以旧方式测量它们 - ADC 内置于游戏端口中。要注册 cooked 游戏端口
struct gameport gameport;
int my_cooked_read(struct gameport *gameport, int *axes, int *buttons)
{
int i;
for (i = 0; i < 4; i++)
axes[i] = my_mmio[i];
buttons[0] = my_mmio[4];
}
int my_open(struct gameport *gameport, int mode)
{
return -(mode != GAMEPORT_MODE_COOKED);
}
gameport.cooked_read = my_cooked_read;
gameport.open = my_open;
gameport.fuzz = 8;
gameport_register_port(&gameport);
这里唯一令人困惑的是 fuzz 值。最好通过实验确定,它是 ADC 数据中的噪声量。完美的游戏端口可以将此设置为零,最常见的 fuzz 在 8 到 32 之间。有关处理 fuzz 的信息,请参见 analog.c 和 input.c - fuzz 值确定用于消除数据中噪声的高斯滤波器窗口的大小。
2.4. 更复杂的游戏端口¶
游戏端口可以支持原始模式和 cooked 模式。在这种情况下,结合示例 1+2 或 1+3。游戏端口可以支持内部校准 - 请参见下文,以及 lightning.c 和 analog.c 中有关其工作方式的信息。如果您的驱动程序同时支持多个游戏端口实例,请使用 gameport 结构的 ->private 成员指向您的数据。
2.5. 注销游戏端口¶
简单
gameport_unregister_port(&gameport);
2.6. 游戏端口结构¶
struct gameport {
void *port_data;
一个私有指针,供游戏端口驱动程序免费使用。(不是操纵杆驱动程序!)
char name[32];
驱动程序通过调用 gameport_set_name() 设置的驱动程序名称。仅用于信息目的。
char phys[32];
驱动程序通过调用 gameport_set_phys() 设置的游戏端口的物理名称/描述。仅用于信息目的。
int io;
用于原始模式的 I/O 地址。如果您的游戏端口支持原始模式,您必须设置此地址,或者将 ->read() 设置为某个值。
int speed;
每秒数千次的原始模式游戏端口读取速度。
int fuzz;
如果游戏端口支持 cooked 模式,则应将其设置为表示数据中噪声量的值。参见Cooked 模式游戏端口。
void (*trigger)(struct gameport *);
触发器。此函数应触发 ns558 oneshots。如果设置为 NULL,则将使用 outb(0xff, io)。
unsigned char (*read)(struct gameport *);
读取按钮和 ns558 oneshot 位。如果设置为 NULL,则将改用 inb(io)。
int (*cooked_read)(struct gameport *, int *axes, int *buttons);
如果游戏端口支持 cooked 模式,则应将其指向其 cooked 读取函数。它应该用操纵杆轴的四个值填充 axes[0..3],并用四个位(代表按钮)填充 buttons[0]。
int (*calibrate)(struct gameport *, int *axes, int *max);
用于校准 ADC 硬件的函数。调用时,调用者应使用 cooked 数据预填充 axes[0..3],max[0..3] 应预先填充每个轴的预期最大值。 calibrate() 函数应设置 ADC 硬件的灵敏度,以便最大值适合其范围,并重新计算 axes[] 值以匹配新的灵敏度,或从硬件重新读取它们,以便它们给出有效值。
int (*open)(struct gameport *, int mode);
Open() 有两个目的。首先,驱动程序以原始模式或 cooked 模式打开端口,open() 回调可以决定支持哪些模式。其次,资源分配可以在这里发生。端口也可以在这里启用。在此调用之前,游戏端口结构的其他字段(即 io 成员)不必有效。
void (*close)(struct gameport *);
Close() 应该释放 open 分配的资源,可能会禁用游戏端口。
struct timer_list poll_timer;
unsigned int poll_interval; /* in msecs */
spinlock_t timer_lock;
unsigned int poll_cnt;
void (*poll_handler)(struct gameport *);
struct gameport *parent, *child;
struct gameport_driver *drv;
struct mutex drv_mutex; /* protects serio->drv so attributes can pin driver */
struct device dev;
struct list_head node;
供游戏端口层内部使用。
};
祝你玩得开心!