لینک کوتاه مطلب : https://hsgar.com/?p=4209

RISC-V در حال دریافت MSI است! – استفان مارز

فهرست

  1. بررسی اجمالی
  2. مخزن
  3. وقفه های سیگنالی پیام (MSI)
  4. کنترلر MSI ورودی (IMSIC)
  5. نتیجه
  6. بعد چه است

بررسی اجمالی

وقفه های سیگنال دهی شده پیام یا MSI روشی را برای سیگنال دادن به وقفه بدون پین درخواست وقفه اختصاصی (IRQ) توصیف می کنند. یکی از رایج ترین کاربردهای MSI ها، گذرگاه PCI است و مشخصات PCI استانداردهای MSI و MSI-X را تعریف می کند. مزایای بالقوه ممکن است شامل موارد زیر باشد: (1) کاهش تعداد سیم های مستقیم از دستگاه به CPU یا کنترل کننده وقفه، (2) بهبود عملکرد سیگنالینگ، و (3) بهبود سیگنال دهی مهمان/میزبان برای محیط های مجازی.


مخزن

همه کدهای این پست را می توانید در اینجا پیدا کنید: https://github.com/sgmarz/riscv_msi.

کد برای RV32I در Rust نوشته شده است. من در ابتدا آن را برای RV64GC نوشتم، اما هر چیز دیگری که نوشتم برای RV64GC است، بنابراین فکر کردم که باید افق هایم را گسترش دهم.

راهنمای AIA را می توانید در اینجا بیابید: https://github.com/riscv/riscv-aia.


وقفه های سیگنالی پیام (MSI)

MSI توسط یک “پیام” راه اندازی می شود که یک اصطلاح فانتزی برای “نوشتن حافظه” است. در واقع، ما می توانیم یک پیام را به سادگی با عدم ارجاع یک اشاره گر MMIO راه اندازی کنیم.

let message = 0xdeadbeef;
// QEMU's 'virt' machine attaches the M-mode IMSIC for HART 0 to 0x2400_0000
write_volatile(0x2400_0000 as *mut u32, message);

کد بالا به آدرس MMIO 0x2400_0000 می نویسد، جایی که ماشین virt QEMU IMSIC حالت M را برای HART 0 وصل می کند. IMSIC حالت S برای HART 0 به 0x2800_0000 متصل می شود. هر HART یک صفحه از یکدیگر فاصله دارد، به این معنی که IMSIC حالت M برای HART 1 در 0x2400_1000 است، و IMSIC حالت S برای HART 1 0x2800_1000 است.

IMSIC ها نسبتاً اخیراً به ماشین virt QEMU اضافه شده اند، بنابراین ممکن است منظور شما شبیه سازی و ساخت QEMU خود باشد. مخزن QEMU را می توان در اینجا یافت: https://github.com/qemu.

پس از اینکه یک دستگاه کلمه ای را در یک آدرس MMIO خاص نوشت، وقفه ایجاد می شود. این بدان معنی است که دستگاه ها نیازی به سیمی ندارند که آن را به یک کنترل کننده IRQ متصل کند، مانند کنترل کننده وقفه سطح پلت فرم RISC-V یا PLIC. درعوض، تا زمانی که دستگاه بتواند حافظه را بنویسد، می تواند باعث ایجاد وقفه شود.

اگرچه راه اندازی یک پیام به همین سادگی است، اما ما به مکانیزمی برای فعال کردن و اولویت بندی این پیام ها نیاز داریم. ممکن است شرایطی وجود داشته باشد که ما نخواهیم پیام های خاصی را بشنویم. اینجاست که کنترلر MSI ورودی یا IMSIC وارد عمل می شود.


کنترلر MSI ورودی (IMSIC)

برای اینکه بتوان از MSI ها پشتیبانی کرد، برخی از دستگاه ها باید بتوانند نوشته های حافظه را بگیرد و آنها را به وقفه تبدیل کند. علاوه بر این، دستگاه باید مکانیزمی برای فعال/غیرفعال کردن و اولویت بندی وقفه ها درست مانند یک کنترل کننده وقفه معمولی ارائه دهد. این کار توسط دستگاه کنترلر MSI ورودی (IMSIC) انجام می شود.

