InnocentZero's Treasure Chest

HomeFeedAbout Me

11 Nov 2024

UGRC hints

TL;DR: Debugging pro max

As of now printing everything using printk functions.

From drivers/base/init.c we call the devices_init function Defined in drivers/base/core.c at line 2895 and calls kset_create_and_add that adds a kset to the sysfs (basically a collection of kobjects, that represents the base type for any driver) The above mentioned call happens for a static const struct kset_uevent_ops which basically controls how the kset reacts to udev events. After that we add block and char to the freestanding dev kobject.

After this I'm a little bit clueless on what happens. Like sure you init the devices but now what?

Then in drivers/tty/serial/8250/8250_core.c somehow:

  • We call serial8250_init on line 1163.

    There is an isa_init_ports functions but that's already executed once and is meant to execute once so it doesn't matter. The first execution happens for the console (ig).

    We pick up a static struct uart_driver serial8250_reg defined at line 56 in the same file. Probably meant for registering shit.

    The struct itself is defined in include/linux/serial_core.h and has the following below (line 303)

struct uart_driver {
        struct module           *owner;
        const char              *driver_name;
        const char              *dev_name;
        int                      major;
        int                      minor;
        int                      nr;
        struct console          *cons;

        /*
         * these are private; the low level driver should not
         * touch these; they should be initialised to NULL
         */
        struct uart_state       *state;
        struct tty_driver       *tty_driver;
};
  • It has a lot of module related stuff, basically a pointer to struct module in include/linux/module.h:350

Also has a driver name, device name, major/minor count and a count Most importantly has a pointer to struct console * which has a couple of interesting functions (include/linux/console.h:145)

struct console {
        char    name[16];
        void    (*write)(struct console *, const char *, unsigned);
        int     (*read)(struct console *, char *, unsigned);
        struct tty_driver *(*device)(struct console *, int *);
        void    (*unblank)(void);
        int     (*setup)(struct console *, char *);
        int     (*match)(struct console *, char *name, int idx, char *options);
        short   flags;
        short   index;
        int     cflag;
        void    *data;
        struct   console *next;
};

This static struct is picked up by a function call to uart_register_driver() that is in drivers/tty/serial/serial_core.c:2528

The register driver function does a lot of its own shit. Allocates space for all the ports each of size uartstate thereby maintaining an array with each state stored.

The state stores a lot of stuff, and is defined in include/linux/serial_core.h:283.

Most importantly it stores the circular buffer that we need to look at. Also has a uart_port struct inside uartstate that has read and write functions to the serial thing. This is in the same file, line 108.

Subsequently, it gets a tty_driver pointer with alloc_tty_driver function present in include/linux/tty_driver.h:355

The tty_driver itself is described in include/linux/tty_driver.h and is:

struct tty_driver {
        int     magic;          /* magic number for this structure */
        struct kref kref;       /* Reference management */
        struct cdev **cdevs;
        struct module   *owner;
        const char      *driver_name;
        const char      *name;
        int     name_base;      /* offset of printed name */
        int     major;          /* major device number */
        int     minor_start;    /* start of minor device number */
        unsigned int    num;    /* number of devices allocated */
        short   type;           /* type of tty driver */
        short   subtype;        /* subtype of tty driver */
        struct ktermios init_termios; /* Initial termios */
        unsigned long   flags;          /* tty driver flags */
        struct proc_dir_entry *proc_entry; /* /proc fs entry */
        struct tty_driver *other; /* only used for the PTY driver */

        /*
         * Pointer to the tty data structures
         */
        struct tty_struct **ttys;
        struct tty_port **ports;
        struct ktermios **termios;
        void *driver_state;

        /*
         * Driver methods
         */

        const struct tty_operations *ops;
        struct list_head tty_drivers;
}

We then do the following changes to the generated tty

drv->tty_driver = normal;

normal->driver_name     = drv->driver_name;
normal->name            = drv->dev_name;
normal->major           = drv->major;
normal->minor_start     = drv->minor;
normal->type            = TTY_DRIVER_TYPE_SERIAL;
normal->subtype         = SERIAL_TYPE_NORMAL;
normal->init_termios    = tty_std_termios;
normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600;
normal->flags           = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
normal->driver_state    = drv;

It sets the operations to uart_ops options specified at line 2472 in the same file. It inits all the tty ports of the uart_state array that we kcalloc'd earlier. This is done using tty_port_init in drivers/tty/tty_port.c:60

tty_port itself is defined in include/linux/tty.h

It also registers the tty_driver we allocated earlier.

