i8253 PIT をアクセスするデバイスドライバ

デバイスをアクセスするドライバを作ります。Windows そして古くは MS-DOS を実行できる PC ならば備わっていると考えられる i8253 Programmable Interval Timer (PIT) にアクセスします。今時の PC ならばほぼ使われていないはずの Channel 1 (D-RAM Refresh conter あるいは D-RAM Refresh timer) のレジスタにアクセスします。

今時の PC の i8253 Channel1 は D-RAM 制御に使われていないの?

今どきの完全な PC の回路が公開されていないので、断言はできません。30pin SIMM, 72pin DIMM を使用するのであれば、i8253-i8237 (PIT-DMAC) の組あるいは i8253(i8254) を timer として容易に Refresh 回路を構成できます。これより機能が増えた DIMM を使用する回路になると i8253 を使用する利点が無くなります。North bridge または CPU に統合された D-RAM controller であれは i8253 を D-RAM controller の一部に組み込むのを止めていると考えられます。i8253, i8237, 440BX North Bridge

bus_tree_to_i8253.png i8253.png

仕様

おおよその仕様は次の通りです。

項目仕様
動作環境PC-AT 仕様のパソコン、PentiumIII あるいはこれより新しいプロセッサを使用している
対象デバイスi8253 PIT channel1
デバイスの形態platform device
ドライバの機能read counter, write rate, readback rate (ドライバ内部に保存した値を読み出し)
ドライバの種類platform device driver
ドライバ実装形態Kernel に直接組み込む部分とモジュール部分に分割
Userland APIsysfs node

割り込み処理は実装しません。物足りないかもしれません。

仕様に書かれた内容を見ていきます。

platform device

platform device とは実行環境に固定的に接続され、ほかのデバイスの初期化をしなくても使えるデバイスです。多くの場合、プロセッサから出ているバスに直接接続されているか、初期化が不要または boot loader などで初期化が済んでしまっている BUS bridge を通して接続されているデバイスです。platform_device_register() で kernel に登録します。

on board の PCI 接続デバイスはどの様な扱いになるの?

/drivers/pci にある PCI bus ドライバ群で扱います。PCI bus bridge の初期化が必要なため PCI device として扱います。PCI bus のアドレス空間配置などを Linux kernel で都合よく扱えるように初期化し管理しています(pci_assign_resource()pci_reassign_resource())。PCI デバイスを発見して登録する処理 pci_device_add()、PCI デバイスのためのドライバを登録する処理 pci_register_driver() 辺りを手掛かりにコードを追いかけてみてください。

platform driver

platform driver は platform device のためのドライバです。platform_driver_register() で kernel に登録します。platform_device_register()platform_driver_register() それぞれの呼び出しでデバイスとドライバの組み合わせが見つかったならばドライバの probe 処理が呼ばれます。

kernel と module に組み込み場所を分割する

kernel に静的にリンクするコードと module として構成し動的にリンクするコードに分割して実装します。それぞれは次のように機能します。

実装機能
kernel に静的にリンクするコード i8253_ref_setup.ci8253 channel 1 を platform device として登録します。
/arch/x86/kernel に配置
kernel の Makefilei8253_ref_setup.c を kernel に静的に結合します。
/arch/x86/kernel/Makefile を修正
kernel の Kconfigi8253_ref_setup.c を組み込むかどうか make menuconfig で選択する。*_defconfig チェックします。
/arch/x86/Kconfig を修正
device header file i8253_control.hi8253 のレジスタを定義します。
include/linux に配置
driver header file i8253_ref.hplatform device の IO 空間配置と初期設定をドライバに渡す定義です。
include/linux に配置
module として構成したドライバ i8253_ref.ci8253 channel 1 をアクセスします。
上の Makefilei8253_ref.c を kernel object として構築します。

急に大規模な開発になった様に感じるかもしれません。Linux Kernel はデバイスとドライバを分けて扱っています。デバイスのためのコードとドライバのためのコードをそれぞれ書きます。

デバイスとドライバを一つのモジュール(ソースファイル)で同時に登録してはいけないの?

ドライバを作ろうとするデバイスのレジスタアドレスが固定的なのにわざわざファイルを分割してまで書くか? kernel の中を見回すと platform_create_bundle() の様に kernel にデバイスとドライバを同時に登録する処理が用意されています。/drivers/block/floppy.c の様にデバイスとドライバを同一のファイル内で kernel に登録する記述も見られます。ドライバを分割して作るまでもないと判断したら、手を抜いても良いかもしれません。

sysfs node を API にする

DEVICE_ATTR() を使い sysfs node を Userland から操作するための API にします。可能な操作は限定的です。単純な open-read-close, または open-write-close の流れに限定されます。一度の read, write で転送できる長さは PAGE_SIZE 以下です。i8253 のカウンタは単純な機能なのでこれで十分です。

汎用性が高い mknod(1)mknod(2) で作成した major, minor 番号を持ったファイルシステム上のノードを使った API は別の機会で扱おうと考えています。

デバイスの sysfs node

Linux kernel のデバイスとドライバの管理作法に従ってデバイスを kernel に登録すれば sysfs 上に対応するノードができます。sysfs は多くの linux で /sys に mount されています。 sysfs のツリーを辿ってデバイスを探してみましょう。

platform device は /sys/devices/platform の下にノードが並びます。デバイス名の一部または機能名の一部がディレクトリ名になって並んでいます。環境によりデバイスの有無は仮想的な物も含めて違うので、存在するディレクトリは増減します。shell command を操作して /sys/devices 以下のノードと /sys/devices/platform 以下のノードの一覧を出してみます。

