Annotation of nono/vm/pio.cpp, revision 1.1.1.8

1.1       root        1: //
                      2: // nono
1.1.1.2   root        3: // Copyright (C) 2020 nono project
                      4: // Licensed under nono-license.txt
1.1       root        5: //
                      6: 
                      7: #include "pio.h"
1.1.1.2   root        8: #include "config.h"
1.1       root        9: #include "lcd.h"
1.1.1.3   root       10: #include "mainapp.h"
1.1       root       11: #include "scheduler.h"
                     12: 
                     13: //           IODevice
                     14: //               |
                     15: //               v
                     16: //          i8255Device  (8255 としての共通部分)
                     17: //               |
                     18: //    +----------+----------+
                     19: //    v          v          v
                     20: // PPIDevice  PIO0Device PIO1Device
                     21: // (X68030)   (LUNA)     (LUNA)
                     22: 
1.1.1.6   root       23: std::unique_ptr<PPIDevice> gPPI;
1.1       root       24: std::unique_ptr<PIO0Device> gPIO0;
                     25: std::unique_ptr<PIO1Device> gPIO1;
                     26: 
                     27: //
                     28: // i8255
                     29: //
                     30: 
                     31: // コンストラクタ
1.1.1.8 ! root       32: i8255Device::i8255Device(const std::string& objname_)
        !            33:        : inherited(objname_)
1.1       root       34: {
                     35: }
                     36: 
                     37: // デストラクタ
                     38: i8255Device::~i8255Device()
                     39: {
                     40: }
                     41: 
1.1.1.4   root       42: // リセット
                     43: void
                     44: i8255Device::ResetHard()
                     45: {
                     46:        // clears the control register
                     47:        // and place ports A, B, and C in input mode.
                     48:        // The input latches in ports A, B, and C are not cleared.
                     49: 
                     50:        ctrl = 0b10011011;
                     51: }
                     52: 
1.1       root       53: // コントロールポートへの書き込み。
                     54: void
                     55: i8255Device::WriteCtrl(uint32 data)
                     56: {
                     57:        if ((data & 0x80)) {
                     58:                // 最上位が立っていればモード設定
                     59:                ctrl = data;
                     60:                putlog(1,
                     61:                        "モード設定 groupA=%d groupB=%d portA=%s portB=%s portC=%s/%s",
                     62:                        GetGroupAMode(), GetGroupBMode(),
                     63:                        PortADir() == DIR_IN ? "IN" : "OUT",
                     64:                        PortBDir() == DIR_IN ? "IN" : "OUT",
                     65:                        PortCHDir() == DIR_IN ? "IN" : "OUT",
                     66:                        PortCLDir() == DIR_IN ? "IN" : "OUT");
                     67: 
                     68:                // モード設定すると PC はゼロクリアされる
                     69:                for (int pc = 0; pc < 8; pc++) {
                     70:                        SetPC(pc, 0);
                     71:                }
                     72:        } else {
                     73:                // portC 出力
                     74:                //
                     75:                // 0  x  x  x  P  P  P  V
                     76:                //             |  |  |  +-- 書き込むビットデータ
                     77:                //             +--+--+----- 書き込むビット位置
                     78: 
                     79:                int pc = (data >> 1) & 7;
                     80:                int val = data & 1;
                     81:                SetPC(pc, val);
                     82:        }
                     83: }
                     84: 
                     85: void
                     86: i8255Device::SetPC(int pc, int data)
                     87: {
                     88:        // 継承側で用意すること。ここには来ない
                     89:        PANIC("not impl");
                     90: }
                     91: 
                     92: //
                     93: // X68k PPI
                     94: //
                     95: 
                     96: // コンストラクタ
                     97: PPIDevice::PPIDevice()
1.1.1.8 ! root       98:        : inherited("PPI")
1.1       root       99: {
                    100:        devaddr = baseaddr;
                    101:        devlen  = 0x2000;
                    102: }
                    103: 
                    104: // デストラクタ
                    105: PPIDevice::~PPIDevice()
                    106: {
                    107: }
                    108: 
                    109: uint64