هشدار: در معماری وقفه پیشرفته (AIA) کتابچه راهنمای کاربر هنوز یک کار در حال انجام است و در حال حاضر تغییرات عمده ای وجود داشته است که CSR یا سایر اطلاعات مربوطه را حذف یا اضافه کرده است. بنابراین، برخی از کدها و جداول ممکن است قدیمی باشند.

مکانیسم ثبت برای IMSIC شامل چندین ثبت کنترل و وضعیت (CSR) و همچنین ثبت داخلی است که از طریق مکانیسم انتخاب قابل دسترسی است.

ثبت های تازه اضافه شده

AIA چندین CSR جدید را بین حالت ماشین و سرپرست تعریف می کند.

نام ثبت نام شماره ثبت شرح
MISELECT 0x350 ثبت نام ماشین را انتخاب کنید
SISELECT 0x150 ثبت نام سرپرست انتخاب کنید
MIREG 0x351 نمای AR/W ثبت انتخاب شده در MISELECT
SIREG 0x151 نمای AR/W ثبت انتخاب شده در SISELECT
MTOPI 0xFB0 وقفه سطح بالای دستگاه
STOPI 0xDB0 وقفه ناظر در سطح بالا
MTOPEI 0x35C وقفه خارجی سطح بالای دستگاه (به IMSIC نیاز دارد)
استوپی 0x15C وقفه خارجی سطح بالای ناظر (به IMSIC نیاز دارد)
CSR های جدید برای AIA تعریف شده است.

رجیسترهای MISELECT و MIREG به ما این امکان را می دهند که با نوشتن شماره آن در ثبات MISELECT، یک ثبات را انتخاب کنیم. سپس MIREG نشان دهنده ثبت انتخاب شده خواهد بود. به عنوان مثال، اگر از MIREG بخوانیم، از ثبات انتخاب شده می خوانیم و اگر به MIREG بنویسیم، به ثبات انتخاب شده می نویسیم.

چهار رجیستر قابل انتخاب وجود دارد. نسخه های ماشینی و ناظر این رجیسترها وجود دارد. به عنوان مثال، اگر برای SISELECT بنویسیم، نسخه نظارتی رجیستر را مشاهده خواهیم کرد.

نام ثبت نام MISELECT/SISELECT شرح
EIDELIVERY 0x70 ثبت تحویل وقفه خارجی
EITTHRESHOLD 0x72 ثبت آستانه وقفه خارجی
EIP0 تا EIP63 0x80 تا 0xBF ثبت‌های معلق وقفه خارجی
EIE0 تا EIE63 0xC0 تا 0xFF رجیسترهای فعال کننده وقفه خارجی
ثبت قابل انتخاب توسط MISELECT/SISELECT و قابل خواندن/نوشتن از طریق MIREG/SIREG.

اولین کاری که باید انجام دهیم این است که خود IMSIC را فعال کنیم. این کار از طریق یک رجیستر به نام EIDELIVERY برای ثبت “فعال کردن تحویل قطعی” انجام می شود. این رجیستر ممکن است شامل یکی از سه مقدار باشد:

ارزش شرح
0 تحویل قطعی غیرفعال است
1 تحویل وقفه فعال است
0x4000_0000 تحویل وقفه اختیاری از طریق PLIC یا APLIC
مقدار نوشته شده در رجیستر EIDELIVERY

فعال کردن IMSIC

بنابراین، ما باید 1 (تحویل وقفه فعال است) را در ثبات EIDELIVERY بنویسیم:

// First, enable the interrupt file
// 0 = disabled
// 1 = enabled
// 0x4000_0000 = use PLIC instead
imsic_write(MISELECT, EIDELIVERY);
imsic_write(MIREG, 1);

ثبات EITHRESHOLD آستانه ای ایجاد می کند که اولویت های وقفه باید قبل از شنیدن آن باشد. اگر یک وقفه دارای اولویت کمتر از مقدار EITHRESHOLD باشد، “شنیده می شود” یا از ماسک خارج می شود. در غیر این صورت ماسک می شود و شنیده نمی شود. به عنوان مثال، EITHRESHOLD 5 فقط به پیام های 1، 2، 3 و 4 اجازه شنیدن می دهد. پیام 0 به معنای “بدون پیام” رزرو شده است.

از آنجایی که آستانه بالاتر پیام های بیشتری را باز می کند، پیام هایی با a پایین تر شماره یک بالاتر اولویت.

