Is anything worthwhile easy? Among things that do not already exist, and haven't been reinvented time and time again?
I prefer to use 'straightforward', because when you know how the driver needs to operate, writing the code itself using the existing kernel guidelines is
laborious but not
difficult: it is a lot of work, but all the needed information and tools are available online for free. It's like digging a ditch: the first thing to do is to ensure it is dug where it performs the desired purpose, and does not accidentally move a nearby lake a couple of miles in the wrong direction.
The difficult part about drivers is to find out exactly what the driver has to do to be useful, and what it needs to do to perform the desired task or tasks. Stuff like locking, memory accounting, workers and workqueues, and so on. This is both difficult (because of its complexity), taking a long time, but also requires experience, or you'll get bogged down in all the myriad traps for new players. The
design part is the hard part, not the
coding part.
In DiTBho's case, probing order and hardware details, and having to maintain/backport/update/upgrade a driver someone else designed, I bet is a major source of pain. Not Linux per se, unless you count in the "why doesn't this already exist in this form for this bloody arch?" sense.
As SiliconWizard mentioned, microkernel drivers tend to be easier to design because the privilege separation also forces one to use simpler interfaces between drivers and subsystems, so the design work is more structured. In monolithic kernels, existing interfaces can have all sorts of inefficiencies and idiosyncracies. In the case of the Linux kernel, those interfaces are constantly evolving; no stable kernel-internal interfaces exist at all. The fewer subsystems a Linux driver interacts with, the easier it is to write; and vice versa. PCI/PCIe + bus management + locking + memory can get quite hairy to properly design, and if you are "forced" to continue someone elses design you aren't even sure is sane, would give me the heebie-jeebies. Add to more than one concurrent userspace user for that driver, and I start getting hives: designing something robust with that kind of complexity takes a lot of effort.
(I've long since learned to treat code rewrites as design-wide refactoring, where you use the old stuff to see what works, make that central and stable, and try to make it possible to try something better for the stuff the old stuff didn't do right. Then again, even with a high output rate, that is not "a commercially viable use of development resources", because users are nowadays used (pun intended) to less than reliable tools; nobody is willing to pay for such work to be done.)
For USB devices, the usbfs
/dev/bus/usb/jjj/kkk device interface is rather nice for userspace drivers. It is no longer a separate filesystem in current kernels, and is included in the USB core, so its name is misleading; but it does support even async bulk and isochronous transfers and so on.
For anyone wishing/having to/considering doing Linux driver development, I do warmly recommend looking through the
Linux Kernel Driver Implementer's API Guide, it being the documentation kernel developers themselves try to keep up to date. Just getting an intuitive picture of how different drivers and hardware architectures work in Linux, will help with the difficult and hard driver design work.