STRUCT TTY_OPERATIONS(9)

๐Ÿ“ฆ version 7.3.1, in nixpkgs unstable

okay so bestie, welcome to struct tty_operations(9) from the Kernel API Manual โ€” this is literally the interface between your driver and tty, and it's giving main character energy for the whole subsystem ๐Ÿ’…โœจ


โœจ NAME โœจ

struct tty_operations โ€” interface between driver and tty ๐ŸŽ€


๐ŸŒธ SYNOPSIS ๐ŸŒธ

ok so here's the whole struct, no cap, every single member, don't skip a line:

struct tty_operations {
    struct tty_struct * (*lookup)(struct tty_driver *driver, struct file
*filp, int idx);
    int (*install)(struct tty_driver *driver, struct tty_struct *tty);
    void (*remove)(struct tty_driver *driver, struct tty_struct *tty);
    int (*open)(struct tty_struct * tty, struct file * filp);
    void (*close)(struct tty_struct * tty, struct file * filp);
    void (*shutdown)(struct tty_struct *tty);
    void (*cleanup)(struct tty_struct *tty);
    ssize_t (*write)(struct tty_struct *tty, const u8 *buf, size_t
count);
    int (*put_char)(struct tty_struct *tty, u8 ch);
    void (*flush_chars)(struct tty_struct *tty);
    unsigned int (*write_room)(struct tty_struct *tty);
    unsigned int (*chars_in_buffer)(struct tty_struct *tty);
    int (*ioctl)(struct tty_struct *tty, unsigned int cmd, unsigned long
arg);
    long (*compat_ioctl)(struct tty_struct *tty, unsigned int cmd,
unsigned long arg);
    void (*set_termios)(struct tty_struct *tty, const struct ktermios
*old);
    void (*throttle)(struct tty_struct * tty);
    void (*unthrottle)(struct tty_struct * tty);
    void (*stop)(struct tty_struct *tty);
    void (*start)(struct tty_struct *tty);
    void (*hangup)(struct tty_struct *tty);
    int (*break_ctl)(struct tty_struct *tty, int state);
    void (*flush_buffer)(struct tty_struct *tty);
    int (*ldisc_ok)(struct tty_struct *tty, int ldisc);
    void (*set_ldisc)(struct tty_struct *tty);
    void (*wait_until_sent)(struct tty_struct *tty, int timeout);
    void (*send_xchar)(struct tty_struct *tty, u8 ch);
    int (*tiocmget)(struct tty_struct *tty);
    int (*tiocmset)(struct tty_struct *tty, unsigned int set, unsigned
int clear);
    int (*resize)(struct tty_struct *tty, struct winsize *ws);
    int (*get_icount)(struct tty_struct *tty, struct
serial_icounter_struct *icount);
    int (*get_serial)(struct tty_struct *tty, struct serial_struct *p);
    int (*set_serial)(struct tty_struct *tty, struct serial_struct *p);
    void (*show_fdinfo)(struct tty_struct *tty, struct seq_file *m);
  #ifdef CONFIG_CONSOLE_POLL;
    int (*poll_init)(struct tty_driver *driver, int line, char
*options);
    int (*poll_get_char)(struct tty_driver *driver, int line);
    void (*poll_put_char)(struct tty_driver *driver, int line, char ch);
  #endif;
    int (*proc_show)(struct seq_file *m, void *driver);
 };
heads up ๐Ÿซถ those last few poll_* ones are wrapped up inside #ifdef CONFIG_CONSOLE_POLL; โ€ฆ #endif; โ€” so they only exist when that config is on, iykyk.

๐Ÿ‘‘ Members ๐Ÿ‘‘