After the uart_register_driver is found, we call serial8250_pnp_init which is defined in drivers/tty/serial/8250/8250_pnp.c:533 that itself calls pnp_register_driver from drivers/pnp/driver.c at line 267

The pnp driver inited is a static struct right above serial8250_pnp_init whose type definition is in include/linux/pnp.h:381

That itself has a struct device_driver that is just a common type for all device drivers.

Doesn't do much but basically add the driver somewhere in the grand list of all drivers.

After that, we do some platform_device_alloc and platform_device_add which do something I don't bother enough to check.

After this, we do a serial8250_register_ports that registers the uart ports. Defined in drivers/tty/serial/8250/8250_core.c:576

That function in turn takes a static uartport struct whose structure is as follows

struct uart_8250_port {
        struct uart_port        port;
        struct timer_list       timer;          /* "no irq" timer */
        struct list_head        list;           /* ports on this IRQ */
        u32                     capabilities;   /* port capabilities */
        unsigned short          bugs;           /* port bugs */
        bool                    fifo_bug;       /* min RX trigger if enabled */
        unsigned int            tx_loadsz;      /* transmit fifo load size */
        unsigned char           acr;
        unsigned char           fcr;
        unsigned char           ier;
        unsigned char           lcr;
        unsigned char           mcr;
        unsigned char           mcr_mask;       /* mask of user bits */
        unsigned char           mcr_force;      /* mask of forced bits */
        unsigned char           cur_iotype;     /* Running I/O type */
        unsigned int            rpm_tx_active;
        unsigned char           canary;         /* non-zero during system sleep
                                                 *   if no_console_suspend
                                                 */
        unsigned char           probe;
        struct mctrl_gpios      *gpios;
#define UART_PROBE_RSA  (1 << 0)

        /*
         * Some bits in registers are cleared on a read, so they must
         * be saved whenever the register is read but the bits will not
         * be immediately processed.
         */
#define LSR_SAVE_FLAGS UART_LSR_BRK_ERROR_BITS
        unsigned char           lsr_saved_flags;
#define MSR_SAVE_FLAGS UART_MSR_ANY_DELTA
        unsigned char           msr_saved_flags;

        struct uart_8250_dma    *dma;
        const struct uart_8250_ops *ops;

        /* 8250 specific callbacks */
        int                     (*dl_read)(struct uart_8250_port *);
        void                    (*dl_write)(struct uart_8250_port *, int);

        struct uart_8250_em485 *em485;

        /* Serial port overrun backoff */
        struct delayed_work overrun_backoff;
        u32 overrun_backoff_time_ms;
};

This struct is also defined in the same file. We basically allot the port's device to the one we passed in the argument.

Go to uart_add_one_port in drivers/tty/serial/serial_core.c:2805 This function is intense. There's a lot of stuff happening here and I don't have much idea of what's happening exactly. This takes the state and the port and links them with each other. Then does some variable defining for both

Now events are handled as mentioned below:

  • user calls open on dev/ttyS0 which eventually reaches the tty subsystem
  • tty subsystem calls open method of the ttyoperations struct and is a function pointer (so specialized at runtime)
  • The exact one is defined at serial_core.c at line 2476 and is the uart_open function in line 1781 in the same file
  • uart_open in turn calls tty_port_open in tty_port.c which in turn calls port->ops->activate which is defined in serial_core.c at line 2512
  • this calls uart_startup() of file serial_core.c
  • This calls uart_port_startup inside of it and that inits the circular buffer if needed
  • This also calls uport->ops->startup which is serial8250_startup
  • Startup: serial8250_startup handles the startup of the ports. In most cases, it'll call serial8250_do_startup which is right above it. This is defined in drivers/tty/serial/8250_port.c:2328
  • serial8250_do_startup will call up->ops->setup_irq which is defined in line 398 of 8250_core.c
  • The value for that is univ8250_setup_irq in line 317 which calls serial_link_irq_chain which is right above it
  • That calls request_irq which registers interrupt request handlers, and that in turn is serial8250_interrupt
  • Interrupt handler: serial8250_interrupt at line 109 in 8250_core.c
  • That defines the interrupt handler for uart and handles all interrupts
  • It calls port->handle_irq for each of the port which is responsible for handling irqs.
  • That is by default set up to be serial8250_default_handle_irq in line 511 of 8250_port.c
  • The function serial8250_default_handle_irq is defined in line 1842 in 8520_port.c
  • Finds the interrupt type using IIR macro (used by the hardware to set the type)
  • After this it passes it to serial_8250_handle_irq which is the final interrupt handler once and for all. After this specialized functions are called.
Tags: programming C++ C

Other posts
Creative Commons License
This website by Md Isfarul Haque is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.