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で指定されるフィールドに特有の情報を提供 する。
© Copyright 2024 ExpyDoc