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 #include #include #include #include \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}