UEFI Native API on mruby

mrubyでブートローダを書けるようにしました - かーねる・う゛いえむにっきの記事はlibc API経由でmrubyからUEFIの機能を使う話でした。
これとは別に、mruby on efi shellにはUEFIのネイティブAPIへアクセスするためのクラスが用意されているという話をします。

mruby on efi shellのソースコードを眺めていると、二つのスクリプトが見つかります(こちら)。

一つはdump_cmos.rbで、UEFI::LowLevel.io_read8/write8を呼び出してIOポートアクセスを行っている事が分かります。

################################################3
# Example
#  CMOS dump.

class String
  def rjust(width, padding=' ')
    str = ""
    if (self.length < width)
      str = padding * (width - self.length)
    end
    return str + self
  end
end

CMOS_INDEX = 0x70
CMOS_DATA = 0x71

orig_index = UEFI::LowLevel.io_read8(CMOS_INDEX)
(0..0x7F).to_a.each_with_index do |i, index|
  UEFI::LowLevel.io_write8(CMOS_INDEX, i)
  v = UEFI::LowLevel.io_read8(CMOS_DATA)
  print "#{v.to_s(16).rjust(2, '0')} "
  print "\n" if (index % 16 == 15)
end
UEFI::LowLevel.io_write8(CMOS_INDEX, orig_index)

もう一つがread_disk.rbで、こちらはUEFI::ProtocolやUEFI::BootService、UEFI::Guidなどのクラスを用いてEFI_DISK_IO_PROTOCOLへアクセスし、メディア情報を取得しています。

# Sample of BLOCK_IO_PROTOCOL

def print_binary_data(data)
  data.bytes.each_slice(16) do |data|
    # print hex.
    data.each do |i|
      print i.to_s(16).rjust(2, "0").upcase
      print " "
    end
    print "   "
    # print ascii.
    data.each do |i|
      print (0x20 <= i && i <= 0x7F) ? i.chr : "."
    end
    puts ""
  end
  puts ""
end

class BlockIoProtocol < UEFI::Protocol
  GUID = UEFI::Guid.new("964e5b21-6459-11d2-8e39-00a0c969723b")

  define_variable(:revision, :u64)
  define_variable(:media, :p)
  define_function(:reset, :e, [:p, :b])
  define_function(:read_blocks, :e, [:p, :u32, :u64, :u64, :p])
  #...
end

# I will rename UEFI::Protocol to UEFI::Structure or any other name...
class Media < UEFI::Protocol
  define_variable(:media_id, :uint32)
  #...
end

# See http://wiki.phoenix.com/wiki/index.php/EFI_DISK_IO_PROTOCOL
class DiskIoProtocol < UEFI::Protocol
  GUID = UEFI::Guid.new("CE345171-BA0B-11d2-8e4F-00a0c969723b")

  define_variable(:revision, :u64)
  define_function(:read_disk, :efi_status, [:p, :u32, :u64, :u64, :p])
  #...
end


# Find first disk.
handles = UEFI::BootService.locate_handle_buffer(BlockIoProtocol::GUID)
handle = handles.first
puts "handle: #{handle}"

# Locate BlockIoProtocol to get the pointer of media.
ptr = UEFI::BootService.handle_protocol(handle, BlockIoProtocol::GUID)
bp = BlockIoProtocol.new(ptr)
# Get media.
media = Media.new(bp.media)
puts "media_id: #{media.media_id}"

# Find DiskIoProtocol related to bp.
ptr = UEFI::BootService.handle_protocol(handle, DiskIoProtocol::GUID)
dp = DiskIoProtocol.new(ptr)

buf = " " * 512  # Buffer to be filled with returned data.
st = dp.read_disk(ptr, media.media_id, 0, buf.size, buf)
if (st.success?)
  print_binary_data(buf)
else
  puts "ERROR: #{st}"
end

プロトコルのハンドルを作るところの操作でクラスを継承してインタフェースを動的に定義するなどの違いはありますが、概ねEDK2に含まれるCで書かれたUEFIのサンプルアプリと同様のインタフェースを持っている事が分かります。

C拡張のコード(mruby_on_efi_shell/src at master · masamitsu-murase/mruby_on_efi_shell · GitHub)を眺めてみると、UEFI::Handle, UEFI::Pointer, UEFI::Protocol, UEFI::Status, UEFI::BootService, UEFI::LowLevel, UEFI::RuntimeServiceのようなクラスが定義されています。

このインタフェースもEDK2で使われているものと大体一致しているので、完全ではないかもしれないがCのコードを翻訳していけばだいたいmrubyで記述出来そうだと予想されます。

今回はここまでで、次回何らかのサンプルコードを書いてみることにします。