1.1.1.6   root      110: PPIDevice::Read(uint32 offset)
1.1       root      111: {
1.1.1.4   root      112:        gMPU->AddCycle(19);     // InsideOut p.135
                    113: 
1.1.1.6   root      114:        switch (offset) {
1.1       root      115:         case 0:
                    116:         case 1:
                    117:                return 0xff;
                    118:         case 2:
1.1.1.6   root      119:                putlog(0, "$e9a005 PortC 未実装読み込み");
1.1.1.5   root      120:                return 0xff;
1.1       root      121:         case 3:
1.1.1.6   root      122:                PANIC("PPI $e9a007 未サポート読み込み");
1.1       root      123:                break;
                    124:         default:
                    125:                __unreachable();
                    126:        }
                    127:        PANIC("not impl");
                    128: }
                    129: 
                    130: uint64
1.1.1.6   root      131: PPIDevice::Write(uint32 offset, uint32 data)
1.1       root      132: {
1.1.1.4   root      133:        gMPU->AddCycle(19);     // InsideOut p.135
                    134: 
1.1.1.6   root      135:        switch (offset) {
1.1       root      136:         case 0:
                    137:         case 1:
                    138:                break;
                    139:         case 2:
1.1.1.6   root      140:                putlog(0, "$e9a005 未実装書き込み $%02X (無視)", data);
1.1       root      141:                break;
                    142:         case 3:
                    143:                if (data != 0x92) {
1.1.1.6   root      144:                        PANIC("PPI $e9a007 未実装書き込み $%02X", data);
1.1       root      145:                } else {
1.1.1.6   root      146:                        putlog(0, "$e9a007 未実装書き込み $%02x (無視)", data);
1.1       root      147:                }
                    148:                break;
                    149:         default:
                    150:                __unreachable();
                    151:        }
                    152:        return 0;
                    153: }
                    154: 
                    155: uint64
1.1.1.6   root      156: PPIDevice::Peek(uint32 offset)
1.1       root      157: {
                    158:        // XXX
                    159:        return 0xff;
                    160: }
                    161: 
                    162: //
                    163: // LUNA PIO0
                    164: //
                    165: // 仕様は 0x49000000 から 4バイトだが、
                    166: // 0x48000000 から 0x4c000000 の手前まで 4バイト単位でミラーが見える。
                    167: //
                    168: // LUNA の DIP-SW は #1, #2 にそれぞれ8個、計16個ある。
                    169: // DIP-SW #1, #2 が設定ファイルの luna_dipsw_{1,2} に対応する。
                    170: // luna_dipsw_{1,2} はいずれも "0"/"1" を8つ並べた文字列であり、
                    171: // 先頭が本体表記でいう 1番、末尾側が本体表記でいう 8番に対応する。
                    172: // 変数は dipsw{1,2}[0..7] に対応。
                    173: // 設定ファイルと変数の値は %0 が down を表し、%1 が up を表す。
                    174: //
1.1.1.3   root      175: // LUNA-1:
1.1       root      176: // #1-1 up なら UNIX をロードして移行。down なら ROM モニタで起動。
                    177: // #1-2 up ならディスプレイコンソール。down ならシリアルコンソール。
                    178: // #1-3 up - color display, down - force to have monochrome display
                    179: // #1-4 up - no write verification,
                    180: //      down - verification on every harddisk write operation
                    181: // #1-5 up - OS is UniOS-U (SystemV/COFF),
                    182: //      down - OS is UniOS (4.3BSD/a.out OMAGIC)
                    183: // #1-6 down - force monochrome display?
                    184: // #1-7 up - boot from local device, down - boot from network
                    185: // #1-8 up - normal boot, down - start diagnostics と書いてあるが
                    186: //      down にすると画面真っ暗で詳細不明。
                    187: //
                    188: // #2-x すべて up にすると /boot が自動的にカーネルを起動。一つでも down が
                    189: //      あれば /boot がプロンプトで停止。これは ROM ではなく NetBSD のブート
                    190: //      ローダの話。
                    191: //      どこか由来のソースコードらしいがたぶんビット演算を間違えている。
1.1.1.3   root      192: //
                    193: // LUNA88K:
                    194: // #1-1 up なら ROM モニタで停止、down ならマルチユーザブート。
                    195: //      OpenBSD カーネルも up なら UserKernelConfig で止まる模様?。
                    196: // #1-2 up なら外付けシリアルコンソール、down なら内蔵ビットマップ。
                    197: // #1-3..#1-8 は予約。
                    198: // #2-x はユーザ(?)
