use alloc::boxed::Box;
use alloc::string::{String, ToString};
use alloc::sync::Arc;
use core::fmt;
use crate::core::device::{Device, DeviceClass, PropertySpec, RealizeCtx, ResetKind};
use crate::core::error::{BusError, Error, Result};
use crate::core::props::{Props, ValueKind};
use crate::core::space::{
AccessConstraints, MemAttrs, MemOps, MemResult, Region as MmioRegion, RegionRef,
};
use crate::core::state::{ChunkReader, ChunkWriter, Sink, Source};
use crate::core::sync::{LockRank, Mutex};
use crate::core::value::Width;
use crate::core::wire::{Level, WireSource};
pub const REGISTER_BASE: u64 = 0xff00;
pub const REGISTER_REGION: &str = "regs";
pub const IRQ_PIN: &str = "irq";
pub const DEFAULT_PAD_PORT: &str = "gb-joypad";
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Button {
Right,
Left,
Up,
Down,
A,
B,
Select,
Start,
}
impl Button {
pub const ALL: [Button; 8] = [
Button::Right,
Button::Left,
Button::Up,
Button::Down,
Button::A,
Button::B,
Button::Select,
Button::Start,
];
#[must_use]
pub const fn bit(self) -> u8 {
match self {
Button::Right => 0,
Button::Left => 1,
Button::Up => 2,
Button::Down => 3,
Button::A => 4,
Button::B => 5,
Button::Select => 6,
Button::Start => 7,
}
}
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Button::Right => "right",
Button::Left => "left",
Button::Up => "up",
Button::Down => "down",
Button::A => "a",
Button::B => "b",
Button::Select => "select",
Button::Start => "start",
}
}
#[must_use]
pub fn from_name(name: &str) -> Option<Button> {
Button::ALL.into_iter().find(|b| b.name() == name)
}
}
impl fmt::Display for Button {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
#[derive(Debug, Clone, Copy, Default)]
struct State {
pressed: u8,
select: u8,
}
impl State {
fn nibble(&self) -> u8 {
let mut low = 0x0f;
if self.select & 0x10 == 0 {
low &= !(self.pressed & 0x0f);
}
if self.select & 0x20 == 0 {
low &= !((self.pressed >> 4) & 0x0f);
}
low
}
fn asserted(&self) -> bool {
self.nibble() != 0x0f
}
fn read(&self) -> u8 {
0xc0 | (self.select & 0x30) | self.nibble()
}
}
pub struct GbJoypad {
pad: Arc<GbPad>,
regs_region: RegionRef,
}
impl fmt::Debug for GbJoypad {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("GbJoypad")
.field("pad", &self.pad)
.finish_non_exhaustive()
}
}
impl Default for GbJoypad {
fn default() -> Self {
GbJoypad::new()
}
}
pub struct GbPad {
state: Mutex<State>,
irq: Mutex<Option<WireSource>>,
}
impl fmt::Debug for GbPad {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("GbPad")
.field("state", &self.state)
.finish_non_exhaustive()
}
}
impl Default for GbPad {
fn default() -> GbPad {
GbPad::new()
}
}
impl GbPad {
#[must_use]
pub fn new() -> GbPad {
GbPad {
state: Mutex::with_rank(LockRank::DEVICE, State::default()),
irq: Mutex::with_rank(LockRank::WIRE, None),
}
}
#[must_use]
pub fn buttons(&self) -> u8 {
self.state.lock().pressed
}
#[must_use]
pub fn read(&self) -> u8 {
self.state.lock().read()
}
pub fn set_pressed(&self, button: Button, pressed: bool) {
let asserted = {
let mut state = self.state.lock();
if pressed {
state.pressed |= 1 << button.bit();
} else {
state.pressed &= !(1 << button.bit());
}
state.asserted()
};
self.drive(asserted);
}
pub fn set_buttons(&self, pressed: u8) {
let asserted = {
let mut state = self.state.lock();
state.pressed = pressed;
state.asserted()
};
self.drive(asserted);
}
pub fn attach_irq(&self, source: WireSource) {
*self.irq.lock() = Some(source);
let asserted = self.state.lock().asserted();
self.drive(asserted);
}
fn drive(&self, asserted: bool) {
let source = self.irq.lock().clone();
if let Some(source) = source {
source.set(Level::from_bool(asserted));
}
}
}
impl GbJoypad {
#[must_use]
pub fn new() -> GbJoypad {
GbJoypad::with_pad(Arc::new(GbPad::new()))
}
#[must_use]
pub fn with_pad(pad: Arc<GbPad>) -> GbJoypad {
let regs_region = Arc::new(MmioRegion::io(
"gb.joypad.regs",
1,
Arc::new(JoypadPort {
pad: Arc::clone(&pad),
}) as Arc<dyn MemOps>,
));
GbJoypad { pad, regs_region }
}
pub fn from_props(props: &Props) -> Result<GbJoypad> {
let mut r = props.reader();
let name = r.or_str("pad", DEFAULT_PAD_PORT)?.to_string();
r.finish()?;
Ok(GbJoypad::with_pad(pads::attach(props, &name)?))
}
#[must_use]
pub fn pad(&self) -> &Arc<GbPad> {
&self.pad
}
#[must_use]
pub fn buttons(&self) -> u8 {
self.pad.buttons()
}
#[must_use]
pub fn read(&self) -> u8 {
self.pad.read()
}
}
pub mod pads {
use super::GbPad;
use alloc::string::String;
use alloc::sync::Arc;
use alloc::vec::Vec;
use crate::core::error::Result;
use crate::core::hosts::{HostKind, HostObjects};
use crate::core::props::Props;
use crate::core::record::{Channel, FnSink, InputSink};
pub const KIND: HostKind = HostKind::new("pad");
pub fn open(hosts: &HostObjects, name: &str) -> Result<Arc<GbPad>> {
hosts.open(KIND, name, GbPad::new)
}
pub fn attach(props: &Props, name: &str) -> Result<Arc<GbPad>> {
props.host(KIND, name, GbPad::new)
}
pub fn get(hosts: &HostObjects, name: &str) -> Result<Option<Arc<GbPad>>> {
hosts.get(KIND, name)
}
pub fn close(hosts: &HostObjects, name: &str) -> bool {
hosts.close(KIND, name)
}
#[must_use]
pub fn names(hosts: &HostObjects) -> Vec<String> {
hosts.names(KIND)
}
#[must_use]
pub fn channel(name: &str) -> Channel {
Channel::new(KIND, name)
}
#[must_use]
pub fn sink(pad: &Arc<GbPad>) -> Arc<dyn InputSink> {
let pad = Arc::clone(pad);
Arc::new(FnSink::new("pad", move |payload: &[u8]| {
for byte in payload {
pad.set_buttons(*byte);
}
}))
}
}
struct JoypadPort {
pad: Arc<GbPad>,
}
impl fmt::Debug for JoypadPort {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("JoypadPort").finish_non_exhaustive()
}
}
impl MemOps for JoypadPort {
fn read(&self, _offset: u64, dst: &mut [u8], _attrs: MemAttrs) -> MemResult {
let [byte] = dst else {
return Err(BusError::BadAccess);
};
*byte = self.pad.state.lock().read();
Ok(())
}
fn write(&self, _offset: u64, src: &[u8], _attrs: MemAttrs) -> MemResult {
let [value] = src else {
return Err(BusError::BadAccess);
};
self.pad.state.lock().select = *value & 0x30;
Ok(())
}
fn constraints(&self) -> AccessConstraints {
AccessConstraints::IO.with_widths(Width::U8, Width::U8)
}
}
static JOYPAD_PROPERTIES: &[PropertySpec] = &[PropertySpec {
name: "pad",
kind: ValueKind::Str,
required: false,
summary: "the host pad port buttons arrive through, by name (default \"gb-joypad\")",
}];
pub static CLASS: DeviceClass = DeviceClass {
name: "gb.joypad",
version: 1,
summary: "Game Boy joypad matrix ($FF00): two selectable rows of four active-low lines",
properties: JOYPAD_PROPERTIES,
construct: |props| Ok(Box::new(GbJoypad::from_props(props)?) as Box<dyn Device>),
};
pub fn register(reg: &mut crate::core::Registry) -> Result<()> {
reg.add(&CLASS)
}
impl Device for GbJoypad {
fn class(&self) -> &'static DeviceClass {
&CLASS
}
fn realize(&self, _ctx: &mut RealizeCtx<'_>) -> Result<()> {
let asserted = self.pad.state.lock().asserted();
self.pad.drive(asserted);
Ok(())
}
fn region(&self, name: &str) -> Option<RegionRef> {
(name.is_empty() || name == REGISTER_REGION).then(|| Arc::clone(&self.regs_region))
}
fn connect(&self, port: &str, source: WireSource) -> Result<()> {
if port != IRQ_PIN {
return Err(Error::Config {
at: String::from(port),
message: alloc::format!("the joypad drives only `{IRQ_PIN}`"),
});
}
self.pad.attach_irq(source);
Ok(())
}
fn announce(&self, port: &str) {
if port == IRQ_PIN {
let asserted = self.pad.state.lock().asserted();
self.pad.drive(asserted);
}
}
fn reset(&self, _kind: ResetKind) {
self.pad.state.lock().select = 0;
let asserted = self.pad.state.lock().asserted();
self.pad.drive(asserted);
}
fn save(&self, w: &mut ChunkWriter<'_>) -> Result<()> {
let state = *self.pad.state.lock();
w.write_u8(state.pressed)?;
w.write_u8(state.select)?;
Ok(())
}
fn load(&self, r: &mut ChunkReader<'_>) -> Result<()> {
let state = State {
pressed: r.read_u8()?,
select: r.read_u8()?,
};
*self.pad.state.lock() = state;
self.pad.drive(state.asserted());
Ok(())
}
}
impl crate::machine::Instance for GbJoypad {}
pub fn bind(bindings: &mut crate::machine::Bindings) -> Result<()> {
bindings.bind(CLASS.name, |props| {
Ok(Arc::new(GbJoypad::from_props(props)?))
})
}
#[must_use]
pub fn schema() -> crate::machine::validate::ClassSchema {
use crate::machine::validate::{ClassSchema, PortDir, PropSchema};
ClassSchema::new(CLASS.name)
.prop(PropSchema::new("pad", ValueKind::Str))
.port(IRQ_PIN, PortDir::Out)
.region(REGISTER_REGION)
}