~ $ cd /sys/devices
/sys/devices $ ls -la
total 0
drwxr-xr-x 16 root root 0 Jul 24 01:43 .
dr-xr-xr-x 13 root root 0 Jul 24 01:43 ..
drwxr-xr-x 10 root root 0 Jul 24 01:43 LNXSYSTM:00
drwxr-xr-x  3 root root 0 Jul 24 01:43 breakpoint
drwxr-xr-x  5 root root 0 Jul 24 01:43 cpu
drwxr-xr-x  5 root root 0 Jul 24 01:43 cstate_core
drwxr-xr-x  5 root root 0 Jul 24 01:43 cstate_pkg
drwxr-xr-x  3 root root 0 Jul 24 01:43 intel_bts
drwxr-xr-x  5 root root 0 Jul 24 01:43 msr
drwxr-xr-x 16 root root 0 Jul 24 01:43 pci0000:00
drwxr-xr-x 22 root root 0 Jul 24 01:43 platform
drwxr-xr-x 10 root root 0 Jul 24 01:43 pnp0
drwxr-xr-x  3 root root 0 Jul 24 01:43 software
drwxr-xr-x  9 root root 0 Jul 24 01:43 system
drwxr-xr-x  3 root root 0 Jul 24 01:43 tracepoint
drwxr-xr-x 18 root root 0 Jul 24 01:43 virtual
/sys/devices/platform $ cd platform
/sys/devices/platform $ ls -la
total 0
drwxr-xr-x 22 root root    0 Jul 24 01:43 .
drwxr-xr-x 16 root root    0 Jul 24 01:43 ..
drwxr-xr-x  3 root root    0 Jul 24 01:45 ACPI000C:00
drwxr-xr-x  4 root root    0 Jul 24 01:45 Fixed MDIO bus.0
drwxr-xr-x  3 root root    0 Jul 24 01:45 INT33FF:00
drwxr-xr-x  3 root root    0 Jul 24 01:45 INT33FF:01
drwxr-xr-x  3 root root    0 Jul 24 01:45 INT33FF:02
drwxr-xr-x  3 root root    0 Jul 24 01:45 INT33FF:03
drwxr-xr-x  3 root root    0 Jul 24 01:45 PNP0103:00
drwxr-xr-x  3 root root    0 Jul 24 01:45 PNP0C0C:00
drwxr-xr-x  3 root root    0 Jul 24 01:45 PNP0C0E:00
drwxr-xr-x  3 root root    0 Jul 24 01:45 alarmtimer
drwxr-xr-x  4 root root    0 Jul 20 11:14 coretemp.0
drwxr-xr-x  3 root root    0 Jul 24 01:45 efi-framebuffer.0
drwxr-xr-x  5 root root    0 Jul 24 01:45 i8042
drwxr-xr-x  3 root root    0 Jul 24 01:45 microcode
drwxr-xr-x  3 root root    0 Jul 24 01:45 pcspkr
drwxr-xr-x  2 root root    0 Jul 24 01:45 power
drwxr-xr-x  4 root root    0 Jul 24 01:45 reg-dummy
drwxr-xr-x  4 root root    0 Jul 24 01:45 serial8250
drwxr-xr-x  3 root root    0 Jul 24 01:45 snd-soc-dummy
-rw-r--r--  1 root root 4096 Jul 24 01:45 uevent
drwxr-xr-x  3 root root    0 Jul 24 01:45 vboxdrv.0

i8042 ディレクトリを見てみましょう。このディレクトリには PS/2 keyboard と mouse デバイスが対応します。/sys/devices/platform/i8042/serio0 ディレクトリの下にある bind_mode ノードのために kernel の中で対応している関数は serio_set_bind_mode(), serio_show_bind_mode()) です。description ノードに対応している関数は (serio_show_description() と description ノードに文字列 "i8042 KBD port" を設定しているのは i8042_create_kbd_port()) です。デバイスを制御、機能確認するためノードです。このようなノードの多くは shell から cat, echo command で読み出し、書き込みできる様に実装されています。手軽にスクリプトで動作確認・制御できる Userland API になっています。続く操作例はノードを読み出しているところです。

/sys/devices/platform $ cd i8042
/sys/devices/platform/i8042 $ ls -la
total 0
drwxr-xr-x  5 root root    0 Jul 24 01:45 .
drwxr-xr-x 22 root root    0 Jul 24 01:43 ..
lrwxrwxrwx  1 root root    0 Jul 24 01:53 driver -> ../../../bus/platform/drivers/i8042
-rw-r--r--  1 root root 4096 Jul 24 01:53 driver_override
-r--r--r--  1 root root 4096 Jul 24 01:53 modalias
drwxr-xr-x  2 root root    0 Jul 24 01:53 power
drwxr-xr-x  4 root root    0 Jul 24 01:53 serio0
drwxr-xr-x  4 root root    0 Jul 24 01:53 serio1
lrwxrwxrwx  1 root root    0 Jul 24 01:53 subsystem -> ../../../bus/platform
-rw-r--r--  1 root root 4096 Jul 24 01:53 uevent
/sys/devices/platform/i8042 $ cd serio0
/sys/devices/platform/i8042/serio0$ ls -la
total 0
drwxr-xr-x 4 root root    0 Jul 24 01:53 .
drwxr-xr-x 5 root root    0 Jul 24 01:45 ..
-rw-r--r-- 1 root root 4096 Jul 24 01:54 bind_mode
-r--r--r-- 1 root root 4096 Jul 24 01:54 description
--w------- 1 root root 4096 Jul 24 01:54 drvctl
-r--r--r-- 1 root root 4096 Jul 24 01:54 firmware_id
drwxr-xr-x 2 root root    0 Jul 24 01:54 id
-r--r--r-- 1 root root 4096 Jul 24 01:54 modalias
drwxr-xr-x 2 root root    0 Jul 24 01:54 power
lrwxrwxrwx 1 root root    0 Jul 24 01:54 subsystem -> ../../../../bus/serio
-rw-r--r-- 1 root root 4096 Jul 24 01:54 uevent
/sys/devices/platform/i8042/serio0 $ cat bind_mode
auto
/sys/devices/platform/i8042/serio0 $ cat description
i8042 KBD port

あちこちにある uevent node はどんな機能があるの?

