\subchapter{Kernel – Serial controller device driver
  programming}{Objective: Develop a serial device driver for the
  AT91SAM9263 CPU from scratch}

\section{Warning}

In this lab, we are going to re-implement a driver that already exists
in the Linux kernel tree. Since the driver already exists, you could
just copy the code, compile it, and get it to work in a few
minutes. However, the purpose of this lab is to re-create this driver
from scratch, taking the time to understand all the code and all the
steps. So please play the game, and follow our adventure of creating a
serial driver from scratch!

\section{Setup}

Go to the \code{/home/<user>/felabs/linux/character} directory. It
contains the root filesystem that you will mount over NFS to work on
this lab. Re-use the setup instructions of the lab on {\em Character
Device Drivers} to get a kernel without the serial port driver and
with {\em Network Console support}.

\section{Basic module}

The serial core cannot be compiled inside the kernel without an
in-tree kernel driver. Therefore, for this lab, we will work directly
inside the kernel source tree and not using an external module.

To do so:

\begin{itemize}

\item Create a basic module in \code{drivers/tty/serial/fedrv.c} with
  just the init and cleanup functions ;

\item Add a new configuration option in
  \code{drivers/tty/serial/Kconfig}. Don't forget to select
  \code{SERIAL_CORE} in this option ;

\item Update \code{drivers/tty/serial/Makefile} to make sure your
  driver gets compiled when your new option is selected

\end{itemize}

Compile your new driver as a module, and after the kernel compilation, run:

\begin{verbatim}
make INSTALL_MOD_PATH=/path/to/nfsroot modules_install
\end{verbatim}

To install the modules (\code{serial_core} and your driver) into the
root filesystem. Then try to load/unload your module on the target
using \code{modprobe}. If you're successful, we can now start working
on the driver itself.

\section{Register the UART driver}

Instantiate an \code{uart_driver} structure with the following values:

\begin{itemize}
\item \code{owner}, \code{THIS_MODULE}
\item \code{driver_name}, \code{fedrv} or any other string
\item \code{dev_name}, \code{ttyS}
\item \code{major}, \code{TTY_MAJOR} (this is the usual major for TTY devices)
\item \code{minor}, \code{64} (this is the usual minor for TTY serial
  devices, see Documentation/devices.txt in the kernel source tree)
\item \code{nr}, \code{1} (we will support only one port)
\end{itemize}

In the \code{init} function, register the UART driver with
\code{uart_register_driver()} and in the cleanup function,
unregister it with \code{uart_unregister_driver()}.

\section{Integration in the device model}

To get notifications of the UART devices that exist on our board, we
will integrate our driver in the device model.

To do, so, first instantiate a \code{platform_driver} structure, with
pointers to the \code{probe()} and \code{remove()} methods (they can
be left empty at the moment). The driver name must be
\code{``atmel_usart''} to match the device definitions in
\code{arch/arm/mach-at91/}.

You should mark the probe function with \code{__devinit} and the remove
function with \code{__devexit}. The remove operation should be declared as
follows:

\begin{verbatim}
.remove = __devexit_p(fedrv_remove)
\end{verbatim}

So that if the driver is statically compiled, the
\code{fedrv_remove()} function is not compiled in and the
\code{.remove} pointer is NULL.

Then, in the init and cleanup functions of the module, register and
unregister the platform driver using
\code{platform_driver_register()} and
\code{platform_driver_unregister()}.

Finally, you need to make a small modification to the
kernel. Currently, the \code{atmel_usart} platform devices are only added
if the Atmel serial port driver is compiled in. However, since we
disabled this driver (because we are re-implementing it), we must
modify a little the board code. So, in
\code{arch/arm/mach-at91/at91sam9263_devices.c}, replace:

\begin{verbatim}
#if defined(CONFIG_SERIAL_ATMEL)
\end{verbatim}

by

\begin{verbatim}
#if defined (CONFIG_SERIAL_FEDRV) || defined (CONFIG_SERIAL_FEDRV_MODULE)
\end{verbatim}

(Assuming you named the option of your driver \code{SERIAL_FEDRV} in
\code{drivers/tty/serial/Kconfig}).

Then, recompile your kernel, re-flash it, and test your new
module. You should see your \code{probe()} function being called
(after adding a simple \code{dev_*()} message in it). And in
\code{/sys/devices/platform/}, you should see the device
\code{atmel_usart.0}. This directory contains a symbolic link driver
to the \code{atmel_usart} driver. If you follow this symbolic link,
you should discover that the \code{atmel_usart} driver is implemented
by the \code{fedrv} module. Congratulations!

