350 likes | 585 Views
EPICS レコード/デバイス / ドライバ サポート. kasemir@lanl.gov, many slides copied from johill@lanl.gov. ハードウェアとの接続. General Idea. EPICS. Software. IOC Core: Db, CA, …. Record Support. “Driver”. Device Support. Hardware. Hardware. Driver Support. どこを拡張するのか …. よくある場合 : 新しいハードウェア (I/O Board,..)
E N D
EPICSレコード/デバイス/ドライバサポート kasemir@lanl.gov,many slides copied from johill@lanl.gov
ハードウェアとの接続 General Idea EPICS Software IOC Core: Db, CA, … Record Support “Driver” Device Support Hardware Hardware Driver Support
どこを拡張するのか… • よくある場合:新しいハードウェア (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, OS-specific (vxWorks, Linux, Win32), … • 典型的な関数の集合体:Check, report, init, read, write, setup_trigger(callback),… • “EPICS part”: オプショナル & 自明!
ドライバ・サポート・エントリ・テーブル(DRVET)ドライバ・サポート・エントリ・テーブル(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)デバイス・サポート・エントリ・テーブル(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
共通 report initialization initialize instance 機器からの割り込み AI特有 Read: aiレコードにデバイスの値を読み込む 線形変換(RVAL->VAL) AI レコードのデバイスサポート
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;
Device supports interrupts, want to use SCAN=“I/O Intr” 高速なレコードのスキャン デバイスと同期したスキャン 困難な点: 割り込み: interrupt levelで動作, ほとんどのOS機能は使えない。 Record 処理: 通常タスクレベルで動作 IOSCANPVT 割り込みに応答してレコードの処理を行うためにEPICS Core に用意された仕組み Advanced: 割り込み
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レコード・サポート・エントリ・テーブル:RSET • 初期化: • レコードタイプ毎の初期化と、レコード毎の初期化 • プロセス関数:レコードの機能を実装する。. Often • このレコード特有のデバイスサポートを呼び出す • アラームのチェック • モニタの掲示 • フォワードリンクの処理 • ユーティリティ関数を用意することで、iocCoreが正しく読み書きされようにする。
レコード・サポート・エントリ・テーブル(RSET)レコード・サポート・エントリ・テーブル(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で指定されるフィールドに特有の情報を提供する。