The ioctl() system call is the entry point for user processes to
control the behaviour of device files. Ioctl management is spawned by
../../fs/ioctl.c, where the real sys_ioctl() resides.
The standard ioctl requests are performed right there, other
file-related requests are processed by file_ioctl() (same source
file), while any other request is dispatches to the device-specific
ioctl() function.

The ioctl material for console devices resides in vt.c,
because the console driver dispatches ioctl requests to vt_ioctl().

Ioctl material is quite confused, indeed. Some requests are related to the
device, and some are related to the line discipline. I'll try to summarize
things for the 1.0 and the 1.1.7x kernels. Anything happened in between.

The 1.1.7x series features the following approach: tty_ioctl.c
implements only line discipline requests (namely
n_tty_ioctl(), which is the only n_tty function outside of
n_tty.c), while the file_operations field points to
tty_ioctl() in tty_io.c. If the request number is not
resolved by tty_ioctl(), it is passed along to
tty->driver.ioctl or, if it fails, to
tty->ldisc.ioctl. Driver-related stuff for the console it to be found
in vt.c, while line discipline material is in tty_ioctl.c.

In the 1.0 kernel, tty_ioctl() is in tty_ioctl.c and
is pointed to by generic tty file_operations. Unresolved requests
are passed along to the specific ioctl function or to the line-discipline code,
in a way similar to 1.1.7x.

Note that in both cases, the TIOCLINUX request is in the
device-independent code. This implies that the console selection can
be set by ioctlling any tty (set_selection() always operates
on the foreground console), and this is a security hole. It is also a good
reason to switch to a newer kernel, where the problem is fixed by
only allowing the superuser to handle the selection.

A variety of requests can be issued to the console device, and the
best way to know about them is to browse the source file vt.c.