1.1       root      199: 
                    200: // コンストラクタ
                    201: PIO0Device::PIO0Device()
1.1.1.8 ! root      202:        : inherited("PIO0")
1.1       root      203: {
                    204:        devaddr = 0x49000000;
                    205:        devlen  = 4;
1.1.1.2   root      206: 
1.1.1.3   root      207:        // 機種固有パラメータ
                    208: 
                    209:        // LUNA-1 と LUNA88K で使いやすいように初期値を変えておく。
                    210:        switch (gMainApp.GetVMType()) {
                    211:         case VMTYPE_LUNA1:
                    212:                gConfig->SetDefault("luna-dipsw1", "11110111");
                    213:                break;
                    214:         case VMTYPE_LUNA88K:
1.1.1.4   root      215:                gConfig->SetDefault("luna-dipsw1", "11111111");
1.1.1.3   root      216:                break;
                    217:         default:
                    218:                __unreachable();
                    219:        }
                    220:        // DIP-SW#2 はユーザ定義(?)なので(今の所?)どちらも共通。
                    221:        gConfig->SetDefault("luna-dipsw2", "11111111");
                    222: 
1.1.1.8 ! root      223:        monitor.func = (MonitorCallback_t)&PIO0Device::MonitorUpdate;
        !           224:        monitor.SetSize(22, 11);
        !           225:        monitor.Regist(ID_MONITOR_PIO0);
1.1       root      226: }
                    227: 
                    228: // デストラクタ
                    229: PIO0Device::~PIO0Device()
                    230: {
                    231: }
                    232: 
                    233: bool
                    234: PIO0Device::Init()
                    235: {
                    236:        // XXX 本当はラッチのタイミングが違う気もするけど、とりあえずね
                    237: 
1.1.1.2   root      238:        const ConfigItem& item1 = gConfig->Find("luna-dipsw1");
1.1       root      239:        const std::string& str1 = item1.AsString();
                    240:        if (str1.size() != 8) {
                    241:                item1.Err("must be 8 digits");
                    242:                return false;
                    243:        }
                    244: 
1.1.1.2   root      245:        const ConfigItem& item2 = gConfig->Find("luna-dipsw2");
1.1       root      246:        const std::string& str2 = item2.AsString();
                    247:        if (str2.size() != 8) {
                    248:                item2.Err("must be 8 digits");
                    249:                return false;
                    250:        }
                    251:        for (int i = 0; i < 8; i++) {
                    252:                dipsw1[i] = (str1[i] != '0');
                    253:                dipsw2[i] = (str2[i] != '0');
                    254:        }
                    255:        return true;
                    256: }
                    257: 
1.1.1.7   root      258: // XXX PowerOn か ResetHard 未調査
                    259: 
1.1       root      260: uint64
1.1.1.6   root      261: PIO0Device::Read(uint32 offset)
1.1       root      262: {
                    263:        uint32 data;
                    264: 
1.1.1.6   root      265:        switch (offset) {
1.1       root      266:         case 0:
                    267:                // PortA
                    268:                putlog(1, "DIP-SW 1 読み込み");
                    269:                return GetPA();
                    270: 
                    271:         case 1:
                    272:                // PortB
                    273:                putlog(1, "DIP-SW 2 読み込み");
                    274:                return GetPB();
                    275: 
                    276:         case 2:
                    277:                // PortC
                    278:                data = GetPC();
1.1.1.6   root      279:                putlog(1, "PC 読み込み -> $%02x", data);
1.1       root      280:                return data;
                    281: 
                    282:         case 3:
                    283:                data = ReadCtrl();
                    284:                putlog(1, "コントロール読み込み -> $%02x", data);
                    285:                return data;
                    286: 
                    287:         default:
                    288:                __unreachable();
                    289:        }
                    290: }
                    291: 
                    292: uint64