書き込む (uevent_store()) と uevent を発行する (kobject_uevent_env()) 機能と、読み込む (uevent_show()) 機能が実装されています。読み込む機能は一部のノード、例えば /sys/class/input/* の下にある uevent ノードに実装 (input_dev_uevent()) されています。読み込む機能は uevent で発行される情報の一部をいつでも取得できる様になっています。
uevent は Userland の udev 機能が使っている socket 通信 API (AF_NETLINK.NETLINK_KOBJECT_UEVENT) に kernel から userland に向かう送信をし、最近のディストリビューションでは使われなくなりつつある /proc/sys/kernel/hotplug または /sys/kernel/uevent_helper に設定された uevent helper (uevent_helper) 実行ファイルを実行する (call_usermodehelper_setup(), call_usermodehelper_exec()) 機能です。実行ファイルは慣例的に /sbin/hotplug (CONFIG_UEVENT_HELPER_PATH) です。

i8253_ref 全体のデータ構造

次の図は i8253 PIT channel1 デバイスとこれを操作するためのドライバに関係するデータ構造です。デバイスとドライバのために最低限扱う必要がある範囲です。構造体のメンバに含まれるポインタ、内包する構造体を追えばもっと範囲は広がります。

i8253_ref_data_structure.png

うす青 i8253_ref_light_blue_color.png の構造体は Linux Kernel で定義された構造体です。うす橙 i8253_ref_light_orange_color.png の構造体はこのページに書かれた i8253 ドライバのために定義した構造体です。

"Prepared in i8253_ref_setup.c" と書かれた囲みの中が kernel の初期化処理に追加した静的なデータ構造です。ドライバを作る立場から見て platform device の仕様・特性を保持する構造体になります。

"Allocated in i8253_ref.c" と書かれた囲みの中がドライバの状態維持のため i8253_ref.c で動的確保するデータ構造です。ドライバ自身の状態、デバイスの状態のコピーなどを保持します。より多くの機能と処理を行うドライバは排他制御・同期・参照カウンタなどをメンバーとして持ちます。

device 構造体の driver_data メンバ getter/setter

device 構造体の driver_data メンバは getter (dev_get_drvdata()) と setter (dev_set_drvdata()) を使ってアクセスします。珍しく排他制御などの付加的な機能がない getter/setter があるメンバです。

i8253_ref_setup.c: kernel に platform device を登録する

kernel の初期化処理で i8253 を platform device として登録します。/arch/x86/kernel の下に i8253_ref_setup.c を新しく作成し、この中に実装します。

ディレクトリの下に配置したソースをコンパイル対象にする

C 言語で書いたソースをディレクトリに置いただけではコンパイルされません。少なくともそのディレクトリの Makefile を編集してコンパイル対象に加える様にします (/Documentation/kbuild/makefiles.txt)。Makefile の位置は一部例外的に上位ディレクトリに有るかもしれません。多くの場合、Kconfig ファイルを編集して make menuconfig または *_defconfig ファイルの指定にてコンパイル対象にするか選択できる様にします (/Documentation/kbuild/kconfig-language.txt)。この章の後半で詳しく触れます。

initcall

次のコード片は i8253_ref_setup.c の初期化処理部分です。

filei8253_ref_setup.c
Expand allFold all
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
-
|
!
 
-
|
|
|
-
|
|
|
|
!
|
!
 
 
 
/*
 * Register i8253 PIT platform device.
 */
static int __init i8253_ref_device_initcall(void)
{       int             ret;
 
        /* Register i8253 as platform device. */
        ret = platform_device_register(&i8253_ref_platform_device);
        if (ret != 0) {
                pr_err("%s: Can not register platform %s device. "
                       "ret=%d\n",
                        __func__, I8253_REF_DEVICE_NAME, ret
                );
        }
        return ret;
}
 
/* Add i8253_ref_device_initcall() to initialize function table. */
arch_initcall(i8253_ref_device_initcall);

kernel 初期化処理の時に呼ぶ関数を arch_initcall() で指定します。呼び出す順番を気にする場合は loader script (.lds) で厳密に制御する、既に存在する他の初期化処理コードを修正するなどして都合の良さそうな前後順になる様にして下さい。

arch_initcall() で指定した i8253_ref_device_initcall() の処理は platform_device_register() で i8253 デバイスを登録するだけです。エラーチェックをしています。しかし、エラーは発生しないはずです。ここでエラーになる場合は resource が衝突しているか、メモリが kernel 起動時から少ない場合です。

platform device と platform driver の繋がり

次のコード片は i8253_ref_device_initcall() で kernel に登録した i8253 device の情報です。IO ポート番号は resource を使って宣言し、i8253 デバイスの詳細な設定情報は platform_device のメンバ dev.platform_data で指した i8253_ref.h で定義した構造体 i8253_ref_platfrom_data に格納しています。 Kernel のデータ構造 resource, platform_device とドライバで定義したマクロとデータ構造 i8253_ref.h を参考に読んで見て下さい。

filei8253_ref_setup.c
Expand allFold all
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
-
|
!
-
-
|
|
|
!
-
|
|
|
!
!
 
-
|
!
-
|
|
!
 
-
|
|
!
-
|
|
|
|
|
-
|
!
!
 
-
|
/* i8253 PIT Refresh counter and control port resource.
 * @note name(s) are compared with argument name of platform_get_resource_byname().
 */
static struct resource i8253_ref_ports[] = {
        {       .name =         I8253_REF_RESOURCE_REFRESH_COUNTER,
                .start =        PIT_CH1,
                .end =          PIT_CH1,
                .flags =        IORESOURCE_IO,
        },
        {       .name =         I8253_REF_RESOURCE_CONTROL_WORD,
                .start =        PIT_MODE,
                .end =          PIT_MODE,
                .flags =        IORESOURCE_IO,
        },
};
 
/*
 * Platform parameters will be passed to driver.
 */
static struct i8253_ref_platfrom_data i8253_ref_ch1 = {
        .channel =      1,      /*!< Refresh counter channel. */
        .rate_default = I8253_REF_RATE_DEFAULT_KEEP,    /*!< Initial rate value. */
};
 
/*
 * Define i8253 PIT channel 1, refresh counter as platform device.
 * 
 */
static struct platform_device i8253_ref_platform_device = {
        .name =          I8253_REF_DEVICE_NAME, /*!< device name match to driver name. */
        .id =            PLATFORM_DEVID_AUTO,   /*!< automatic id numbering. */
        .id_auto =       true,                  /*!< automatic id numbering. */
        .num_resources = ARRAY_SIZE(i8253_ref_ports),
        .resource =      i8253_ref_ports,
        .dev = {        /*!< struct device. */
                .platform_data = &i8253_ref_ch1, /*!< pass platform parameter to driver. */
        },
};
 