okay so this page is very much a "here's the roster, that's the whole page" situation โ€” every member, one signature at a time, zero filler. Let's go down the list, nobody gets skipped ๐ŸŒˆ

  • lookup โ€” struct tty_struct *()(struct tty_driver *self, struct file *, int idx)
  • install โ€” int ()(struct tty_driver *self, struct tty_struct *tty)
  • remove โ€” void ()(struct tty_driver *self, struct tty_struct *tty)
  • open โ€” int ()(struct tty_struct *tty, struct file *)
  • close โ€” void ()(struct tty_struct *tty, struct file *)
  • shutdown โ€” void ()(struct tty_struct *tty)
  • cleanup โ€” void ()(struct tty_struct *tty)
  • write โ€” ssize_t ()(struct tty_struct *tty, const u8 *buf, size_t count)
  • put_char โ€” int ()(struct tty_struct *tty, u8 ch)
  • flush_chars โ€” void ()(struct tty_struct *tty)
  • write_room โ€” unsigned int ()(struct tty_struct *tty)
  • chars_in_buffer โ€” unsigned int ()(struct tty_struct *tty)
  • ioctl โ€” int ()(struct tty_struct *tty, unsigned int cmd, unsigned long arg)
  • compat_ioctl โ€” long ()(struct tty_struct *tty, unsigned int cmd, unsigned long arg)
  • set_termios โ€” void ()(struct tty_struct *tty, const struct ktermios *old)
  • throttle โ€” void ()(struct tty_struct *tty)
  • unthrottle โ€” void ()(struct tty_struct *tty)
  • stop โ€” void ()(struct tty_struct *tty)
  • start โ€” void ()(struct tty_struct *tty)
  • hangup โ€” void ()(struct tty_struct *tty)
  • break_ctl โ€” int ()(struct tty_struct *tty, int state)
  • flush_buffer โ€” void ()(struct tty_struct *tty)
  • ldisc_ok โ€” int ()(struct tty_struct *tty, int ldisc)
  • set_ldisc โ€” void ()(struct tty_struct *tty)
  • wait_until_sent โ€” void ()(struct tty_struct *tty, int timeout)
  • send_xchar โ€” void ()(struct tty_struct *tty, u8 ch)
  • tiocmget โ€” int ()(struct tty_struct *tty)
  • tiocmset โ€” int ()(struct tty_struct *tty, unsigned int set, unsigned int clear)
  • resize โ€” int ()(struct tty_struct *tty, struct winsize *ws)
  • get_icount โ€” int ()(struct tty_struct *tty, struct serial_icounter *icount)
  • get_serial โ€” int ()(struct tty_struct *tty, struct serial_struct *p)
  • set_serial โ€” int ()(struct tty_struct *tty, struct serial_struct *p)
  • show_fdinfo โ€” void ()(struct tty_struct *tty, struct seq_file *m)
  • poll_init โ€” int ()(struct tty_driver *driver, int line, char *options)
  • poll_get_char โ€” int ()(struct tty_driver *driver, int line)
  • poll_put_char โ€” void ()(struct tty_driver *driver, int line, char ch)
  • proc_show โ€” int ()(struct seq_file *m, void *driver)

that's the entire member roster, all 37 of them, ate and left no crumbs ๐Ÿ”ฅ๐Ÿ’œ

๐Ÿ’… DESCRIPTION ๐Ÿ’…

okay so this is the part where we find out what all those members actually do, and bestie we're going in the exact same order as the Members list, so keep a finger on it ๐Ÿ‘†๐Ÿซถ

