Skip to content

Commit

Permalink
Make FFI generation automatic
Browse files Browse the repository at this point in the history
  • Loading branch information
Stovent committed Nov 21, 2022
1 parent 52bb411 commit ea71f6c
Show file tree
Hide file tree
Showing 14 changed files with 461 additions and 335 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
!.gitignore
!Cargo.toml
!CHANGELOG.md
!CINTERFACE.md
!generate_headers.ps1
!generate_headers.sh
!LICENSE
!README.md

Expand Down
196 changes: 196 additions & 0 deletions CINTERFACE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
# m68000 C interface

By enabling the `ffi` feature, the following structs and enums are made `repr(C)`:
- AddressingMode
- BriefExtensionWord
- Direction
- Instruction
- Operands
- Registers
- Size
- StatusRegister
- Vector

The crate `m68000-ffi` in the repo is a collection of structures and functions that allows using m68000's
interpreter and disassembler in other languages through a C interface.

## Build the C interface

First, build the two static libraries by building the project using the correct target toolchain.
To change the build toolchain, add `+<toolchain name>`.
```sh
cargo build --lib --release --features="ffi"
# or
cargo +nightly-x86_64-pc-windows-gnu build --lib --release --features="ffi"
```

Then, include the given header files `m68000.h` and `m68000-ffi.h` in your project.

If you want to generate the header files from scratch, install [cargo-expand](https://github.com/dtolnay/cargo-expand) with the following command `cargo install cargo-expand`, then generate the header files using the `generate_headers.ps1` script on Windows or `generate_headers.sh` on Linux.

## Use the C interface

You need to link to the two generated libraries in your project:
- `libm68000` which contains the Rust core of the library.
- `libm68000_ffi` which contains the interface code between Rust and C.

The `m68000.h` header contains the m68000 library data structures.
The `m68000-ffi.h` header is the one to include in your C files and it contains the structs and functions declarations of the interface.

Because the main Rust library is generic over a certain CPU type trait, and this trait is only usable in Rust code, the C interface exports both of the pre-defined CPU types.

Each function name starts with the library name `m68000_`, then the CPU type `mc68000_` or `scc68070_` and finally the action performed by the function. Make sure you use the correct core with the correct functions.

The complete documentation for the functions and structures can be found in the `m68000-ffi/lib.rs` file.
See the C example below for a basic start.

Include the generated header file in your project, and define your memory access callback functions. These functions will be passed to the core through a M68000Callbacks struct.

The returned values are in a GetSetResult struct. Set `GetSetResult.exception` to 0 and set `GetSetResult.data` to the value to be returned on success. Set `GetSetResult.exception` to 2 (Access Error vector) if an Access Error occurs.

## C example

```c
#include "m68000-ffi.h"

#include <stdint.h>
#include <stdlib.h>

#define MEMSIZE (1 << 20) // 1 MB.

GetSetResult getByte(uint32_t addr, void* user_data)
{
const uint8_t* memory = user_data;
if(addr < MEMSIZE)
return (GetSetResult){
.data = memory[addr],
.exception = 0,
};

// If out of range, return an Access (bus) error.
return (GetSetResult){
.data = 0,
.exception = 2,
};
}

GetSetResult getWord(uint32_t addr, void* user_data)
{
const uint8_t* memory = user_data;
if(addr < MEMSIZE)
return (GetSetResult){
.data = (uint16_t)memory[addr] << 8
| (uint16_t)memory[addr + 1],
.exception = 0,
};

// If out of range, return an Access (bus) error.
return (GetSetResult){
.data = 0,
.exception = 2,
};
}

GetSetResult getLong(uint32_t addr, void* user_data)
{
const uint8_t* memory = user_data;
if(addr < MEMSIZE)
return (GetSetResult){
.data = (uint32_t)memory[addr] << 24
| (uint32_t)memory[addr + 1] << 16
| (uint32_t)memory[addr + 2] << 8
| memory[addr + 3],
.exception = 0,
};

// If out of range, return an Access (bus) error.
return (GetSetResult){
.data = 0,
.exception = 2,
};
}

GetSetResult setByte(uint32_t addr, uint8_t data, void* user_data)
{
uint8_t* memory = user_data;
GetSetResult res = {
.data = 0,
.exception = 0,
};

if(addr < MEMSIZE)
memory[addr] = data;
else
res.exception = 2;

return res;
}

GetSetResult setWord(uint32_t addr, uint16_t data, void* user_data)
{
uint8_t* memory = user_data;
GetSetResult res = {
.data = 0,
.exception = 0,
};

if(addr < MEMSIZE)
{
memory[addr] = data >> 8;
memory[addr + 1] = data;
}
else
res.exception = 2;

return res;
}

GetSetResult setLong(uint32_t addr, uint32_t data, void* user_data)
{
uint8_t* memory = user_data;
GetSetResult res = {
.data = 0,
.exception = 0,
};

if(addr < MEMSIZE)
{
memory[addr] = data >> 24;
memory[addr + 1] = data >> 16;
memory[addr + 2] = data >> 8;
memory[addr + 3] = data;
}
else
res.exception = 2;

return res;
}

void reset(void* user_data) {}

int main()
{
uint8_t* memory = malloc(MEMSIZE);
// Check if malloc is successful, then load your program in memory here.
// Next create the memory callback structure:
M68000Callbacks callbacks = {
.get_byte = getByte,
.get_word = getWord,
.get_long = getLong,
.set_byte = setByte,
.set_word = setWord,
.set_long = setLong,
.reset_instruction = reset,
.user_data = memory,
};

m68000_mc68000_t* core = m68000_mc68000_new(); // Create a new core.
// Now execute instructions as you want.
m68000_mc68000_interpreter(core, &callbacks);

// end of the program.
m68000_mc68000_delete(core);
free(memory);
return 0;
}
```
Loading

0 comments on commit ea71f6c

Please sign in to comment.