\section{Registering the port}

Now, it's time to implement the \code{probe()} and \code{remove()}
functions. Before that, we need a few definitions:

\begin{itemize}
\item Declare a global \code{uart_port} structure, that will be used
  to contain the informations about the single port we will manage;
\item Declare an empty \code{uart_ops} structure.
\end{itemize}

Then, in the \code{probe()} operation:

\begin{itemize}

\item Make sure \code{pdev->id} is 0 (we only want to handle the first
  serial port). If it's not zero, bail out with \code{-ENODEV}

\item Initialize the fields of the \code{uart_port} structures

  \begin{itemize}

  \item \code{->ops} should point to the \code{uart_ops} structure

  \item \code{->dev} should point to the \code{struct device} embedded
    in the \code{platform_device} structure. So \code{&pdev->dev}
    should work

  \item \code{->line} should be the serial port number, i.e \code{0} or
    \code{pdev->id}

  \end{itemize}

\item Register the port with \code{uart_add_one_port()}

\item Associate the port pointer to the platform device structure
  using \code{platform_set_drvdata()}. This will make it easy to find
  the port structure from the \code{platform_device} structure in the
  \code{remove()} operation.

\end{itemize}

In the \code{remove()} method:

\begin{itemize}

\item Get the port structure from the platform device structure using
  \code{platform_get_drvdata()}

\item Unregister the port with \code{uart_remove_one_port()}

\end{itemize}

Now, when testing your driver, in
\code{/sys/devices/platform/atmel_usart.0/}, you should have a
\code{tty} directory, which itself contains a \code{ttyS0}
directory. Similarly, if you go in \code{/sys/class/tty/ttyS0}, you
should see that the \code{ttyS0} device is handled by
\code{atmel_usart.0}. Good!

Before going on, we need to disable the getty process that the init
process is trying to start on \code{/dev/ttyS0}. Edit the
\code{/etc/inittab} file of your target root filesystem and comment
the line that starts the \code{getty} program on \code{ttyS0}, and
reboot your platform.

\section{Polled mode transmission}

To keep our driver simple, we will implement a very simple polled-mode
transmission model.

In the \code{probe()} operation, let's define a few more things in the port
structure:

\begin{itemize}

\item \code{->fifosize}, to \code{1} (this is hardware-dependent)

\item \code{->iotype} should be \code{UPIO_MEM} because we are
  accessing the hardware through memory-mapped registers

\item \code{->flags} should be \code{UPF_BOOT_AUTOCONF} so that the
  \code{config_port()} operation gets called to do the configuration

\item \code{->mapbase} should be \code{pdev->resource[0].start}, this is
  the address of the memory-mapped registers

\item \code{->membase} should be set to \code{data->regs}, where data is
  the device-specific platform data associated to the device. In our
  case, it's a \code{atmel_uart_data} structure, available through
  \code{pdev->dev.platform_data}. In the case of the first serial port
  \code{data->regs} is non-zero and contains the virtual address at
  which the registers have been remapped. For the other serial ports,
  we would have to \code{ioremap()} them.

\end{itemize}

Then, we need to create stubs for a fairly large number of
operations. Even if we don't implement anything inside these
operations for the moment, the \code{serial_core} layer requires these
operations to exist:

\begin{itemize}
\item \code{tx_empty()}
\item \code{start_tx()}
\item \code{stop_tx()}
\item \code{stop_rx()}
\item \code{type()}
\item \code{startup()}
\item \code{shutdown()}
\item \code{set_mctrl()}
\item \code{get_mctrl()}
\item \code{enable_ms()}
\item \code{set_termios()}
\item \code{release_port()}
\item \code{request_port()}
\item \code{config_port()}
\end{itemize}

First, let's implement what's related to setting and getting the
serial port type:

\begin{itemize}

\item In the \code{config_port()} operation, if \code{flags & UART_CONFIG_TYPE}
  is true, then set \code{port->type = PORT_ATMEL}. There is a global
  list of serial port types, and we are re-using the existing
  definition.

\item In the \code{type()} operation, if \code{port->type} is \code{PORT_ATMEL} return a
  string like \code{ATMEL_SERIAL}, otherwise return \code{NULL}.

\end{itemize}

