[FE training-materials-updates] kernel: remove serial framework lab

Michael Opdenacker michael.opdenacker at free-electrons.com
Wed Oct 2 15:14:56 CEST 2013


Repository : git://git.free-electrons.com/training-materials.git

On branch  : kernel-ng
Link       : http://git.free-electrons.com/training-materials/commit/?id=d29a7bf72ab41bad31efbd7300a39489c327949d

>---------------------------------------------------------------

commit d29a7bf72ab41bad31efbd7300a39489c327949d
Author: Michael Opdenacker <michael.opdenacker at free-electrons.com>
Date:   Wed Oct 2 15:14:03 2013 +0200

    kernel: remove serial framework lab
    
    - No longer present in the agenda
    
    Signed-off-by: Michael Opdenacker <michael.opdenacker at free-electrons.com>


>---------------------------------------------------------------

d29a7bf72ab41bad31efbd7300a39489c327949d
 Makefile                                           |    1 -
 labs/kernel-serial-driver/kernel-serial-driver.tex |  409 --------------------
 2 files changed, 410 deletions(-)

diff --git a/Makefile b/Makefile
index ac0175f..2983cf0 100644
--- a/Makefile
+++ b/Makefile
@@ -231,7 +231,6 @@ KERNEL_LABS   = setup \
 		kernel-serial-interrupt \
 		kernel-locking \
 		kernel-debugging \
-		kernel-serial-driver \
 		kernel-git \
 		backup
 
diff --git a/labs/kernel-serial-driver/kernel-serial-driver.tex b/labs/kernel-serial-driver/kernel-serial-driver.tex
deleted file mode 100644
index 934c3df..0000000
--- a/labs/kernel-serial-driver/kernel-serial-driver.tex
+++ /dev/null
@@ -1,409 +0,0 @@
-\subchapter{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/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}.
-
-For this lab, we advise you to work with Linux 3.0, as this lab and the
-solution that we will provide have been tested with this version.
-
-Therefore, you should download the Linux 3.0 sources.
-
-\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 \emph{init} and \emph{exit} 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 a \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 \code{Documentation/devices.txt} in the kernel source tree)
-\item \code{nr}, \code{1} (we will support only one port)
-\end{itemize}
-
-In the \emph{init} function, register the UART driver with
-\code{uart_register_driver()} and in the \emph{exit} 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 (for the 
-moment, you can just define these functions as returning a zero value)
-The driver name must be \code{atmel_usart} to match the device definitions in
-\code{arch/arm/mach-at91/}.
-
-You should mark the \code{probe()} function with \code{__devinit} and
-the \code{remove()}
-function with \code{__devexit}. The \code{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 \code{NULL}.
-
-Then, in the \emph{init} and \emph{exit} 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 the board code a little. 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 information 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 \code{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 \code{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 \code{port} structure from the \code{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 \code{getty} process that the
-\code{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
-\code{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
-  \code{data} is the device-specific platform data structure 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
-\code{port->membase}). If the \code{ATMEL_US_TXEMPTY} bit is set, it means that the
-port is ready to transmit, therefore return \code{TIOCSER_TEMT}, otherwise
-return \code{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
-  \code{~0UL}\footnote{That's \code{0xffffffff} on a 32 bit CPU}
-  to the \code{ATMEL_US_IDR} register.
-
-\item Register the \code{port->irq} IRQ channel with a
-  \code{fedrv_interrupt()} interrupt handler.
-  Pass \code{port} as the \code{dev_id} so that we
-  get a pointer to \code{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 \code{~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 \code{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 \code{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}



More information about the training-materials-updates mailing list