/*
 * Register i8253 PIT platform device.

kernel に登録した情報の中に device と driver を結びつけるための情報が含まれています。I8253_REF_DEVICE_NAME です。この文字列をキーにして platform device と platform driver を対応づけます。resource 構造体の配列にも文字列 I8253_REF_RESOURCE_REFRESH_COUNTER, I8253_REF_RESOURCE_CONTROL_WORD が含まれています。これは $ cat /proc/ioports で表示される様になります。文字列をキーにして driver 側で I/O ポート番号を platform_get_resource_byname()で取得できます。デバイス固有のパラメータは platform_device.dev.platform_data で指した先に構造体 i8253_ref_platfrom_data を配置し、driver に渡します。

filei8253_ref.h
Expand allFold all
 25
 26
 27
 28
 29
 
 
 
 
 
#define I8253_REF_DEVICE_NAME   "i8253_ref"
 
#define I8253_REF_RATE_DEFAULT_KEEP             (~(uint32_t)0)
#define I8253_REF_RESOURCE_REFRESH_COUNTER      "refresh_counter"
#define I8253_REF_RESOURCE_CONTROL_WORD         "control_word"

i8253_ref_setup.c を kernel に静的に結合する

i8253_ref_setup.c を kernel に静的に結合する(スタティック・リンク)する方法を見ていきます。新しく作ったソース・ファイルを kernel にスタティック・リンクするのに最低限必要なことは、ソースを配置したディレクトリまたはその上流に辿る経路で近い場所にある Makefile を修正しコンパイル・リンクする対象に加えることです。加えてソースファイルから上流に辿る経路で近い場所にある Kconfig ファイルに新しく作ったソースをコンパイル・リンク対象にするのかどうか make menuconfig で選択できる様にします。

make menuconfig で新しく加えたソース・ファイルをコンパイル対象にするかどうか選択できる様にしておくと、チーム開発をしている状況での問題の切り分け、開発環境の足並みが揃っていない状況で柔軟に対応することができます。

Makefile

/arch/x86/kernel/Makefile の変更箇所は次の通りです。

filei8253_ref-kernel-Makefile/Makefile.diff
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
diff --git a/arch/x86/kernel/Makefile b/arch/x86/kernel/Makefile
index 9bcd0b5..077d68b 100644
--- a/arch/x86/kernel/Makefile
+++ b/arch/x86/kernel/Makefile
@@ -98,6 +98,7 @@ obj-$(CONFIG_PARAVIRT_CLOCK)  += pvclock.o
 obj-$(CONFIG_X86_PMEM_LEGACY)  += pmem.o
 
 obj-$(CONFIG_PCSPKR_PLATFORM)  += pcspeaker.o
+obj-$(CONFIG_I8253_REFRESH)    += i8253_ref_setup.o
 
 obj-$(CONFIG_X86_CHECK_BIOS_CORRUPTION) += check.o
 

obj-$(CONFIG_I8253_REFRESH) += i8253_ref_setup.o は CONFIG_I8253_REFRESH を展開した結果によって obj- += i8253_ref_setup.o または obj-y += i8253_ref_setup.o と解釈されます。ここでは出てきませんが CONFIG_I8253_REFRESH が tristate (モジュール) の場合は obj-m += i8253_ref_setup.o となる場合が有ります。obj-y += i8253_ref_setup.o となった場合、kernel に静的に結合します。

obj-* += file.o構築結果
obj- += file.ofile.c はコンパイルされない
obj-y += file.ofile.c は kernel に静的に結合(スタティックリンクされる)
obj-m += file.ofile.c はモジュールとして構築され file.ko が作られる (このページでは扱いません)

Kconfig

arch/x86/Kconfig の変更箇所は次の通りです。この Kconfig の位置は例外的に Makefile と ソースファイル i8253_ref_setup.c が有るディレクトリと違っています。この修正で make menuconfig で i8253_ref_setup.c を kernel に組み込むかどうか前節の Makefile の修正と併せ選択することができる様になります。詳細な書き方は /Documentation/kbuild/kconfig-language.txt を参照して下さい。

filei8253_ref-kernel-kconfig/Kconfig.diff
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index 226d569..1074f0f 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -759,6 +759,14 @@ config APB_TIMER
          as it is off-chip. APB timers are always running regardless of CPU
          C states, they are used as per CPU clockevent device when possible.
 
+config I8253_REFRESH
+       bool "I8253 PIT Refresh timer driver"
+       default n
+       depends on X86
+       help
+         Access I8253 refresh timer.
+         Linux Driver Quest tutorial platform device.
+
 # Mark as expert because too many people got it wrong.
 # The code disables itself when not needed.
 config DMI

config I8253_REFRESH で選択項目を宣言します。ここでは接頭辞 CONFIG_ を付けずに書くことに注意して下さい。bool は y か n (定義しない)の選択だと言う意味です。default は n (定義しない)、 depends on X86 は X86(CONFIG_X86) が定義されれば選択項目に現れることを意味します。help は make menuconfig で help を要求したときに表示されるテキストです。他の項目で ---help--- と書かれているところもあります。どちらも同様に解釈されます。help の部分はインデントに意味があります。他の部分のインデントも慣例的に合わせて書くことをお勧めします。

追加した config 項目は make menuconfig のどこに現れるの?

make menuconfig を実行して追加した項目がどこに行ったか分からなくなるかもしれません。手っ取り早く知るには make menuconfig で対話的設定を開始したところで / を押してマクロ名を検索します。このページの例では I8253_REFRESH を探す文字列とします。Location: 以下に場所のパスが示されます。丁寧に Kconfig の階層構造を探すより早いです。

他の architecture や platform の場合、どこにソース・コードを追加して、どこの Makefile や Kconfig の修正をしたら良いの?

開発ターゲットの基板に実装されたテバイスを追加するために /arch/processor 以下に配置されたファイルを修正したり、追加するやり方に迷うかもしれません。ディレクトリ構成はプロセッサ(CPU)毎に大きく違います。見回してみて、しっくりする修正方法を考えてください。似たようなデバイスを見つけて並べるように修正するのが良いでしょう。他のプロセッサに比べて派生品種が多い ARM 系では /arch/arm/plat-platform に SoC 毎に派生するコード、/arch/arm/mach-machine に基板品種毎に派生する(付加した周辺回路によって派生する)コードが含まれることが多いです(SoC 毎に派生するコードもあります)。

i8253_ref.c: i8253 をアクセスするドライバ

i8253_ref.c がソースコード全体です。

init

モジュールを初期化する処理から見ていきましょう。module_init() マクロに初期化処理関数 i8253_ref_init() を指定します。これは HelloWorld モジュールと同じです。printk() で関数が呼び出されたことを表示します。デバッグ目的です。platform_driver_register() で platform driver を登録します。

filei8253_ref.c
Expand allFold all
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
-
|
|
|
!
 
-
|
|
-
|
|
|
|
!
|
!
/*
 * Tutorial i8253 refresh platform device driver init.
 * @return int == 0: Success. 
 *             < 0:  Fail, negative errno number.
 */
static int __init i8253_ref_init(void)
{       int     ret;
        printk(KERN_INFO "%s: Called.\n", __func__);
        ret = platform_driver_register(&i8253_ref_driver);
        if (ret != 0) {
                /* Can not register driver. */
                printk(KERN_ERR "%s: Can not register driver. ret=%d\n",
                        __func__, ret
                );
        }
        return ret;
}
filei8253_ref.c
Expand allFold all
445
446
 
 
module_init(i8253_ref_init);
module_exit(i8253_ref_exit);

