Skip to content

Commit

Permalink
feat(doc): README ToC, device driver tutorial
Browse files Browse the repository at this point in the history
- update README with table of contents
- move tutorial do add device drivers to dedicated file
  • Loading branch information
P-Miranda committed Jun 24, 2024
1 parent 833d657 commit dc82989
Show file tree
Hide file tree
Showing 2 changed files with 204 additions and 169 deletions.
195 changes: 26 additions & 169 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,30 @@

IOb-SoC-OpenCryptoLinux is a System-on-Chip (SoC) template comprising an open-source RISC-V processor (VexRiscv), an internal boot memory subsystem, a UART, and an interface to external memory. The external memory interface is a 3rd party memory controller IP (typically a DDR controller) using an AXI4 master bus.

## Getting Started (Quickly)
* * *
## Table of Contents
- [Getting Started (Quickly)](https://github.com/iobundle/iob-soc-opencryptolinux#getting-started-quickly)
- [Differences to IOb-SoC](https://github.com/iobundle/iob-soc-opencryptolinux#differences-to-iob-soc)
- [Nix Environment](https://github.com/iobundle/iob-soc-opencryptolinux#nix-environment)
- [Ethernet Simulation](https://github.com/iobundle/iob-soc-opencryptolinux#ethernet-simulation)
- [Ethernet Receiver MAC Address](https://github.com/iobundle/iob-soc-opencryptolinux#ethernet-receiver-mac-address)
- [Dependencies](https://github.com/iobundle/iob-soc-opencryptolinux#dependencies)
- [Operating Systems](https://github.com/iobundle/iob-soc-opencryptolinux#operating-systems)
- [Clone the Repository](https://github.com/iobundle/iob-soc-opencryptolinux#clone-the-repository)
- [Configure your SoC](https://github.com/iobundle/iob-soc-opencryptolinux#configure-your-soc)
- [Setup environment variables](https://github.com/iobundle/iob-soc-opencryptolinux#set-environment-variables-for-local-or-remote-building-and-running)
- [Create the Build directory](https://github.com/iobundle/iob-soc-opencryptolinux#create-the-build-directory)
- [Simulate the System](https://github.com/iobundle/iob-soc-opencryptolinux#simulate-the-system)
- [Build and run on FPGA](https://github.com/iobundle/iob-soc-opencryptolinux#build-and-run-on-fpga-board)
- [Compile documentation](https://github.com/iobundle/iob-soc-opencryptolinux#compile-the-documentation)
- [Total test](https://github.com/iobundle/iob-soc-opencryptolinux#total-test)
- [Clean the Build directory](https://github.com/iobundle/iob-soc-opencryptolinux#cleaning-the-build-directory)
- [Compile documentation](https://github.com/iobundle/iob-soc-opencryptolinux#compile-the-documentation)
- [Install the RISC-V GNU Compiler Toolchain](https://github.com/iobundle/iob-soc-opencryptolinux#instructions-for-installing-the-risc-v-gnu-compiler-toolchain)
- [Acknowledgement](https://github.com/iobundle/iob-soc-opencryptolinux#acknowledgement)


# Getting Started (Quickly)

This section offers a quick guide for users to start using IOb-SoC-OpenCryptoLinux. The following commands streamline the setup process for simulation and running on an FPGA board:

Expand Down Expand Up @@ -315,174 +338,8 @@ export PATH=$PATH:/path/to/riscv/bin
The above command should be added to your `~/.bashrc` file so you do not have to type it on every session.

# Tutorial: Add New Device Driver
This tutorial presents the steps required to create simple linux character device drivers for a device and use them in OpenCryptoLinux.
The [IOb-SPI](https://github.com/IObundle/iob-spi) device is used as an example.
To follow this tutorial, clone the [iob-linux](https://github.com/IObundle/iob-linux) repository.

1. Create device drivers: on the device repository create a `software/linux` directory with the following files:
```bash
software/
├── linux
│   ├── drivers
│   │   ├── driver.mk
│   │   └── iob_spi_main.c
│   ├── iob_spi.dts
│   └── Readme.md
```
1.1. `software/linux/drivers/driver.mk`: compilation makefile segment
```make
iob_spi_master-objs := iob_spi_main.o iob_class/iob_class_utils.o
```
1.2. `software/linux/drivers/iob_spi_main.c`: main driver file, see [TODO: SPI commit] for full code snippet.
1.2.1. Rename the functions to the particular device
1.2.2. Update the `iob_spi_read()` function implementation according with the read registers of the device. SPI has the `FL_READY`, `FL_DATAOUT` and `VERSION` (implicit) read registers:
```C
static ssize_t iob_spi_read(struct file *file, char __user *buf, size_t count,
loff_t *ppos) {
// (...)

/* read value from register */
switch (*ppos) {
// Add one case for each Read software register
case IOB_SPI_MASTER_FL_READY_ADDR:
value =
iob_data_read_reg(iob_spi_data->regbase, IOB_SPI_MASTER_FL_READY_ADDR,
IOB_SPI_MASTER_FL_READY_W);
size = (IOB_SPI_MASTER_FL_READY_W >> 3); // bit to bytes
pr_info("[Driver] %s: Read FL_READY: 0x%x\n", IOB_SPI_MASTER_DRIVER_NAME,
value);
break;
case IOB_SPI_MASTER_FL_DATAOUT_ADDR:
value =
iob_data_read_reg(iob_spi_data->regbase, IOB_SPI_MASTER_FL_DATAOUT_ADDR,
IOB_SPI_MASTER_FL_DATAOUT_W);
size = (IOB_SPI_MASTER_FL_DATAOUT_W >> 3); // bit to bytes
pr_info("[Driver] %s: Read FL_DATAOUT: 0x%x\n", IOB_SPI_MASTER_DRIVER_NAME,
value);
break;
// NOTE: version software register is implicit and always added
case IOB_SPI_MASTER_VERSION_ADDR:
value = iob_data_read_reg(iob_spi_data->regbase, IOB_SPI_MASTER_VERSION_ADDR,
IOB_SPI_MASTER_VERSION_W);
size = (IOB_SPI_MASTER_VERSION_W >> 3); // bit to bytes
pr_info("[Driver] %s: Read version 0x%x\n", IOB_SPI_MASTER_DRIVER_NAME, value);
break;
default:
// invalid address - no bytes read
return 0;
}

// (...)
}
```
1.2.3. Update the `iob_spi_write()` function implementation according with the write registers of the device. SPI had the `FL_RESET`, `FL_DATAIN`, `FL_ADDRESS`, `FL_COMMAND`, `FL_COMMANDTP` and `FL_VALIDFLG`write registers:
```C
static ssize_t iob_spi_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos) {
// (...)
switch (*ppos) {
case IOB_SPI_MASTER_FL_RESET_ADDR:
size = (IOB_SPI_MASTER_FL_RESET_W >> 3); // bit to bytes
if (read_user_data(buf, size, &value))
return -EFAULT;
iob_data_write_reg(iob_spi_data->regbase, value,
IOB_SPI_MASTER_FL_RESET_ADDR,
IOB_SPI_MASTER_FL_RESET_W);
pr_info("[Driver] %s: FL_RESET iob_spi: 0x%x\n",
IOB_SPI_MASTER_DRIVER_NAME, value);
break;
case IOB_SPI_MASTER_FL_DATAIN_ADDR:
// FL_DATAIN access implementation
break;
case IOB_SPI_MASTER_FL_ADDRESS_ADDR:
// FL_ADDRESS access implementation
break;
case IOB_SPI_MASTER_FL_COMMAND_ADDR:
// FL_COMMAND access implementation
break;
case IOB_SPI_MASTER_FL_COMMANDTP_ADDR:
// FL_COMMANDTP access implementation
break;
case IOB_SPI_MASTER_FL_VALIDFLG_ADDR:
// FL_VALIDFLG access implementation
break;
default:
pr_info("[Driver] %s: Invalid write address 0x%x\n", IOB_SPI_MASTER_DRIVER_NAME,
(unsigned int)*ppos);
// invalid address - no bytes written
return 0;
}
return count;
}
```
1.2.4. Notice the string used in the `struct of_device_id`. This string must match the `compatible` field in the device tree to associate the hardware device with the correct driver.
```C
static const struct of_device_id of_iob_spi_match[] = {
{.compatible = "iobundle,spi0"},
{},
};
```
1.3. Generate `iob_spi_master.h` and `iob_spi_master_sysfs_multi.h` header files with the [`drivers.py`](https://github.com/IObundle/iob-linux/blob/main/scripts/drivers.py) script:
```bash
python3 .path/to/iob-linux/scripts/drivers.py iob_spi_master -o [output_dir]
```
2. Update device tree with device node. The `compatible` string must match with the `compatible` field in the driver source:
```
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
/* Copyright (c) 2024 IObundle */
/dts-v1/;
/ {
#address-cells = <1>;
#size-cells = <1>;
model = "IOb-SoC, VexRiscv";
compatible = "IOb-SoC, VexRiscv";
// CPU
// Memory
// Choosen
soc {
#address-cells = <1>;
#size-cells = <1>;
compatible = "iobundle,iob-soc", "simple-bus";
ranges;
// Other SOC peripherals go here
// Add this Node to device tree
SPI0: spi@/*SPI0_ADDR_MACRO*/ {
compatible = "iobundle,spi0";
reg = <0x/*SPI0_ADDR_MACRO*/ 0x100>;
};
};
};
```
3. Compile driver module and add to rootfs.
3.1. Copy device driver files to [`iob-linux/software/drivers`](https://github.com/IObundle/iob-linux/tree/main/software/drivers)
3.2. Run `make all` target. Note that linux driver module compilation requires an already built linux kernel:
```bash
cd ./path/to/iob-linux/software/drivers
make all
```
3.3. Add module files to buildroot:
```bash
# copy module files to directory used to build rootfs
cp ./path/to/iob-linux/software/drivers $OS_SOFTWARE_DIR
# change directory to iob-linux
cd ./path/to/iob-linux
# re-build buildroot
make build-buildroot OS_SOFTWARE_DIR=$OS_SOFTWARE_DIR
```
4. Load module in linux. After booting into linux, use the `insmod` command to load the new driver modules:
```bash
# inside linux console
insmod ./path/to/driver.ko
# iob-spi example:
insmod /drivers/iob_spi_master.ko
```
Checkout [this tutorial](document/device_driver_tutorial.md) for more details on
how to add a new device to be tested.

# Acknowledgement
The [OpenCryptoLinux](https://nlnet.nl/project/OpenCryptoLinux/) project was funded through the NGI Assure Fund, a fund established by NLnet with financial support from the European Commission's Next Generation Internet programme, under the aegis of DG Communications Networks, Content and Technology under grant agreement No 957073.
Expand Down
178 changes: 178 additions & 0 deletions document/device_driver_tutorial.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
# Tutorial: Add New Device Driver
This tutorial presents the steps required to create simple linux character device drivers for a device and use them in OpenCryptoLinux.
The [IOb-SPI](https://github.com/IObundle/iob-spi) device is used as an example.
To follow this tutorial, clone the [iob-linux](https://github.com/IObundle/iob-linux) repository.

1. Create device drivers: on the device repository create a `software/linux` directory with the following files:
```bash
software/
├── linux
│   ├── drivers
│   │   ├── driver.mk
│   │   └── iob_spi_main.c
│   ├── iob_spi.dts
│   └── Readme.md
```

1.1. `software/linux/drivers/driver.mk`: compilation makefile segment
```make
iob_spi_master-objs := iob_spi_main.o iob_class/iob_class_utils.o
```

1.2. `software/linux/drivers/iob_spi_main.c`: main driver file, see [this commit](https://github.com/IObundle/iob-spi/commit/dce9c93f1202da96d3570f88e31b9409afb43419) for full code snippet.
1.2.1. Rename the functions to the particular device
1.2.2. Update the `iob_spi_read()` function implementation according with the read registers of the device. SPI has the `FL_READY`, `FL_DATAOUT` and `VERSION` (implicit) read registers:
```C
static ssize_t iob_spi_read(struct file *file, char __user *buf, size_t count,
loff_t *ppos) {
// (...)

/* read value from register */
switch (*ppos) {
// Add one case for each Read software register
case IOB_SPI_MASTER_FL_READY_ADDR:
value =
iob_data_read_reg(iob_spi_data->regbase, IOB_SPI_MASTER_FL_READY_ADDR,
IOB_SPI_MASTER_FL_READY_W);
size = (IOB_SPI_MASTER_FL_READY_W >> 3); // bit to bytes
pr_info("[Driver] %s: Read FL_READY: 0x%x\n", IOB_SPI_MASTER_DRIVER_NAME,
value);
break;
case IOB_SPI_MASTER_FL_DATAOUT_ADDR:
value =
iob_data_read_reg(iob_spi_data->regbase, IOB_SPI_MASTER_FL_DATAOUT_ADDR,
IOB_SPI_MASTER_FL_DATAOUT_W);
size = (IOB_SPI_MASTER_FL_DATAOUT_W >> 3); // bit to bytes
pr_info("[Driver] %s: Read FL_DATAOUT: 0x%x\n", IOB_SPI_MASTER_DRIVER_NAME,
value);
break;
// NOTE: version software register is implicit and always added
case IOB_SPI_MASTER_VERSION_ADDR:
value = iob_data_read_reg(iob_spi_data->regbase, IOB_SPI_MASTER_VERSION_ADDR,
IOB_SPI_MASTER_VERSION_W);
size = (IOB_SPI_MASTER_VERSION_W >> 3); // bit to bytes
pr_info("[Driver] %s: Read version 0x%x\n", IOB_SPI_MASTER_DRIVER_NAME, value);
break;
default:
// invalid address - no bytes read
return 0;
}

// (...)
}
```
1.2.3. Update the `iob_spi_write()` function implementation according with the write registers of the device. SPI had the `FL_RESET`, `FL_DATAIN`, `FL_ADDRESS`, `FL_COMMAND`, `FL_COMMANDTP` and `FL_VALIDFLG`write registers:
```C
static ssize_t iob_spi_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos) {
// (...)
switch (*ppos) {
case IOB_SPI_MASTER_FL_RESET_ADDR:
size = (IOB_SPI_MASTER_FL_RESET_W >> 3); // bit to bytes
if (read_user_data(buf, size, &value))
return -EFAULT;
iob_data_write_reg(iob_spi_data->regbase, value,
IOB_SPI_MASTER_FL_RESET_ADDR,
IOB_SPI_MASTER_FL_RESET_W);
pr_info("[Driver] %s: FL_RESET iob_spi: 0x%x\n",
IOB_SPI_MASTER_DRIVER_NAME, value);
break;
case IOB_SPI_MASTER_FL_DATAIN_ADDR:
// FL_DATAIN access implementation
break;
case IOB_SPI_MASTER_FL_ADDRESS_ADDR:
// FL_ADDRESS access implementation
break;
case IOB_SPI_MASTER_FL_COMMAND_ADDR:
// FL_COMMAND access implementation
break;
case IOB_SPI_MASTER_FL_COMMANDTP_ADDR:
// FL_COMMANDTP access implementation
break;
case IOB_SPI_MASTER_FL_VALIDFLG_ADDR:
// FL_VALIDFLG access implementation
break;
default:
pr_info("[Driver] %s: Invalid write address 0x%x\n", IOB_SPI_MASTER_DRIVER_NAME,
(unsigned int)*ppos);
// invalid address - no bytes written
return 0;
}
return count;
}
```
1.2.4. Notice the string used in the `struct of_device_id`. This string must match the `compatible` field in the device tree to associate the hardware device with the correct driver.
```C
static const struct of_device_id of_iob_spi_match[] = {
{.compatible = "iobundle,spi0"},
{},
};
```

1.3. Generate `iob_spi_master.h` and `iob_spi_master_sysfs_multi.h` header files with the [`drivers.py`](https://github.com/IObundle/iob-linux/blob/main/scripts/drivers.py) script:
```bash
python3 .path/to/iob-linux/scripts/drivers.py iob_spi_master -o [output_dir]
```

2. Update device tree with device node. The `compatible` string must match with the `compatible` field in the driver source:
```
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
/* Copyright (c) 2024 IObundle */
/dts-v1/;
/ {
#address-cells = <1>;
#size-cells = <1>;
model = "IOb-SoC, VexRiscv";
compatible = "IOb-SoC, VexRiscv";
// CPU
// Memory
// Choosen
soc {
#address-cells = <1>;
#size-cells = <1>;
compatible = "iobundle,iob-soc", "simple-bus";
ranges;
// Other SOC peripherals go here
// Add this Node to device tree
SPI0: spi@/*SPI0_ADDR_MACRO*/ {
compatible = "iobundle,spi0";
reg = <0x/*SPI0_ADDR_MACRO*/ 0x100>;
};
};
};
```

3. Compile driver module and add to rootfs.

3.1. Copy device driver files to [`iob-linux/software/drivers`](https://github.com/IObundle/iob-linux/tree/main/software/drivers)

3.2. Run `make all` target. Note that linux driver module compilation requires an already built linux kernel:
```bash
cd ./path/to/iob-linux/software/drivers
make all
```

3.3. Add module files to buildroot:
```bash
# copy module files to directory used to build rootfs
cp ./path/to/iob-linux/software/drivers $OS_SOFTWARE_DIR
# change directory to iob-linux
cd ./path/to/iob-linux
# re-build buildroot
make build-buildroot OS_SOFTWARE_DIR=$OS_SOFTWARE_DIR
```

4. Load module in linux. After booting into linux, use the `insmod` command to load the new driver modules:
```bash
# inside linux console
insmod ./path/to/driver.ko
# iob-spi example:
insmod /drivers/iob_spi_master.ko
```

0 comments on commit dc82989

Please sign in to comment.