// Set the interrupt threshold.
// 0 = enable all interrupts
// P = enable < P only
imsic_write(MISELECT, EITHRESHOLD);
// Only hear 1, 2, 3, and 4
imsic_write(MIREG, 5);

اولویت ها را قطع کنید

اسناد AIA از خود پیام به عنوان اولویت استفاده می کند. بنابراین، پیام 1 دارای اولویت 1 است، در حالی که پیام 1234 دارای اولویت 1234 است. این راحت تر است زیرا ما می توانیم پیام ها را مستقیماً کنترل کنیم. با این حال، از آنجایی که هر شماره پیام دارای بیت های فعال و معلق است، محدودیتی برای بالاترین وقفه شماره وجود دارد. مشخصات دارای حداکثر (32times64 – 1 = 2,047) کل پیام است (یک پیام را کم می کنیم تا 0 به عنوان یک پیام معتبر حذف شود).

فعال کردن پیام ها

ثبت EIE فعال یا غیرفعال بودن یک پیام را کنترل می کند. برای RV64، این ثبات ها 64 بیت عرض دارند، اما همچنان دو عدد رجیستر مجاور را اشغال می کنند. بنابراین، برای RV64، فقط رجیسترهای دارای شماره زوج قابل انتخاب هستند (به عنوان مثال، EIE0، EIE2، EIE4، …، EIE62). اگر سعی کنید یک EIE با شماره فرد انتخاب کنید، یک تله دستورالعمل نامعتبر دریافت خواهید کرد. این موضوع ساعت‌ها طول کشید تا بفهمم حتی اگر مستندات بیان می‌کنند که این رفتار مطلوب است. برای RV32، رجیسترهای EIE فقط 32 بیتی هستند و EIE0 تا EIE63 همگی قابل انتخاب هستند.

ثبات EIE یک مجموعه بیتی است. اگر بیت یک پیام متناظر 1 باشد، نقاب برداری شده و فعال می شود، در غیر این صورت ماسک شده و غیرفعال می شود. برای RV64، پیام های 0 تا 63 همه در EIE0 هستند[63:0]. بیت پیام است. ما می توانیم از فرمول های زیر برای تعیین اینکه کدام ثبات را برای RV64 انتخاب کنیم استفاده کنیم:

// Enable a message number for machine mode (RV64)
fn imsic_m_enable(which: usize) {
    let eiebyte = EIE0 + 2 * which / 64;
    let bit = which % 64;

    imsic_write(MISELECT, eiebyte);
    let reg = imsic_read(MIREG);
    imsic_write(MIREG, reg | 1 << bit);
}

RV32 تقریباً یکسان رفتار می کند، با این تفاوت که مجبور نیستیم آن را 2 مقیاس کنیم.

// Enable a message number for machine mode (RV32)
fn imsic_m_enable(which: usize) {
    let eiebyte = EIE0 + which / 32;
    let bit = which % 32;

    imsic_write(MISELECT, eiebyte);
    let reg = imsic_read(MIREG);
    imsic_write(MIREG, reg | 1 << bit);
}

با کد بالا، اکنون می توانیم پیام هایی را که می خواهیم بشنویم فعال کنیم. مثال زیر پیام های 2، 4 و 10 را فعال می کند.

imsic_m_enable(2);
imsic_m_enable(4);
imsic_m_enable(10);

پیام های معلق

رجیسترهای EIP دقیقاً مانند رجیسترهای EIE عمل می‌کنند با این تفاوت که بیت 1 به این معنی است که آن پیام خاص در انتظار است، به این معنی که یک نوشتن به IMSIC با آن شماره پیام ارسال شده است. ثبت EIP خواندن/نوشتن است. اگر از آن بخوانیم، می‌توانیم تعیین کنیم که کدام پیام‌ها در انتظار هستند. اگر روی آن بنویسیم، می توانیم به صورت دستی یک پیام وقفه را با نوشتن یک عدد در بیت پیام مربوطه راه اندازی کنیم.

// Trigger a message by writing to EIP for Machine mode in RV64
fn imsic_m_trigger(which: usize) {
    let eipbyte = EIP0 + 2 * which / 64;
    let bit = which % 64;

    imsic_write(MISELECT, eipbyte);
    let reg = imsic_read(MIREG);
    imsic_write(MIREG, reg | 1 << bit);
}

آزمایش کردن