登録するドライバ i8253_ref_driver の内容を見てみましょう。platform_driver.device_driver driver.name に文字列 I8253_REF_DEVICE_NAME を指定しています。この文字列は platform_device_register で登録したデバイスの名前と照合され、一致していれば platform_driver の .probe に指定した関数 i8253_ref_probe() が呼ばれます。

filei8253_ref.c
Expand allFold all
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
-
|
!
 
-
|
|
!
 
 
-
|
!
-
-
|
|
|
|
!
|
|
|
!
/*
 * Suspend and resume power event handler table.
 */
#if (defined(CONFIG_PM))
struct dev_pm_ops i8253_ref_pm = {
        .suspend = i8253_ref_suspend,
        .resume = i8253_ref_resume,
};
#endif /* (defined(CONFIG_PM)) */
 
/*
 * i8253 PIT refresh counter platform driver structure.
 */
struct platform_driver i8253_ref_driver = {
        .driver = {
                .name = I8253_REF_DEVICE_NAME,  /*!< will be matched to device name. */
#if (defined(CONFIG_PM))
                .pm = &i8253_ref_pm,    /*!< power manage methods. */
#endif /* (defined(CONFIG_PM)) */
        },
        .probe = i8253_ref_probe,       /*!< device present or plugged. */
        .remove = i8253_ref_remove,     /*!< device or driver removed. */
        .shutdown = i8253_ref_shutdown, /*!< going halt. */
};

platform_driver のメソッド(関数テーブル)

platform_driver のメンバのいくつかは関数を指すポインタです。このページに出てきた関数を指すメンバ(メソッド)を次の表に示します。

メンバ変数変数が指した先の関数機能
probe対応するデバイスが見つかった(kernel に{登録されていたら | 登録されたら})呼び出される。一般的なドライバはレジスタやデバイスのメモリをマップ(bus 直結のデバイス)、デバイスを存在確認、ドライバ状態領域確保、thread または work queue などの worker context 初期化・起動、割り込みハンドラ設置、初期化などをする。
remove対応するデバイスが外された(kernel から削除された)、または、ドライバが削除されたら呼び出される。一般的なドライバはデバイスを停止する(外された場合はアクセスできないので注意が必要)、デバイスからの割り込みを止めてハンドラを外す、確保した起動した thread, work queue などのコンテキスト停止・終了、ドライバ状態領域解放、メモリマップ解除(bus 直結のデバイス)を行い probe 前の状態にする。
shutdownshutdown の時に呼び出される。一般的なドライバはほぼ remove の時と同じ処理をする。電池動作機器の場合、デバイスに供給されている電源を遮断、クロックを停止などの追加的な停止処理を行う場合もある。
driver->pm.suspendsuspend する(standby または hibernate 状態に入る)時に呼び出される。一般的なドライバは電源供給停止、クロック停止または低周波数化などの電力管理、wakeup 準備、不意な割り込みを停止するなどを行う。
driver->pm.resumeresume する(standby または hibernate 状態から出る)時に呼び出される。一般的なドライバは電源供給再開、クロック供給または常用周波数に設定するなどの電力管理、wakeup 完了処理、割り込みを再開するなどを行う。

ほかにも i8253_ref driver で出てこなかった関数を指すメンバが platform_driver.device_driver driver->dev_pm_ops pm の中に多くあります。これらの中に system call の read(), write(), open(), close()(release) 機能を実装している関数を指すポインタはありません(代表的で原始的な構造体は file_operations)。userland からデバイスをアクセスするための API は別の kernel 内関数を使って登録します。Userland から操作する機能を全く提供しないデバイス・ドライバも作れます。例えばハードウエア・シーケンサの代わりの様な house keeping (実行環境の維持) をするだけのドライバです。

driver->pm.suspend と suspend メンバのどちらを使えば良いの?

platform_driverdevice_driver driver->dev_pm_ops pm のメンバを見てみると、それぞれに suspend メンバがあります。 driver->pm.suspend を使った方か良いと考えられます。platform_pm_suspend(), platform_pm_resume() が suspend, resume 処理で通過する関数です。実装を見てみると driver->pm.suspend が NULL でなければ suspend を呼び出す様になっています。resume も同様です。platform_driver の suspend, resume メンバが指している関数を呼ぶ処理はそれぞれ platform_legacy_suspend(), platform_legacy_resume() という名前になっています。旧式だという扱いです。Linux Kernel ではこういった古い方式の処理は書き換えられ、廃止されることがあります。Userland 向けの API を頑なに維持するのとは対照的です。

probe

probe 処理 i8253_ref_probe() を見ていきます。i8253_ref_driver.probe が指している関数です。 platform_driver_register() で対応するデバイスが見つかったならば呼ばれます。kernel に組み込んだ初期化処理 i8253_ref_setup.c にて既に platform_device_register() で name=I8253_REF_DEVICE_NAME となっているデバイスは登録済みです。ですので platform_driver_register() を呼び出すと直ちに probe 関数が呼ばれます。i8253_ref_probe() の back trace を取得すると platform_driver_register() からいくつかの関数を経由して呼ばれた様に見えます。

filei8253_ref.c
Expand allFold all
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
-
|
|
|
|
!
 
-
|
|
|
|
|
|
|
|
|
|
|
-
|
|
|
!
|
/*
 * Probe i8253 PIT refresh counter.
 * @param pdev points platform_device structure.
 * @return int 0:  Success, \
 *             <0: Error, negative errno number.
 */
