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. 熟化模式游戏端口¶
有些游戏端口可以将轴值报告为数字,这意味着驱动程序不必以旧的方式测量它们 - ADC 内置于游戏端口中。要注册一个熟化游戏端口
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);
这里唯一令人困惑的是模糊值。最好通过实验确定,它是 ADC 数据中的噪声量。完美的游戏端口可以将此值设置为零,最常见的模糊值介于 8 和 32 之间。有关模糊处理,请参见 analog.c 和 input.c - 模糊值决定了用于消除数据中噪声的高斯滤波器窗口的大小。
2.4. 更复杂的游戏端口¶
游戏端口可以支持原始模式和熟化模式。在这种情况下,将示例 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;
如果游戏端口支持熟化模式,则应将其设置为表示数据中噪声量的数值。请参见熟化模式游戏端口。
void (*trigger)(struct gameport *);
触发器。此函数应触发 ns558 单次触发器。如果设置为 NULL,将使用 outb(0xff, io)。
unsigned char (*read)(struct gameport *);
读取按钮和 ns558 单次触发器位。如果设置为 NULL,将改为使用 inb(io)。
int (*cooked_read)(struct gameport *, int *axes, int *buttons);
如果游戏端口支持熟化模式,则应将其指向其熟化读取函数。它应该使用游戏杆轴的四个值填充 axes[0..3],并使用表示按钮的四个位填充 buttons[0]。
int (*calibrate)(struct gameport *, int *axes, int *max);
用于校准 ADC 硬件的函数。调用时,axes[0..3] 应由调用者预先填充熟化数据,max[0..3] 应预先填充每个轴的预期最大值。calibrate() 函数应设置 ADC 硬件的灵敏度,以便最大值适合其范围,并重新计算 axes[] 值以匹配新的灵敏度,或者从硬件重新读取它们,以便它们给出有效值。
int (*open)(struct gameport *, int mode);
Open() 有两个用途。首先,驱动程序以原始模式或熟化模式打开端口,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;
供游戏端口层内部使用。
};
尽情享用!