one tiny heads-up before we dive: unless something says it's mandatory, assume it's optional and can literally just be NULL. I'll yell about the mandatory ones, don't worry ๐Ÿ”ฅ

  1. hand it a device or nothing โ€” returns the tty device corresponding to idx, NULL if there isn't one currently in use, and an ERR_PTR value on error ๐Ÿ˜ญ. It's called under tty_mutex (for now! ๐Ÿ™ˆ). Optional method, and the default behaviour is to use the self->ttys array.
  2. the installer โ€” installs a new tty into the self's internal tables. Works as a trio with the lookup and remove methods ๐Ÿค. Optional, default behaviour is the self->ttys array.
  3. the evictor โ€” removes a closed tty from the self's internal tables. Used in conjunction with the lookup and remove methods. Optional, default behaviour is the self->ttys array.
  4. open, the mandatory one โ€” this routine is called when a particular tty device is opened โœจ. This routine is mandatory; if it's not filled in, the attempted open will fail with ENODEV ๐Ÿ’€. Required method. Called with tty lock held. May sleep.
  5. close, also mandatory โ€” called when a particular tty device is closed. At the point of return from this call the driver must make no further ldisc calls of any kind ๐Ÿšซ. Remark: it's called even if the corresponding open failed ๐Ÿ‘€. Required method. Called with tty lock held. May sleep.
  6. shutdown โ€” called under the tty lock when a particular tty device is closed for the last time. It executes before the tty resources are freed, so it may execute while another function holds a tty kref ๐Ÿงš
  7. cleanup โ€” called asynchronously when a particular tty device is closed for the last time, freeing up the resources. It's actually the second part of shutdown, for routines that might sleep ๐Ÿ˜ด
  8. write โ€” the kernel calls this to write a series (count) of characters (buf) to the tty device. The characters may come from user space or kernel space. It returns the number of characters actually accepted for writing ๐Ÿ”ฅ. May occur in parallel in special cases โ€” and because this includes panic paths, drivers generally shouldn't try and do clever locking here ๐Ÿ™ˆ. Optional: Required for writable devices. May not sleep.
  9. put_char โ€” the kernel calls this to write a single character ch to the tty device. If the kernel uses this routine, it must call the flush_chars routine (if defined) when it's done stuffing characters into the driver. If there's no room in the queue, the character is ignored ๐Ÿซฅ. Optional: the kernel will use the write method if this isn't provided. Do not call this function directly โ€” call tty_put_char.
  10. flush_chars โ€” called by the kernel after it has written a series of characters to the tty device using put_char ๐ŸŒŠ. Optional. Do not call this function directly โ€” call tty_driver_flush_chars.
  11. write_room โ€” returns the numbers of characters the tty driver will accept for queuing to be written ๐Ÿ“ฅ. This number is subject to change as output buffers get emptied, or if the output flow control is acted. The ldisc is responsible for being intelligent about multi-threading of write_room/write calls. Required if the write method is provided, else not needed. Do not call this function directly โ€” call tty_write_room.
  12. chars_in_buffer โ€” returns the number of characters in the device private output queue ๐Ÿ“Š. Used in tty_wait_until_sent and for the poll implementation. Optional: if not provided, it's assumed there's no queue on the device. Do not call this function directly โ€” call tty_chars_in_buffer.
  13. ioctl โ€” lets the tty driver implement device-specific ioctls ๐ŸŽ›๏ธ. If the ioctl number passed in cmd isn't recognized by the driver, it should return ENOIOCTLCMD. Optional.
  14. compat_ioctl โ€” implement ioctl processing for a 32 bit process on a 64 bit system ๐Ÿง . Optional.
  15. set_termios โ€” lets the tty driver be notified when the device's termios settings have changed. New settings are in tty->termios. Previous settings are passed in the old argument ๐Ÿ•ฐ๏ธ. The API is defined such that the driver should return the actual modes selected โ€” meaning the driver is responsible for modifying any bits in tty->termios it cannot fulfill, to indicate the actual modes being used. Optional. Called under the tty->termios_rwsem. May sleep.
  16. the ldisc vibe check โ€” lets the tty driver decide if it can deal with a particular ldisc ๐Ÿค”. Optional. Called under the tty->ldisc_sem and tty->termios_rwsem.
  17. ldisc switch notification โ€” lets the tty driver be notified when the device's line discipline is being changed. At the point this is done, the discipline is not yet usable โš ๏ธ. Optional. Called under the tty->ldisc_sem and tty->termios_rwsem.
  18. throttle โ€” notifies the tty driver that input buffers for the line discipline are close to full, and it should somehow signal that no more characters should be sent to the tty ๐Ÿ›‘. Serialization including with unthrottle is the job of the ldisc layer. Optional: always invoke via tty_throttle_safe. Called under the tty->termios_rwsem.
  19. unthrottle โ€” notifies the tty driver that it should signal that characters can now be sent to the tty without fear of overrunning the input buffers of the line disciplines ๐ŸŒŠโœจ. Optional. Always invoke via tty_unthrottle. Called under the tty->termios_rwsem.
  20. stop โ€” notifies the tty driver that it should stop outputting characters to the tty device โœ‹. Called with tty->flow.lock held. Serialized with the start method. Optional. Always invoke via stop_tty.
  21. start โ€” notifies the tty driver that it resumed sending characters to the tty device โ–ถ๏ธ. Called with tty->flow.lock held. Serialized with the stop method. Optional. Always invoke via start_tty.
  22. hangup โ€” notifies the tty driver that it should hang up the tty device ๐Ÿ“ด. Optional. Called with tty lock held.
  23. break_ctl โ€” this optional routine requests the tty driver to turn on or off BREAK status on the RS-232 port ๐Ÿ’ฅ. If state is -1, then the BREAK status should be turned on; if state is 0, then BREAK should be turned off. If this routine is implemented, the high-level tty driver will handle the following ioctls: TCSBRK, TCSBRKP, TIOCSBRK, TIOCCBRK. If the driver sets TTY_DRIVER_HARDWARE_BREAK in tty_alloc_driver, then the interface will also be called with actual times and the hardware is expected to do the delay work itself โฑ๏ธ. 0 and -1 are still used for on/off. Optional: Required for TCSBRK/BRKP/etc. handling. May sleep.
  24. flush_buffer โ€” discards the device private output buffer ๐Ÿ—‘๏ธ. Invoked on close, hangup, to implement the TCOFLUSH ioctl and similar. Optional: if not provided, it's assumed there's no queue on the device. Do not call this function directly โ€” call tty_driver_flush_buffer.
  25. wait_until_sent โ€” waits until the device has written out all of the characters in its transmitter FIFO, or until timeout (in jiffies) is reached โณ. Optional: if not provided, the device is assumed to have no FIFO. Usually correct to invoke via tty_wait_until_sent. May sleep.
  26. send_xchar โ€” used to send a high-priority XON/XOFF character (ch) to the tty device โšก. Optional: if not provided, then the write method is called under the tty->atomic_write_lock to keep it serialized with the ldisc.
  27. tiocmget โ€” used to obtain the modem status bits from the tty driver ๐Ÿ“ก. Optional: if not provided, then ENOTTY is returned from the TIOCMGET ioctl. Do not call this function directly โ€” call tty_tiocmget.
  28. tiocmset โ€” used to set the modem status bits to the tty driver ๐Ÿ“ถ. First, clear bits should be cleared, then set bits set. Optional: if not provided, then ENOTTY is returned from the TIOCMSET ioctl. Do not call this function directly โ€” call tty_tiocmset.
  29. resize โ€” called when a termios request is issued which changes the requested terminal geometry to ws ๐Ÿ“. Optional: the default action is to update the termios structure without error. This is usually the correct behaviour. Drivers should not force errors here if they are not resizable objects (e.g. a serial line) ๐Ÿ™…. See tty_do_resize if you need to wrap the standard method in your own logic โ€” the usual case.
  30. get_icount โ€” called when the tty device receives a TIOCGICOUNT ioctl. Passed a kernel structure icount to complete ๐Ÿงฎ. Optional: called only if provided, otherwise ENOTTY will be returned.
  31. get_serial โ€” called when the tty device receives a TIOCGSERIAL ioctl. Passed a kernel structure p (struct serial_struct) to complete ๐Ÿ”Œ. Optional: called only if provided, otherwise ENOTTY will be returned. Do not call this function directly โ€” call tty_tiocgserial.
  32. set_serial โ€” called when the tty device receives a TIOCSSERIAL ioctl. Passed a kernel structure p (struct serial_struct) to set the values from ๐Ÿ”ง. Optional: called only if provided, otherwise ENOTTY will be returned. Do not call this function directly โ€” call tty_tiocsserial.
  33. show_fdinfo โ€” called when the tty device file descriptor receives a fdinfo request from VFS (to show in /proc/<pid>/fdinfo/) ๐Ÿ“„. m should be filled with information. Optional: called only if provided, otherwise nothing is written to m. Do not call this function directly โ€” call tty_show_fdinfo.
  34. poll_init โ€” kgdboc support (Documentation/process/debugging/kgdb.rst) ๐Ÿ›. This routine is called to initialize the HW for later use by calling poll_get_char or poll_put_char. Optional: called only if provided, otherwise skipped as a non-polling driver.
  35. poll_get_char โ€” kgdboc support (see poll_init). The driver should read a character from a tty identified by line and return it ๐Ÿ”Ž. Optional: called only if poll_init provided.
  36. poll_put_char โ€” kgdboc support (see poll_init). The driver should write character ch to a tty identified by line โœ๏ธ. Optional: called only if poll_init provided.
  37. proc_show โ€” Driver driver (cast to struct tty_driver) can show additional info in /proc/tty/driver/<driver_name> ๐Ÿ“. It is enough to fill in the information into m. Optional: called only if provided, otherwise no /proc entry created.
  38. the closing statement ๐Ÿซถ โ€” this structure defines the interface between the low-level tty driver and the tty routines. These routines can be defined. Unless noted otherwise, they are optional, and can be filled in with a NULL pointer.

๐ŸŒˆ SEE ALSO ๐ŸŒˆ

  • Kernel file ./include/linux/tty_driver.h โ€” enum tty_driver_flag(9), struct tty_driver(9), tty_alloc_driver(9) ๐Ÿ‘‘

and that's the whole thing, bestie, you made it ๐ŸŽ€โœจ go forth and fill in your function pointers responsibly ๐Ÿ’œ

sign-off szn: this gem lives in ./include/linux, it's dated October 2026, and it's the page for struct tty_operations(9) โ€” slay, go read the headers ๐Ÿ’…๐Ÿ‘‘

๐Ÿ“ฆ where this page comes from