ioctl tends to go hand-in-hand with a
/dev entry; your typical code would do
This is perfectly standard Unix behaviour. Inside the kernel driver you can put access controls (eg only
root can do some things, or require a specific capability for more fine grained access) which makes it pretty flexible and powerful.
Of course this means that devices can expose a lot more than use block/character read-write activity; many things can be done via
ioctl calls. Not so easy to use from shell scripts, but pretty easy from
python or similar.
sysfs entries are another way of interacting with drivers. Typically each type of command would have a different entry, so it can be complicated to write the driver but it makes it very easy to access via userspace; simple shell scripts can manipulate lots of stuff, but may not be very efficient
netlink is primarily focused (I think!) on network data transfers, but it could be used for other stuff. It's really good for larger volumes of data transfer and is meant to be a successor to
ioctl in some cases.
All the options are good; your use case may better determine which type of interface to expose from your driver.