1.1.1.6   root      293: PIO0Device::Write(uint32 offset, uint32 data)
1.1       root      294: {
1.1.1.6   root      295:        switch (offset) {
1.1       root      296:         case 0:        // PIO0 PortA: DIPSW1 (入力のみ)
                    297:         case 1:        // PIO0 PortB: DIPSW2 (入力のみ)
                    298:                return 0;
                    299: 
                    300:         case 2:        // PIO0 PortC: ホスト割り込み制御
                    301:                for (int i = 0; i < 8; i++) {
                    302:                        SetPC(i, data & 1);
                    303:                        data >>= 1;
                    304:                }
                    305:                return 0;
                    306: 
                    307:         case 3:        // コントロール
                    308:                WriteCtrl(data);
                    309:                return 0;
                    310: 
                    311:         default:
                    312:                __unreachable();
                    313:        }
                    314: }
                    315: 
                    316: uint64
1.1.1.6   root      317: PIO0Device::Peek(uint32 offset)
1.1       root      318: {
1.1.1.6   root      319:        switch (offset) {
1.1       root      320:         case 0:
                    321:                return GetPA();
                    322: 
                    323:         case 1:
                    324:                return GetPB();
                    325: 
                    326:         case 2:
                    327:                return GetPC();
                    328: 
                    329:         case 3:
                    330:                return ReadCtrl();
                    331: 
                    332:         default:
                    333:                __unreachable();
                    334:        }
                    335: }
                    336: 
1.1.1.3   root      337: void
1.1.1.8 ! root      338: PIO0Device::MonitorUpdate(Monitor *, TextScreen& screen)
1.1.1.2   root      339: {
                    340:        int y;
                    341: 
1.1.1.8 ! root      342:        screen.Clear();
1.1.1.2   root      343: 
                    344:        y = 0;
1.1.1.8 ! root      345:        screen.Print(0, y++, "PortA(DIPSW1):%c%c%c%c%c%c%c%c",
1.1.1.2   root      346:                (dipsw1[0] ? '1' : '0'),
                    347:                (dipsw1[1] ? '1' : '0'),
                    348:                (dipsw1[2] ? '1' : '0'),
                    349:                (dipsw1[3] ? '1' : '0'),
                    350:                (dipsw1[4] ? '1' : '0'),
                    351:                (dipsw1[5] ? '1' : '0'),
                    352:                (dipsw1[6] ? '1' : '0'),
                    353:                (dipsw1[7] ? '1' : '0'));
1.1.1.8 ! root      354:        screen.Print(0, y++, "PortB(DIPSW2):%c%c%c%c%c%c%c%c",
1.1.1.2   root      355:                (dipsw2[0] ? '1' : '0'),
                    356:                (dipsw2[1] ? '1' : '0'),
                    357:                (dipsw2[2] ? '1' : '0'),
                    358:                (dipsw2[3] ? '1' : '0'),
                    359:                (dipsw2[4] ? '1' : '0'),
                    360:                (dipsw2[5] ? '1' : '0'),
                    361:                (dipsw2[6] ? '1' : '0'),
                    362:                (dipsw2[7] ? '1' : '0'));
1.1.1.8 ! root      363:        screen.Puts(0, y++, "PortC");
        !           364:        screen.Puts(1, y++, TA::OnOff(xp_reset), "b7: XP Reset");
        !           365:        screen.Puts(1, y++, TA::OnOff(parity),   "b6: Parity");
        !           366:        screen.Puts(1, y++,                      "b5: ---");
        !           367:        screen.Puts(1, y++, TA::OnOff(int5en),   "b4: Int5 Enable");
        !           368:        screen.Puts(1, y++, TA::OnOff(int5rq),   "b3: Int5 Request");
        !           369:        screen.Puts(1, y++, TA::OnOff(int1en),   "b2: Int1 Enable");
        !           370:        screen.Puts(1, y++,                      "b1: ---");
        !           371:        screen.Puts(1, y++, TA::OnOff(int1rq),   "b0: Intq Request");
1.1.1.2   root      372: }
                    373: 