int i8253_ref_probe(struct platform_device *pdev)
{       struct device                   *dev;
        struct resource                 *res;
        struct i8253_ref                *iref;
        struct i8253_ref_platfrom_data  *pdata;
        uint32_t                        rate_default;
        int                             ret;
 
        ret = 0;
        printk(KERN_INFO "%s: Called. pdev=0x%p, dev=0x%p\n",
                __func__, pdev, &(pdev->dev)
        );
        iref = kzalloc(sizeof(*iref), GFP_KERNEL);
        if (!iref) {
                printk(KERN_ERR "%s: Not enough memory.\n", __func__);
                ret = -ENOMEM;
                goto err;
        }
        res = platform_get_resource_byname(pdev, IORESOURCE_IO, I8253_REF_RESOURCE_REFRESH_COUNTER);

probe 処理の順番はデバイスの挙動、確保するリソース同士の依存関係によって緻密に考える必要があります。i8253_ref ドライバではドライバ状態 i8253_ref を確保する処理から始めました。次に resource から I/O ポート番号を取得して i8253_ref に保持する処理をします。メモリ・マップド・デバイスの場合は、物理メモリ空間を(kernel 内の)仮想アドレスへマップする処理 (例えば devm_ioremap_resource(), devm_request_mem_region(), ioremap(), ioremap_nocache() などを呼び出す処理) をする所です。

filei8253_ref.c
Expand allFold all
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
 
-
|
|
|
|
|
!
-
|
!
 
 
 
-
|
|
|
|
|
!
-
|
!
 
        res = platform_get_resource_byname(pdev, IORESOURCE_IO, I8253_REF_RESOURCE_REFRESH_COUNTER);
        if (!res) {
                printk(KERN_ERR "%s: Can not found resource. name=%s\n",
                        __func__, I8253_REF_RESOURCE_REFRESH_COUNTER
                );
                ret = -ENODEV;
                goto err;
        }
        /* @note we skip request_resource(),
           drivers/base/platform.c:platform_device_add() claims resource.
        */
        iref->refresh_counter = res->start;
 
        res = platform_get_resource_byname(pdev, IORESOURCE_IO, I8253_REF_RESOURCE_CONTROL_WORD);
        if (!res) {
                printk(KERN_ERR "%s: Can not found resource. name=%s\n",
                        __func__, I8253_REF_RESOURCE_CONTROL_WORD
                );
                ret = -ENODEV;
                goto err;
        }
        /* @note we skip request_resource(),
           drivers/base/platform.c:platform_device_add() claims resource.
        */
        iref->control_word = res->start;

platform_device_register() で登録したデバイスのパラメータを platform_data メンバが指す先から取り出します。都度ポインタを辿るのが面倒なのでドライバ状態 i8253_ref のメンバ変数にコピーを保持します。rate 初期値指定がある場合 (rate_default != I8253_REF_RATE_DEFAULT_KEEP) は分周比レジスタ(ここでは rate register と呼びます)へ値を設定します。rate register を読み出すことはできないので、ドライバ状態として rate_saved に書き込んだ値を保持します。

filei8253_ref.c
Expand allFold all
315
316
317
318
319
320
321
322
323
324
325
326
327
 
 
-
|
|
|
!
 
 
-
|
!
 
        dev = &(pdev->dev);
        pdata = dev->platform_data;
        if (!pdata) {
                printk(KERN_ERR "%s: Missing platform data.\n", __func__);
                ret = -ENODEV;
                goto err;
        }
        iref->channel = pdata->channel;
        rate_default = pdata->rate_default;
        if (rate_default != I8253_REF_RATE_DEFAULT_KEEP) {
                i8253_ref_rate_write(iref, rate_default);
        }
        iref->rate_saved = iref->rate_default = rate_default;

dev_set_drvdata()device 構造体にドライバが確保したコンテキスト(排他状態、デバイスの状態、ステートなど)を保持する構造体を指すポインタを設定する関数です。設定したポインタを取得する dev_get_drvdata() と対になっています。

filei8253_ref.c
Expand allFold all
328
 
        dev_set_drvdata(dev, iref);

/drivers/base を主要な処理とする Kernel の中ではデバイスを device 構造体で扱います。Kernel がドライバに call back をする時に、引数あるいは間接的に渡される情報は device 構造体を指すポインタです。この中からドライバが確保したコンテキストを取り出すために dev_get_drvdata() を使います。

Userland 向け API を sysfs node に作ります。DEVICE_ATTR() マクロでノードのデータ構造を作ります。DEVICE_ATTR() は _name 引数(ここでは counter と rate)に接頭辞 dev_attr_ を付けて device_attribute 型の構造体変数を宣言します。このようにいくつかのマクロは接頭辞を付けて変数を宣言するものがあります。Linux Kernel ソース・コードを grep で追いにくくしています。宣言されているはずの変数が見つからない場合、接頭辞部分と思われる部分を除いて検索してみると見つかる可能性が高くなります。

S_IRUGO, S_IWUSR などはノードのパーミッションです。system call の chmod() と同様です。i8253_ref_counter_show, i8253_ref_rate_show, i8253_ref_rate_store は *_show が read() system call に対応する関数、*_store() が write() system call に対応する関数です。_show, _store に付いては後で詳しく触れます。

filei8253_ref.c
Expand allFold all
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
-
|
|
!
 
 
 
-
|
|
!
-
|
|
|
!
 
-
|
!
-
|
!
/*
 * Define device attributes.
 * device attribute appears as sysfs node in device directory.
 */
DEVICE_ATTR(counter, S_IRUGO, i8253_ref_counter_show, NULL);
DEVICE_ATTR(rate,    S_IWUSR | S_IRUGO, i8253_ref_rate_show, i8253_ref_rate_store);
 
/*
 *  Device attribute entries.
 *  @note DEVICE_ATTR creates structure dev_attr_##name
 */
static struct attribute *i8253_ref_attrs[] = {
        &dev_attr_counter.attr,
        &dev_attr_rate.attr,
        NULL,
};
 
/*
 * Device attribute group.
 */
static struct attribute_group i8253_ref_group = {
        .attrs = i8253_ref_attrs,
};

配列 attribute*[] の要素を DEVICE_ATTR() で作ったデータ構造の attr メンバを指すポインタで構成し NULL 終端します。さらに attribute_group 構造体の attrs メンバで配列 attribute*[] を指します。多段のポインタリンクを構成する理由は、手軽な Kernel 内 API sysfs_create_group() を使うためです。sysfs_create_group() を呼び出すと sysfs の /sys/devices/platform/i8253_ref.0.auto ディレクトリの下に counter, rate ノードが作られます。

filei8253_ref.c
Expand allFold all
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
 
-
|
|
|
!
 
 
 
 
 
 
 
 
 
 
!
        ret = sysfs_create_group(&(dev->kobj), &i8253_ref_group);
        if (ret != 0) {
                dev_err(dev, "Can not create sysfs nodes.\n");
                dev_set_drvdata(dev, NULL);
                goto err;
        }
        dev_info(dev, "Probed. refresh_counter=0x%lx, control_word=0x%lx, channel=0x%x\n",
                iref->refresh_counter, iref->control_word, iref->channel
        );
        dev_info(dev, "Current state. rate_default=0x%lx, counter=0x%.4x\n",
                (unsigned long)rate_default, i8253_ref_counter_read(iref)
        );
        return ret;
err:
        kfree(iref);
        return ret;
}

ノードを作った直後から Userland のアプリケーションからアクセスされる可能性が有ることに注意して下さい。デバイスの初期化が未完了だったり、定常動作に入るまで時間が掛かったり、ドライバが起動した thread や work queue などの worker が十分に処理可能な状態で無い場合、異常な値をアプリケーションに返したり、デバイスを異常状態に遷移させる可能性が無いようにして下さい。

