aos/doc/015-disk-driver-arch/usage.tex
Daniel Schwyn 6d444bf552 Main handout
Signed-off-by: Daniel Schwyn <daniel.schwyn@inf.ethz.ch>
2022-03-03 14:57:51 +01:00

168 lines
5.3 KiB
TeX

This lab project contains a new testcase \verb+ata_rw28_test+ to test the
Flounder-generated interface for \acs{ata} in LBA28 addressing mode. This
chapter walks through its code to demonstrate the steps needed to access disks
using the Flounder backend.
The application first initializes the necessary bindings and \acs{rpc} client.
It then uses the \acs{rpc} wrapper around the Flounder-based ATA interface
geared towards LBA28 addressing mode. The test itself is performed by writing
\lstinline+0xdeadbeef+ in multiple 512 byte blocks and verifying that the data
is actually written to disk by reading it back and checking the contents. The
test concludes with releasing the port.
\section{Datastructures}
To be able to perform \acs{rpc} calls to read from or write to the disk, an
\lstinline+ahci_binding+ as well as an \lstinline+ahci_ata_rw28_binding+ and an
\lstinline+ata_rw28_rpc_client+ are necessary. \lstinline+ata_rw28_test+
defines these as global variables out of convenience:
\begin{lstlisting}
struct ahci_ata_rw28_binding ahci_ata_rw28_binding;
struct ata_rw28_rpc_client ata_rw28_rpc;
struct ata_rw28_binding *ata_rw28_binding = NULL;
struct ahci_binding *ahci_binding = NULL;
\end{lstlisting}
The required header files are:
\begin{lstlisting}
#include <barrelfish/barrelfish.h>
#include <barrelfish/waitset.h>
#include <if/ata_rw28_defs.h>
#include <if/ata_rw28_ahci_defs.h>
#include <if/ata_rw28_rpcclient_defs.h>
\end{lstlisting}
\section{Initialization}
First, we need to initialize the \acs{dma} pool which is used to manage frames
that are mapped uncached and are therefore suitable for \acs{dma} transfers. We
initialize the pool to be 1MB in size:
\begin{lstlisting}
ahci_dma_pool_init(1024*1024);
\end{lstlisting}
Next, we need to initialize \verb+libahci+ and specify which \ac{ahci} port we
want to use. For simplicity, we use port $0$ which is the first device
detected. To achieve blocking behaviour, we enter a spinloop and wait for the
callback from \verb+ahcid+:
\begin{lstlisting}
err = ahci_init(0, ahci_bind_cb, NULL, get_default_waitset());
if (err_is_fail(err) ||
err_is_fail(err=wait_bind((void**)&ahci_binding))) {
USER_PANIC_ERR(err, "ahci_init");
}
\end{lstlisting}
The callback \lstinline+ahci_bind_cb+ simply sets the global
\lstinline+ahci_binding+ and \lstinline+wait_bind+ waits for this global to be
set:
\begin{lstlisting}
static void ahci_bind_cb(void *st,
errval_t err, struct ahci_binding *_binding)
{
bind_err = err;
if (err_is_ok(err)) {
ahci_binding = _binding;
}
}
static errval_t wait_bind(void **bind_p)
{
while (!*bind_p && err_is_ok(bind_err)) {
messages_wait_and_handle_next();
}
return bind_err;
}
\end{lstlisting}
The \acs{rpc} client can be constructed by first initializing the
\lstinline+ata_rw28+ binding and then building an \acs{rpc} client on top of
it. The pointer to the binding is stored for convenience as it is used
frequently:
\begin{lstlisting}
err = ahci_ata_rw28_init(&ahci_ata_rw28_binding, get_default_waitset(),
ahci_binding);
if (err_is_fail(err)) {
USER_PANIC_ERR(err, "ahci_ata_rw28_init");
}
ata_rw28_binding = (struct ata_rw28_binding*)&ahci_ata_rw28_binding;
err = ata_rw28_rpc_client_init(&ata_rw28_rpc, ata_rw28_binding);
if (err_is_fail(err)) {
USER_PANIC_ERR(err, "ata_rw28_rpc_client_init");
}
\end{lstlisting}
\acs{rpc} calls can now be made to perform operations on the disk.
\section{Data Manipulation}
\lstinline+write_and_check_32+ is the function used to write
\lstinline+0xdeadbeef+ to the disk and verify that writing succeeded. It
accepts arbitrary 32 bit patterns that are written to disk. First off, we need
to calculate some values, allocate a buffer and fill this buffer with the
pattern:
\begin{lstlisting}
static void write_and_check_32(uint32_t pat, size_t start_lba,
size_t block_size, size_t block_count)
{
errval_t err;
size_t bytes = block_size*block_count;
uint8_t *buf = malloc(bytes);
assert(buf);
size_t step = sizeof(pat);
size_t count = bytes / step;
assert(bytes % sizeof(pat) == 0);
for (size_t i = 0; i < count; ++i)
*(uint32_t*)(buf+i*step) = pat;
\end{lstlisting}
The actual writing is very simple. We issue the \lstinline+write_dma+ \acs{rpc}
call, pass it the binding, the buffer, the number of bytes to write, the
\ac{lba} on the disk where we want to write to and do some basic error
handling:
\begin{lstlisting}
printf("writing data\n");
errval_t status;
err = ata_rw28_rpc.vtbl.write_dma(&ata_rw28_rpc, buf, bytes,
start_lba, &status);
if (err_is_fail(err))
USER_PANIC_ERR(err, "write_dma rpc");
if (err_is_fail(status))
USER_PANIC_ERR(status, "write_dma status");
\end{lstlisting}
Reading data is equally simple:
\begin{lstlisting}
size_t bytes_read;
err = ata_rw28_rpc.vtbl.read_dma(&ata_rw28_rpc, bytes,
start_lba, &buf, &bytes_read);
if (err_is_fail(err))
USER_PANIC_ERR(err, "read_dma rpc");
if (!buf)
USER_PANIC("read_dma -> !buf");
if (bytes_read != bytes)
USER_PANIC("read_dma -> bytes_read != bytes");
\end{lstlisting}
At the end, we do a simple verification and free the allocated buffer.
\section{Cleanup}
To return ownership of the port and clean up resources, a simple call to
\lstinline+ahci_close+ suffices:
\begin{lstlisting}
ahci_close(ahci_binding, NOP_CONT);
\end{lstlisting}