1.1       root      374: // PortA (DIP-SW 1) 読み込み
                    375: uint32
                    376: PIO0Device::GetPA() const
                    377: {
                    378:        uint32 data = 0;
                    379:        for (int i = 0; i < 8; i++) {
                    380:                data |= ((int)dipsw1[i]) << i;
                    381:        }
                    382:        return data & 0xff;
                    383: }
                    384: 
                    385: // PortB (DIP-SW 2) 読み込み
                    386: uint32
                    387: PIO0Device::GetPB() const
                    388: {
                    389:        uint32 data = 0;
                    390:        for (int i = 0; i < 8; i++) {
                    391:                data |= ((int)dipsw2[i]) << i;
                    392:        }
                    393:        return data & 0xff;
                    394: }
                    395: 
                    396: // PortC 読み込み
                    397: uint32
                    398: PIO0Device::GetPC() const
                    399: {
                    400:        uint32 data;
                    401: 
                    402:        data = 0x00     // XXX 空きビットは何が読めるか?
                    403:                 | (int1rq ? 0x01 : 0)
                    404:                 | (int1en ? 0x04 : 0)
                    405:                 | (int5rq ? 0x08 : 0)
                    406:                 | (int5en ? 0x10 : 0)
                    407:                 | (parity ? 0x40 : 0)
                    408:                 | (xp_reset?0x80 : 0);
                    409:        return data;
                    410: }
                    411: 
                    412: // PortC 書き込み
                    413: void
                    414: PIO0Device::SetPC(int pc, int val)
                    415: {
                    416:        switch (pc) {
                    417:         case 0:
                    418:                if ((val ^ int1rq)) {
                    419:                        int1rq = val;
                    420:                        putlog(0, "INT1割り込み要求 %d (未実装)", int1rq);
                    421:                }
                    422:                return;
                    423:         case 1:
                    424:                // Not Connected.
                    425:                return;
                    426:         case 2:
                    427:                if ((val ^ int1en)) {
                    428:                        int1en = val;
                    429:                        putlog(0, "INT1割り込み許可 %d (未実装)", int1en);
                    430:                }
                    431:                return;
                    432:         case 3:
                    433:                if ((val ^ int5rq)) {
                    434:                        int5rq = val;
                    435:                        putlog(0, "INT5割り込み要求 %d (未実装)", int5rq);
                    436:                }
                    437:                return;
                    438:         case 4:
                    439:                if ((val ^ int5en)) {
                    440:                        int5en = val;
                    441:                        putlog(0, "INT5割り込み許可 %d (未実装)", int5en);
                    442:                }
                    443:                return;
                    444:         case 5:
                    445:                // Not Connected.
                    446:                return;
                    447:         case 6:
                    448:                // パリティ有効/無効
                    449:                // 使わないので無視するが読み返しのために覚えておく
                    450:                parity = val;
                    451:                return;
                    452:         case 7:
                    453:                // XP リセット
                    454:                if ((val ^ xp_reset)) {
                    455:                        xp_reset = val;
                    456:                        putlog(0, "XPリセット %d (未実装)", xp_reset);
                    457:                }
                    458:                return;
                    459:        }
                    460:        PANIC("invalid pc=%d", pc);
                    461: }
                    462: 
                    463: 
                    464: //
                    465: // LUNA PIO1
                    466: //
                    467: // 仕様は 0x4d000000 から 4バイトだが、
                    468: // 0x4c000000 から 0x50000000 の手前まで 4バイト単位でミラーが見える。
                    469: 
                    470: // コンストラクタ
                    471: PIO1Device::PIO1Device()
1.1.1.8 ! root      472:        : inherited("PIO1")
1.1       root      473: {
                    474:        devaddr = 0x4d000000;
                    475:        devlen  = 4;
                    476: 
                    477:        poffevent.func = (DeviceCallback_t)&PIO1Device::PowerOffCallback;
                    478:        poffevent.SetName("PIO1 Power Off");
                    479: }
                    480: 
                    481: // デストラクタ
                    482: PIO1Device::~PIO1Device()
                    483: {
                    484: }
                    485: 
1.1.1.4   root      486: // リセット
                    487: void
                    488: PIO1Device::ResetHard()
                    489: {
                    490:        inherited::ResetHard();
                    491: 
                    492:        poffevent.Stop();
                    493: }
                    494: 