read and write

ドライバにとって必須の機能「デバイスをアクセスする」を見ていきます。レジスタを read, write し「デバイスの状態を取得する」、「デバイスの動作を変える」機能です。PC を構成する i8253 には 3 つのカウンタが収められています。シングルプロセッサ構成で使うことを想定した 8bit CPU 向けに作られた設計が古いデバイスです。不可分なアクセス手順があります。周期割り込み機能を持つ Channel 0 のドライバ /drivers/clocksource/i8253.c と BEEP 周波数設定機能を持つ Channel 2 のドライバ /sound/drivers/pcsp, /drivers/input/misc/pcspkr.c と他いくつかのドライバが一連のアクセス手順を不可分で実行できるように排他制御が必要です。spin lock i8253_lock をロック raw_spin_lock_irqsave()、アンロック raw_spin_unlock_irqrestore() します。

spin_lock_irqsave() と spin_unlock_irqrestore() を使うサンプルを作りたかった

raw_spin_lock_irqsave(), raw_spin_unlock_irqrestore() が使われるのは希です。他での i8253_lock の使われ方に併せて raw_*() を使いました。チェック機能が付いた spin_lock_irqsave(), spin_unlock_irqrestore() を使うのか普通です。spin_lock_irqsave() マクロの定義を見ると raw_spin_lock_irqsave() を包んでいることが分かります。

read counter

次の i8253_ref_counter_read() 関数は i8253 の counter を読み出す処理です。outb_p(), inb_p(), inb() が i8253 の IO ポートをアクセスする関数です。制御の詳細は i8253 または i8254 のデータシートを参照してください。iref->control_word ポートに制御対象の channel、アクセス手順、モードを設定します。その後、iref->refresh_counter ポートから LSB 8 ビット、MSB 8 ビットの順で値を読みます。i8253 は低速なデバイスです。ポートを読み書きする間隔を開ける必要があります。間隔が短い場合、i8253 の内部状態が十分に遷移せず、予測できない動作をします。待ち時間を入れる _p 付きの関数 outb_p(), inb_p() を使っています。

filei8253_ref.c
Expand allFold all
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
-
|
|
|
!
 
-
|
|
|
|
|
|
|
|
|
|
|
|
!
/*
 *  Read i8253 counter.
 *  @param  iref points driver state.
 *  @return uint16_t counter value.
 */
uint16_t i8253_ref_counter_read(struct i8253_ref *iref)
{       unsigned long   flags;
        uint16_t        counter;
 
        raw_spin_lock_irqsave(&i8253_lock, flags);
        outb_p(   I8253_CONTROL_WORD_SCX_CH(iref->channel)
                | I8253_CONTROL_WORD_RLX_LATCH
                | I8253_CONTROL_WORD_MX_RATE_GENERATOR
                , iref->control_word
        );
        counter =  (inb_p(iref->refresh_counter)) << 0x0;
        counter |= (inb(iref->refresh_counter)) << 0x8;
        raw_spin_unlock_irqrestore(&i8253_lock, flags);
        return counter;
}

outb_p() の実装はどこにあるの?

outb_p() のリンク先を辿っても、待ち処理が入った IO ポート出力処理は見つかりません。x86 と x86-64 アーキテクチャの場合はどこに実装されているのか? outb_p() の実装はどこにあるの? に探した時の記録をまとめてあります。Linux Kernel ソース・コードを追っていくときの参考にもなるでしょう。

write rate

次の i8253_ref_rate_write() 関数は i8253 の rate (分周比)に書き込む処理です。手順は counter を読み出す処理とほぼ同様です。inb_p(), inb() 関数に代えて outb_p(), outb() を使用しています。

filei8253_ref.c
Expand allFold all
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
-
|
|
|
!
 
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
!
/*
 *  Write i8253 counter.
 *  @param iref points driver state.
 *  @param rate Refresh counter divider rate.
 */
void i8253_ref_rate_write(struct i8253_ref *iref, uint16_t rate)
{       unsigned long   flags;
 
        raw_spin_lock_irqsave(&i8253_lock, flags);
        outb_p(   I8253_CONTROL_WORD_SCX_CH(iref->channel)
                | I8253_CONTROL_WORD_RLX_LSB_MSB
                | I8253_CONTROL_WORD_MX_RATE_GENERATOR
                , iref->control_word
        );
        outb_p((rate >> 0x0) & 0xff
                , iref->refresh_counter
        );
        outb((rate >> 0x8) & 0xff
                , iref->refresh_counter
        );
        raw_spin_unlock_irqrestore(&i8253_lock, flags);
}

show and store

sysfs_create_group() で登録した counter, rate ノードの getter, setter 関数の実装を見ていきます。DEVICE_ATTR() マクロの第 3 引数が getter 関数です。慣習的に _show という接尾辞を付けます。第 4 引数が setter 関数です。慣習的に _store という接尾辞を付けます。

filei8253_ref.c
Expand allFold all
176
177
178
179
180
181
-
|
|
!
 
 
/*
 * Define device attributes.
 * device attribute appears as sysfs node in device directory.
 */
DEVICE_ATTR(counter, S_IRUGO, i8253_ref_counter_show, NULL);
DEVICE_ATTR(rate,    S_IWUSR | S_IRUGO, i8253_ref_rate_show, i8253_ref_rate_store);

i8253_ref_counter_show() は read() system call に対して返す文字列を構築する関数です。文字列の格納先は buf 引数が指す先になります。NUL 終端を含まない文字列の最大長は PAGE_SIZE - 1 です。NUL 終端込みで PAGE_SIZE です。

DEVICE_ATTR() の _show() 関数(メソッド)で返せる文字列の長さを PAGE_SIZE で決めているのは何処?

DEVICE_ATTR() の _show() 関数が返せる文字列の長さを決めているのは dev_attr_show() です。_show() 関数が返した値をチェックしています。本当に buf が指している先に NUL 終端込みで PAGE_SIZE バイトの領域は確保されているのか? file system に近い側の関数をたどると sysfs_kf_seq_show() で呼び出す前に領域を memset で PAGE_SIZE バイト 0x00 で fill しています。さらに file system 近い側 sequential read を実装している部分 seq_read()seq_buf_alloc() を呼び出し PAGE_SIZE バイト確保しています。seq_read() 内では条件分岐で traverse() を呼び出す経路もあります。こちらに実行が進むのは open() 直後に lseek() する場合のはずです。利点のない使い方になるはずです。
Kernel ソースの読みを深く進めると kernfs_seq_next() が常に NULL を返すことから、open() 直後の read() を終わらせた時点で EOF に達し、buf が指した領域に継ぎ足すように Userland に返す文字列を伸ばしたり、領域の伸長が行われないことも確認できます。