اکنون که می‌توانیم ارسال و همچنین پیام‌های فردی را فعال کنیم، می‌توانیم آنها را به یکی از دو روش فعال کنیم: (1) پیام را مستقیماً در آدرس MMIO بنویسیم یا (2) بیت معلق وقفه پیام مربوطه را روی 1 تنظیم کنیم.

unsafe {
    // We are required to write only 32 bits.
    write_volatile(imsic_m(hartid) as *mut u32, 2)
}
imsic_m_trigger(4);

تله های پیام

هر زمان که یک پیام بدون ماسک به یک IMSIC فعال ارسال شود، به عنوان یک وقفه خارجی به HART مشخص شده می رسد. برای IMSIC حالت ماشینی، این به عنوان علت ناهمزمان 11 و برای IMSIC در حالت سرپرست، به عنوان علت ناهمزمان 9 خواهد آمد.

وقتی وقفه ای به دلیل ارسال پیام دریافت می کنیم، باید با خواندن از رجیسترهای MTOPEI یا STOPEI بسته به حالت امتیاز، از وقفه در انتظار سطح بالا خارج شویم. این مقداری را به ما می دهد که در آن بیت های 26:16 حاوی شماره پیام و بیت های 10:0 دارای اولویت وقفه هستند. بله، شماره پیام و اولویت پیام یکی هستند، بنابراین می توانیم یکی را انتخاب کنیم.

// Pop the top pending message
fn imsic_m_pop() -> u32 {
    let ret: u32;
    unsafe {
        // 0x35C is the MTOPEI CSR.
        asm!("csrrw {retval}, 0x35C, zero", retval = out(reg) ret),
    }
    // Message number starts at bit 16
    ret >> 16
}

کامپایلر من از نام CSR ها در این مشخصات پشتیبانی نمی کند، بنابراین من به جای آن از شماره CSR استفاده کردم. به همین دلیل است که شما به جای mtopei 0x35C را می بینید، اما معنی آنها یکسان است.

وقتی از رجیستر MTOPEI (0x35C) می خوانیم، شماره پیام بالاترین اولویت را به ما می دهد. دستور csrrw در قطعه کد بالا به صورت اتمی مقدار CSR را در ثبات بازگشتی می خواند و سپس مقدار صفر را در CSR ذخیره می کند.

وقتی صفر را در رجیستر MTOPEI (0x35C) می نویسیم، به IMSIC می گوییم که “ادعا” می کنیم که بالاترین پیام را مدیریت می کنیم، که بیت EIP را برای شماره پیام مربوطه پاک می کند.

/// Handle an IMSIC trap. Called from `trap::rust_trap`
pub fn imsic_m_handle() {
    let msgnum = imsic_m_pop();
    match msgnum {
        0 => println!("Spurious message (MTOPEI returned 0)"),
        2 => println!("First test triggered by MMIO write successful!"),
        4 => println!("Second test triggered by EIP successful!"),
        _ => println!("Unknown msi #{}", v),
    }
}

پیام 0 یک پیام معتبر نیست زیرا وقتی از MTOPEI خارج می شویم، 0 علامت “بدون وقفه” می دهد.

خروجی نمونه

اگر مخزن را با یک QEMU جدید اجرا کنید، پس از یک آزمایش موفقیت آمیز باید موارد زیر را مشاهده کنید.


نتیجه

در این پست شاهد اضافه شدن رجیسترهای جدید برای پشتیبانی از کنترلر MSI ورودی (IMSIC) برای RISC-V بودیم. ما همچنین تحویل IMSIC، پیام‌های فردی را فعال کردیم و دو روش برای ارسال پیام انجام دادیم: مستقیماً از طریق MMIO یا با تنظیم بیت EIP مربوطه. در نهایت، وقفه را از تله کنترل کردیم.


بعد چه می شود؟

بخش دوم کتابچه راهنمای AIA شامل کنترلر جدید وقفه در سطح پلتفرم پیشرفته یا APLIC است. ما این سیستم و همچنین نوشتن درایورها را بررسی می‌کنیم تا ببینیم این APLIC جدید چگونه می‌تواند با سیم یا پیام سیگنال دهد.

پس از APLIC، یک درایور برای دستگاه PCI می نویسیم و از آن برای ارسال پیام های MSI-X استفاده می کنیم.

لینک منبع

ارسال یک پاسخ

آدرس ایمیل شما منتشر نخواهد شد.