Now, for the transmission itself, we will first implement
\code{tx_empty()}. In this function, read the register \code{ATMEL_US_CSR} from
the hardware (note: the virtual base address of the registers is in
port->membase). If bit \code{ATMEL_US_TXEMPTY} is set, it means that the
port is ready to transmit, therefore return \code{TIOCSER_TEMT}, otherwise
return 0.

Then, the \code{start_tx()} function will do the transmission
itself. Iterate until the transmission buffer is empty (use
\code{uart_circ_empty()}) and do:

\begin{itemize}
\item call an auxiliary function that prints one character
\item update the tail pointer of the transmission buffer
\item increment \code{port->icount.tx}
\end{itemize}

The auxiliary function should wait until bit \code{ATMEL_US_TXRDY}
gets set in the \code{ATMEL_US_CSR} register, and then send the
character through the \code{ATMEL_US_THR} register.

Then, compile and load your driver. You should now be able to do:

\begin{verbatim}
echo "foo" > /dev/ttyS0
\end{verbatim}

\section{Implementing reception}

The last part to make our driver usable is to implement reception.

We first need to modify the \code{probe()} method to set
\code{port->irq} to \code{pdev->resource[1].start} so that we fetch
the IRQ number from the board-specific platform device definition.

Then, in the \code{startup()} operation, do the following steps:

\begin{itemize}

\item Disable all interrupts in the serial controller by writing ~0UL
  to the \code{ATMEL_US_IDR} register

\item Register the IRQ channel \code{port->irq} to an interrupt handler
  \code{fedrv_interrupt()}. Pass port as the \code{dev_id} so that we
  get a pointer to the port in the interrupt handler. Make it a shared
  interrupt.

\item Reset the serial controller by writing \code{ATMEL_US_RSTSTA |
  ATMEL_US_RSTRX} to the \code{ATMEL_US_CR} register

\item Enable transmission and reception by writing \code{ATMEL_US_TXEN |
  ATMEL_US_RXEN} to the \code{ATMEL_US_CR} register

\item Enable interrupts on reception by writing \code{ATMEL_US_RXRDY} to
  the \code{ATMEL_US_IER} register

\end{itemize}

Similarly, in the \code{shutdown()} operation, do:

\begin{itemize}

\item Disable all interrupts by writing \verb+~0UL+ to the
  \code{ATMEL_US_IDR} register

\item Free the IRQ channel using \code{free_irq()}

\end{itemize}

Then, in the interrupt handler, do the following:

\begin{itemize}

\item Read the \code{ATMEL_US_CSR} register to get the controller
  status and perform the logical and of this value with the enabled
  interrupts by reading the \code{ATMEL_US_IMR} register. If the
  resulting value is 0, then the interrupt was not for us, return
  \code{IRQ_NONE}.

\item If the result value has bit \code{ATMEL_US_RXRDY} set, call an
  auxiliary function \code{fedrv_rx_chars()} to receive the characters.

\end{itemize}

Finally, we have to implement the \code{fedrv_rx_chars()}
function. This function should read the \code{ATMEL_US_CSR}
register, and while \code{ATMEL_US_RXRDY} is set in this register,
loop to read characters the following way:

\begin{itemize}

\item Read one character from the \code{ATMEL_US_RHR} register

\item Increment \code{port->icount.rx}

\item Call \code{uart_insert_char()} with the value of the status
  register, overrun to \code{ATMEL_US_OVRE}, and the flag set to
  \code{TTY_NORMAL} (we don't handle break characters, frame or
  parity errors, etc. for the moment)

\end{itemize}

Once all characters have been received, we must tell the upper layer
to push these characters, using \code{tty_flip_buffer_push()}.

Now, if you do cat \code{/dev/ttyS0}, you should be able to receive
characters from the serial port. By default, a \code{ttyS} is opened in the
so-called {\em canonical} mode, so the characters are sent to the reading
process only after entering a newline character.

You can also try to run the program that will display the login prompt
and then a shell:

\begin{verbatim}
/sbin/getty -L ttyS0 115200 vt100
\end{verbatim}

Go back to your Picocom, you should be able to login normally, but
using your own serial driver!

\section{Improvements}

Of course, our serial driver needs several improvements:

\begin{itemize}
\item Real implementation of \code{set_termios()} and \code{set_mctrl()}
\item Usage of interrupts for transmission
\item Console support for early messages
\item Support of several serial ports
\item Handle parity and frame errors properly
\item Support break characters and SysRq
\item Use of DMA for transmission and reception
\item etc.
\end{itemize}