filei8253_ref.c
Expand allFold all
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
-
|
|
|
|
|
|
|
!
 
 
-
|
|
-
|
|
!
|
!
/*
 *  counter node: show (user does read())
 *  @param dev points device context.
 *  @param attr points device attribute node.
 *  @param buf points buffer to store read stream.
 *  @return ssize_t >=0: bytes in buffer, \
 *                  <0: error, negative errno number.
 *  @note fs/kernfs/sysfs allocates buffer which length is PAGE_SIZE.
 */
ssize_t i8253_ref_counter_show(struct device *dev,
        struct device_attribute *attr, char *buf)
{       struct i8253_ref        *iref;
 
        iref = dev_get_drvdata(dev);
        if (!iref) {
                printk(KERN_ERR "%s: Driver data gnoe away.\n", __func__);
                return  -ENODEV;
        }
        return snprintf(buf, PAGE_SIZE, "%u\n", (unsigned)i8253_ref_counter_read(iref));
}

dev 引数は device 構造体を指します。そこから dev_get_drvdata() でドライバが確保した i8253_ref 構造体を指すポインタを取り出し、返そうとする文字列を構築するための情報を取り出します。改行を含めるかどうかは任意です。1 行で済ませるのか複数行で構成するかも任意です。cat コマンドで読んだ場合の利便性を考慮すると文字列の最後は改行にしたほうが良いでしょう。

filei8253_ref.c
Expand allFold all
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
-
|
|
|
|
|
|
|
!
 
 
-
|
|
-
|
|
!
-
|
|
!
|
!
/*
 *  rate node: show (user does read())
 *  @param dev points device context.
 *  @param attr points device attribute node.
 *  @param buf points buffer to store read stream.
 *  @return ssize_t >=0: bytes in buffer, \
 *                  <0: error, negative errno number.
 *  @note fs/kernfs/sysfs allocates buffer which length is PAGE_SIZE.
 */
ssize_t i8253_ref_rate_show(struct device *dev,
        struct device_attribute *attr, char *buf)
{       struct i8253_ref        *iref;
 
        iref = dev_get_drvdata(dev);
        if (!iref) {
                printk(KERN_ERR "%s: Driver data gnoe away.\n", __func__);
                return  -ENODEV;
        }
        if (iref->rate_saved == I8253_REF_RATE_DEFAULT_KEEP) {
                /* Read back value is not available. */
                return snprintf(buf, PAGE_SIZE, "-1\n");
        }
        return snprintf(buf, PAGE_SIZE, "%lu\n", (unsigned long)(iref->rate_saved));
}
filei8253_ref.c
Expand allFold all
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
-
|
|
|
|
|
|
|
|
!
 
 
 
-
|
|
|
|
-
|
|
!
|
|
-
|
|
!
-
|
|
!
-
|
|
!
|
|
|
!
/*
 *  rate node: store (user does write())
 *  @param dev points device context.
 *  @param attr points device attribute node.
 *  @param buf points buffer filled with write stream.
 *  @param count write stream bytes in buffer.
 *  @return ssize_t >=0: bytes read from buffer, \
 *                  <0: error, negative errno number.
 *  @note kernfs/sysfs terminates write stream by '\0'.
 */
ssize_t i8253_ref_rate_store(struct device *dev,
        struct device_attribute *attr,
        const char *buf, size_t count)
{       char                    *p2;
        unsigned long           rate;
        struct i8253_ref        *iref;
 
        iref = dev_get_drvdata(dev);
        if (!iref) {
                printk(KERN_ERR "%s: Driver data gnoe away.\n", __func__);
                return  -ENODEV;
        }
        p2 = NULL;
        rate = simple_strtoul(buf,&p2,0);
        if (!p2) {
                /* simple_strtoul doesn't work. */
                return -EINVAL;
        }
        if ((unsigned)(*p2) >= ' ') {
                /* Terminated with some invalid character. */
                return -EINVAL;
        }
        if (rate > 0xffff) {
                /* Too large value. */
                return -EINVAL;
        }
        i8253_ref_rate_write(iref, rate);
        iref->rate_saved = rate;
        return (__force ssize_t)count;
}

suspend and resume

filei8253_ref.c
Expand allFold all
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
-
|
|
|
|
!
 
-
|
|
|
-
|
|
|
!
|
-
|
|
!
|
|
|
|
|
!
/*
 * Handle event suspend.
 * @param dev points struct device.
 * @return int ==0: Success \
 *             <0:  Fail, Negative errno number.
 */
int i8253_ref_suspend(struct device *dev)
{       uint16_t                counter;
        struct i8253_ref        *iref;
 
        counter = 0;
        if (!dev) {
                /* not available device. */
                printk(KERN_ERR "%s: Invalid argument.", __func__);
                return -EINVAL;
        }
        iref = dev_get_drvdata(dev);
        if (!iref) {
                /* not available private context. */
                goto out;
        }
        counter = i8253_ref_counter_read(iref);
 
out:
        dev_info(dev, "Suspended. counter=0x%.4x\n", counter);
        return 0;
}
filei8253_ref.c
Expand allFold all
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
-
|
|
|
|
!
 
-
|
|
|
-
|
|
|
!
|
-
|
|
!
|
|
|
|
!
/*
 * Handle event resume.
 * @param dev points struct device.
 * @return int ==0: Success \
 *             <0:  Fail, Negative errno number.
 */
int i8253_ref_resume(struct device *dev)
{       uint16_t                counter;
        struct i8253_ref        *iref;
 
        counter = 0;
        if (!dev) {
                /* not available device. */
                printk(KERN_ERR "%s: Invalid argument.", __func__);
                return -EINVAL;
        }
        iref = dev_get_drvdata(dev);
        if (!iref) {
                /* not available private context. */
                goto out;
        }
        counter = i8253_ref_counter_read(iref);
out:
        dev_info(dev, "Resumed. counter=0x%.4x\n", counter);
        return 0;
}

exit

filei8253_ref.c
Expand allFold all
435
436
437
438
439
440
441
442
443
-
|
|
!
 
-
|
|
!
/*
 * Tutorial i8253 refresh platform device driver exit (removing module).
 * @return void
 */
static void __exit i8253_ref_exit(void)
{       printk(KERN_INFO "%s: Called.\n", __func__);
        /* platform_driver_unregister will call _remove driver method. */
        platform_driver_unregister(&i8253_ref_driver);
}

トップ   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS