EPICS record/device/driver

EPICS
レコード/デバイス/ドライバ
サポート
[email protected],
many slides copied from
[email protected]
ハードウェアとの接続
General Idea
Software
EPICS
IOC Core: Db, CA, …
Record Support
“Driver”
Device Support
Driver Support
Hardware
Hardware
どこを拡張するのか…

よくある場合:新しいハードウェア (I/O Board,..)



時々:特定目的レコード


ドライバ:ハードウェアに直接アクセスする低レベルのソフト
ウェア。EPICSについて何も関知しない。
デバイス:EPICS特有のドライバレコード(のサブセット)を
つなぐ糊ソフトウェア
既存のレコードサポートを少し変えてつくる。
まれに: 新しいレコードタイプ

SNLや既存のレコードを組み合わせてできないだろうか?
ドライバ/デバイス/レコード


これらについて何かしようと考える前に、“IOC
Application Developer’s Guide” を読むこと !
共通の考え方:





新しいドライバ/デバイス/レコードをDBDファイル(Database
Description File, ASCII)に記述する
機能を実装し、ドライバ/デバイス/レコードサポートに特有な関数
テーブルを用意する。
関数テーブルを外部参照とするコンパイル済みバイナリを
Link/loadする。(関数自体はstaticな関数となる)
初期化時にiocCore/iocshはDBDファイルを解釈し、関数
テーブルを設置し、適切な関数を実行する。
新しいサポートを追加するためのインタフェースはよく定
義されており、再コンパイルは最小限でよい。
ドライバサポート

ドライバは一般に複雑になる:


Bus-level access, critical timing, interrupts,
semaphores, threads, deadlocks, fail-safe, OSspecific (vxWorks, Linux, Win32), …
典型的な関数の集合体:
Check, report, init, read, write,
setup_trigger(callback),…

“EPICS part”: オプショナル & 自明!
ドライバ・サポート・エントリ・テー
ブル(DRVET)
/* EPICS Base include file <drvSup.h> */
typedef long (*DRVSUPFUN) ();
struct drvet
{
long
number; /*number of support routines*/
DRVSUPFUN report; /*print report*/
DRVSUPFUN init;
/*init the driver */
};

どの関数ポインタもNULLとすることができる。
DRVET の例
/* xy.c */
#include<drvSup.h>
static long xy_report()
{
printf(“XY Driver Info:\n);
…
}
static long xy_init()
{
if (xy_check()) {
…
}
struct drvet drvXy =
{
2,
xy_report,
xy_init
};
EPICSへのドライバの登録

EPICS DBD File への登録


driver(drvXy)
結果:


EPICS iocInitは”drvXy”をシンボルテーブル
から探し出し、その中の”init”ルーチンを呼び
出す。
VxShell/iocshでdbior を使うと
“report”ルーチンを実行する。
よい習慣


Wrong:2枚の XY boardがあり一枚のbase
addressは 0x1234 でもう一枚は 0x4567二ある
と仮定し、ドライバプログラムに書き込んだ。
Best: “configure” ルーチンを用意し、
vxWorks/iocshのスタートアップファイルでiocInit
が実行される前にこの”conifugre”関数を実行す
る。
# EPICS records that refer to XY #0 will
# access board at base addr. 0xfe12
# in mode 15
xy_config(0, 0xfe12, 15)
デバイス サポート


レコードとドライバをつなぐ糊ソフトウェアでレコード型それぞれに特有のも
のとなる。(デバイス型xレコード型):
 AI record, DTYP=“XY”, INP=“#C0 S5”:
デバイスサポートは”XY”のドライバをカード#0にたいして呼び出し、信
号#5を読み出してレコードのRVALに収める。
 デバイス・サポートの名前は、debAiSoft.cなどのように
dev<RecType><Device>.cとするのが慣習。
すべてのレコードに共通なDev.Sup. 関数:
 Report: 情報を表示
 Init: 初期化、一度だけ呼ばれる。
 Init_Record: レコード毎に呼ばれる。
 Get I/O Interrupt Info: SCAN=“I/O Intr”で使われる。
これらは省略可能であるが、それぞれのレコード型についてチェックが必
要。
デバイス・サポート・エントリ・
テーブル(DSET)
/* Defined in devSup.h */
struct dset
{
long
number; /* number of support routines */
DEVSUPFUN report; /* print report*/
DEVSUPFUN init;
/* init support*/
DEVSUPFUN init_record; /* init particular record */
DEVSUPFUN get_ioint_info; /* get I/O Intr. Info */
/* Rest specific to record, e.g. BI: */
DEVSUPFUN
read_bi;
/* Result: (0,2,error)
0 -> raw value stored in RVAL, convert to VAL
2 -> value already stored in VAL, don’t convert
*/
}
デバイス・サポートの登録

EPICS DBD ファイル:


device(ai,INST_IO,devAiXX,“My XX")
結果: iocCore …


DTYP=“My XX” を ai recordsで指定できる。
DSET “devAiXX” をシンボル・テーブルから見
つけ出し、init()関数を実行し、それぞれのレ
コードについてinit_record()を実行する。 レ
コードが「処理」される毎にread()関数が実行
される。
Dev.Supを実装する前に


ドライバの使い方を理解しておくこと。
以下の文献を読むこと:



“Application Developer Guide”
対象となるレコードXXのソースコードをよみ、
どのようにレコードサポートルーチンがデバイ
スサポートを呼び出すかを理解すること。
EPICS baseのサンプルを読む:
base/src/dev/softDev/devXXSoft.c
AI レコードのデバイスサポート

共通




report
initialization
initialize instance
機器からの割り込み

AI特有


Read: aiレコードにデ
バイスの値を読み込
む
線形変換
(RVAL->VAL)
AI Dev. Sup.の初期化
long aiDevInit (unsigned pass)
 すべてのレコード型に共通
 デバイス特有の初期化
 pass = 0, “iocInit()“ですべてのレコードの初期
化前に呼ばれる。


HWのチェックなど、ただし、レコードはまだデータを取
り扱える状態ではない。
pass = 1, “iocInit()“中で個々のレコードの初期
化が終わった後にもう一度呼び出される。

トリガを有効にするなど
AI デバイス レポート
long aiDevReport (struct aiRecord *
pai, int level);
 すべてのレコードに共通。ただし特有のレコード
型へのポインタを引数としてとる。
 ユーザが”dbior <level>”を実行した際に、それ
ぞれのレコードの実体にたいして一度ずつ呼び
出される。
 デバイス状態をstdoutに出力する。
 Idea: “level”をあげることでより詳細な情報を得
る。
レコード毎のAI デバイスの初期化
long aiDevInitInstance(struct aiRecord *pai)


“iocInit()”中でデイスに接続されているレコード
のそれぞれについて 一度ずつ呼び出される。
典型的な役割


デバイスアドレス (pai->inp)の解釈とチェック
デバイス固有データ(シグナル#、ドライバハンドラな
ど)のDPVTフィールドへの保存、:
pvt = (X *) calloc(1, sizeof(X));
pvt->signal = signal_this_record_wants;
pvt->drv = magic_handle_we_got_from_driver;
pai->dpvt = (void *) pvt;
信号の値を読む
long aiDevRead_(struct aiRecord * pai){
long rval;
if (device OK)
{
rval=pDevMemoryMap->
aiRegister[pai->dpvt->signal];
pai->rval = rval;
}
else
recGblSetSevr(pai,
READ_ALARM, INVALID_ALARM);
}
AI 線形変換
long aiDevLinearConv (
struct aiRecord *pai, int after);

制御値への変換のスロープとオフセットの設定
if (!after)
return S_XXXX_OK;
/* A 12 bit DAC is assumed here */
pai->eslo = (pai->eguf - pai->egul)/0x0FFF;
pai->roff = 0;
/* roff could be different
for device w/ e.g. +-5V, half scale = 0V */
From convert() in
aiRecord.c
double val;
val = pai->rval + pai->roff;
/*
*
adjust with slope/offset
*
if linear convert is used
*/
if ( pai->aslo != 0.0 ) val *= pai->aslo;
if( pai->aoff != 0.0 ) val+= pai->aoff;
if(pai->linr == menuConvertLINEAR)
val = (val * pai->eslo) + pai->eoff;
Advanced: 割り込み

Device supports interrupts, want to use SCAN=“I/O
Intr”



困難な点:



高速なレコードのスキャン
デバイスと同期したスキャン
割り込み: interrupt levelで動作, ほとんどのOS機能は使えない。
Record 処理: 通常タスクレベルで動作
IOSCANPVT

割り込みに応答してレコードの処理を行うためにEPICS Core に
用意された仕組み
IOSCANPVT


独立した割り込み要因毎にIOSCANPVT構造体を用意し、初期化し
ておく。
/* Record’s DPVT points to struct X
* which contains IOCSCANPVT ioscanpvt */
X = (X *) rec->dpvt;
scanIoInit(&X->ioscanpvt);
割り込み処理ルーチン (ISR)の中では:
scanIoRequest(X->ioscanpvt);


ISRの中から呼んでも安全である。
ただし、iocInitが終了する前(データベースの初期化完了前)に
scanIoRequest()が呼ばれてはならない。(extern volatile int
interruptAccept;)
IO 割り込みの情報を提供
long aiDevGetIoIntInfo (
int cmd,
struct aiRecord *pai,
IOSCANPVT *ppvt);
割り込みソースとレコード関連づける
*ppvt = X->ioscanpvt;
 cmd==0 - IO 割り込みによるスキャンに
登録
 cmd==1 -登録から外す

非同期 デバイス



read/write 関数は “PACT” をtrue に設定
し、0を正常終了として返す。
非同期IO終了コールバックがレコードの
「処理」を終了させる。(PACT=False)
ISR(Interupt Service Routine)中ではレ
コード処理関数を呼び出してはいけない。
非同期読み込みの例
long devXxxRead (struct aiRecord *pai) {
if (pai->pact)
return S_devXxx_OK;
/* zero */
pai->pact = TRUE
devXxxBeginAsyncIO(pai->dpvt);
return S_devXxx_OK;
}
非同期読み込み完了の例
void devXxxAsyncIOCompletion(struct aiRecord *pai, long ioStatus)
{
struct rset *prset = (struct rset *) pai->rset;
dbScanLock(pai);
if (ioStatus != S_devXxx_OK) {
recGblSetSevr(pai, READ_ALARM, INVALID_ALARM);
}
(*prset->process)(pai);
dbScanUnlock(pai);
}
レコードサポート

デバイス/ドライバサポートと類似している:



いくつかの関数は実装されなければならない。
コンパイルされたバイナリファイルは“Record
support entry table” を外部参照としてもつ。
レコードとそれぞれのフィールド (name, data
type,maybe menu of enumerated values, …)の
情報は.DBDファイルに定義される。
レコード DBD ファイル


完全なDBDの文法については、 “IOC Application
Developer’s Guide”を読む必要がある!
makeBaseAppでつくられるxxxRecord を例にとる
recordtype(xxx)
{
# Each record needs to start w/ the common fields!
include "dbCommon.dbd"
field(VAL,DBF_DOUBLE)
{
prompt("Current EGU Value")
asl(ASL0)
pp(TRUE)
}
…
}
レコード・サポート・エントリ・テー
ブル:RSET

初期化:


プロセス関数:
レコードの機能を実装する。. Often





レコードタイプ毎の初期化と、レコード毎の初期化
このレコード特有のデバイスサポートを呼び出す
アラームのチェック
モニタの掲示
フォワードリンクの処理
ユーティリティ関数を用意することで、iocCoreが
正しく読み書きされようにする。
レコード・サポート・エントリ・テー
ブル(RSET)

レコードの実装は
struct rset <xxxRecord>RSET;
を外部参照におく。
struct rset /* record support entry table */
{
long number;
/* number of support routine */
RECSUPFUN report;
/* print report */
RECSUPFUN init;
/* init support */
RECSUPFUN init_record;
/* init record */
RECSUPFUN process;
/* process record */
RECSUPFUN special;
/* special processing */
RECSUPFUN get_value;
/* OBSOLETE: Just leave NULL */
RECSUPFUN cvt_dbaddr;
/* cvt dbAddr */
RECSUPFUN get_array_info;
RECSUPFUN put_array_info;
RECSUPFUN get_units;
RECSUPFUN get_precision;
RECSUPFUN get_enum_str;
/* get string from enum */
RECSUPFUN get_enum_strs;
/* get all enum strings */
RECSUPFUN put_enum_str;
/* put enum from string */
RECSUPFUN get_graphic_double;
RECSUPFUN get_control_double;
RECSUPFUN get_alarm_double;
};
初期化

init()


IOC 起動時に一回呼ばれる。
init_record(void *precord, int pass)

一つのレコードにつき一回ずつ呼ばれる。.
Second pass can affect other records.
処理

通常はこのExample に従えばよい。
static long process(void *precord)
{
xxxRecord*pxxx = (xxxRecord *)precord;
xxxdset *pdset = (xxxdset *)pxxx->dset;
long status;
unsigned char pact=pxxx->pact;
if( (pdset==NULL) || (pdset->read_xxx==NULL) )
{
/* leave pact true so that dbProcess doesnt call again*/
pxxx->pact=TRUE;
recGblRecordError(S_dev_missingSup, pxxx, ”read_xxx”);
return (S_dev_missingSup);
}
/* pact must not be set true until read_xxx completes*/
status=(*pdset->read_xxx)(pxxx); /* read the new value */
/* return if beginning of asynch processing*/
if(!pact && pxxx->pact) return(0);
pxxx->pact = TRUE;
recGblGetTimeStamp(pxxx);
/* check for alarms */
alarm(pxxx);
/* check event list */
monitor(pxxx);
/* process the forward scan link record */
recGblFwdLink(pxxx);
pxxx->pact=FALSE;
return(status);
}
ユーティティ関数

次の動作を実装できる。


フィードが読み書きされたときレコードはどう反
応すればよいか
ユニット、精度、リミット;VALフィールドだけで
はない。
ユーティリティ関数…

special(DBADDR *addr, int after)


Addrで指定されたフィールドに誰かがアクセスする前
後にとるアクションを記述できる。
get_units(DBADDR *addr, char *units),
get_precision(DBADDR *addr, long *prec),
get_array_info(…)

EGUあるいはPRECフィールドの値を返すだけである。
またaddrで指定されるフィールドに特有の情報を提供
する。