1.1       root      495: uint64
1.1.1.6   root      496: PIO1Device::Read(uint32 offset)
1.1       root      497: {
                    498:        uint32 data;
                    499: 
1.1.1.6   root      500:        switch (offset) {
1.1       root      501:         case 0:
                    502:                // PIO1 PortA: LCD データ
                    503:                return gLCD->Read();
                    504: 
                    505:         case 1:
                    506:                // PIO1 PortB: 無効
                    507:                return 0xff;
                    508: 
                    509:         case 2:
                    510:                // PortC
                    511:                data = gLCD->Get();
                    512:                data |= xp_intreq ? 0 : 0x02;
                    513:                // 残りのビットは %1 だろうか?
                    514:                data |= 0x0d;
                    515:                putlog(1, "PC 読み込み -> $%02x", data);
                    516:                return data;
                    517: 
                    518:         case 3:
                    519:                data = ReadCtrl();
                    520:                putlog(1, "コントロール読み込み -> $%02x", data);
                    521:                return data;
                    522: 
                    523:         default:
                    524:                __unreachable();
                    525:        }
                    526: }
                    527: 
                    528: uint64
1.1.1.6   root      529: PIO1Device::Write(uint32 offset, uint32 data)
1.1       root      530: {
1.1.1.6   root      531:        switch (offset) {
1.1       root      532:         case 0:        // PIO1 PortA: LCD データ
                    533:                gLCD->Write(data);
                    534:                return 0;
                    535: 
                    536:         case 1:        // PIO1 PortB: ローカル割り込み
                    537:                break;
                    538: 
                    539:         case 2:        // PIO1 PortC: LCD/パワーオフ制御
                    540:                for (int i = 0; i < 8; i++) {
                    541:                        SetPC(i, data & 1);
                    542:                        data >>= 1;
                    543:                }
                    544:                return 0;
                    545: 
                    546:         case 3:        // コントロール
                    547:                WriteCtrl(data);
                    548:                return 0;
                    549: 
                    550:         default:
                    551:                __unreachable();
                    552:        }
1.1.1.6   root      553:        PANIC("$%08X <- $%02X 未サポート書き込み", gMPU->GetPaddr(), data);
1.1       root      554:        return 0;
                    555: }
                    556: 
                    557: uint64
1.1.1.6   root      558: PIO1Device::Peek(uint32 offset)
1.1       root      559: {
                    560:        uint32 data;
                    561: 
1.1.1.6   root      562:        switch (offset) {
1.1       root      563:         case 0:        // PIO1 PortA LCD データ
                    564:                return gLCD->Peek();
                    565: 
                    566:         case 1:        // PIO1 PortB 無効
                    567:                return 0xff;
                    568: 
                    569:         case 2:        // PIO1 PortC
                    570:                data = gLCD->Get();
                    571:                data |= xp_intreq ? 0 : 0x02;
                    572:                // 残りのビットは %1 だろうか?
                    573:                data |= 0x0d;
                    574:                return data;
                    575: 
                    576:         case 3:
                    577:                return ReadCtrl();
                    578: 
                    579:         default:
                    580:                __unreachable();
                    581:        }
                    582: }
                    583: 
                    584: void
                    585: PIO1Device::SetPC(int pc, int val)
                    586: {
                    587:        switch (pc) {
                    588:         case 0:
                    589:         case 1:
                    590:         case 2:
                    591:         case 3:
                    592:                // PortC の下半分は入力ポートとして使われることを想定して
                    593:                // いるので、ここへの書き込みは起きないだろうし、無視する。
                    594:                return;
                    595: 
                    596:         case 4:
                    597:                power = val;
                    598:                if (power) {
                    599:                        // 電源オフ取り消し
                    600:                        putlog(1, "電源 OFF 取消");
                    601:                        poffevent.Stop();
                    602:                } else {
                    603:                        // 1msec 後に電源オフ
                    604:                        putlog(1, "電源 OFF 指示");
                    605:                        poffevent.time = 1_msec;
                    606:                        poffevent.Start();
                    607:                }
                    608:                return;
                    609:         case 5:
                    610:                gLCD->SetRW(val);
                    611:                return;
                    612:         case 6:
                    613:                gLCD->SetRS(val);
                    614:                return;
                    615:         case 7:
                    616:                gLCD->SetE(val);
                    617:                return;
                    618: 
                    619:         default:
                    620:                break;
                    621:        }
                    622:        PANIC("PC%d 未実装", pc);
                    623: }
                    624: 
                    625: // 電源 OFF イベントのコールバック
                    626: void
1.1.1.4   root      627: PIO1Device::PowerOffCallback(Event& ev)
1.1       root      628: {
                    629:        putlog(0, "電源 OFF");
                    630:        gScheduler->RequestPowerOff();
                    631: }

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.