Why We Switched From Classic AUTOSAR CAN Stack to a Custom Driver (And Regretted Half of It)

Not a story about being wrong. A story about being right about one thing and not thinking hard enough about three others.
I want to write this one honestly, because the easy version of this story — either "we made a brilliant optimization" or "we made a terrible mistake we now regret" — would both be more comfortable to write than what actually happened, which is messier and more useful to anyone facing a similar decision.
The Problem That Started It
We were building a domain controller for a project with genuinely unusual CAN throughput requirements — a high message rate across multiple CAN-FD channels, feeding a downstream processing pipeline with tight latency requirements. Profiling our standard AUTOSAR CAN stack (the full layered path: CanIf, PduR, CanTp, down through the CAN driver itself) showed measurable overhead per message — not enormous in absolute terms, but consistently present, and at our target message rate, that per-message overhead added up to a real, non-trivial fraction of our latency budget.
The AUTOSAR layered architecture, doing what it's designed to do — providing hardware abstraction and a stable interface contract between layers — was costing us cycles specifically because of that abstraction: indirection through interface calls, generic handling paths built to accommodate configurations we didn't actually need, PDU router logic doing routing decisions for a topology that, in our specific ECU, was actually fixed and simple.
The proposal was straightforward: write a lean, purpose-built CAN driver specifically for our exact hardware and exact use case, skip the generic AUTOSAR layering for this one specific data path, and get the cycles back.
What We Actually Got Right
I'll say this plainly: the performance win was real, not imagined, and not a rounding error. Our custom driver measurably reduced per-message processing overhead, and in aggregate, at our actual message rate, that translated into a genuine, needed improvement in our end-to-end latency budget — enough to move a borderline requirement from "technically failing under worst-case load" to "comfortably passing with margin." If the only question had been "did this solve the specific performance problem it was built to solve," the honest answer is yes, cleanly.
We were also right that the standard stack's generality was, in our specific case, genuinely unused generality — we weren't using multiple CAN controller configurations, weren't using the router's more flexible PDU handling capabilities, weren't benefiting from most of what the abstraction layers were actually there to provide. The overhead we removed was overhead we truly weren't getting value from, not corners we cut on features we secretly still needed.

What We Didn't Fully Account For
Here's where I need to be honest about the parts we didn't think through carefully enough at the time, because the decision was evaluated almost entirely on the performance axis, and not enough on the axes that turned out to matter just as much over the following two years.
We lost compatibility with standard AUTOSAR tooling, and we underestimated how much day-to-day value that tooling actually provided. Vector's CANoe/CANalyzer integration, DaVinci's configuration validation, standard diagnostic tooling — all of it assumes a stack that conforms to the AUTOSAR CAN interface contracts. Our custom driver didn't, which meant every one of these tools needed either a custom adapter layer we had to build and maintain ourselves, or simply didn't work at all, forcing engineers back onto lower-level, less convenient debugging approaches for problems the standard tooling would have solved in minutes. We'd budgeted for the driver's own development cost. We had not seriously budgeted for the ongoing cost of tooling incompatibility, which turned out to be a real, recurring tax on debugging efficiency for the life of the project.
The safety certification burden was larger than we expected, and this is the one I'd flag most strongly to anyone considering a similar path today. A standard AUTOSAR CAN stack, from a reputable vendor, typically comes with an existing body of qualification evidence and a well-trodden certification path — assessors have seen it before, and there's established precedent for what evidence is sufficient. Our custom driver had none of that inherited credibility. Every piece of safety argument we needed — freedom from interference, robustness under fault injection, timing analysis — had to be built from scratch, specifically for our implementation, with no prior art to lean on. This cost considerably more engineering and documentation time than our original estimate, which had, in retrospect, implicitly assumed the certification effort would scale similarly to certifying a configuration of the standard stack rather than certifying an entirely bespoke one.
Onboarding new engineers became measurably harder. An engineer who's worked with AUTOSAR Classic CAN stacks before — which is most automotive embedded engineers with any relevant experience — could historically get productive on our CAN layer within days, because the architecture matched their existing mental model. With the custom driver, that same engineer needed real ramp-up time specifically to learn our bespoke implementation, with no transferable prior knowledge helping them get there faster. This showed up as a genuine, recurring cost every time we hired or rotated someone onto this part of the codebase — not a one-time cost we paid once and moved past, but a standing tax on every future onboarding.
What I'd Tell Someone Facing This Decision Today
I don't think the answer is "never do this." The performance problem was real, and the custom driver solved it — that part of the decision holds up under honest scrutiny two years later. What I'd change, if I were making this call again, is the width of the evaluation before committing.
We evaluated this as fundamentally a performance engineering decision, and treated tooling compatibility, certification cost, and team onboarding as secondary considerations to be managed after the fact rather than primary factors weighed alongside performance from the start. They should have been weighed at the same level, up front, with actual estimated costs attached — not because they would necessarily have changed the outcome, but because we would have gone in with accurate expectations rather than being repeatedly surprised by costs that, looking back, were entirely foreseeable if we'd asked the right people the right questions before starting, rather than mostly after.

Where This Actually Landed
We didn't revert the custom driver — the performance requirement it solves is still real, and reverting now would mean solving that problem again, differently, at a point where the sunk cost of the current solution genuinely matters. But we did invest, after the fact, in building better internal tooling parity and writing much more thorough onboarding documentation specifically for this part of the codebase — essentially paying down some of the debt we hadn't accounted for taking on.
If I had to summarize where we landed, it's not "we were wrong to do this" and it's not "we'd absolutely do it again without changes." It's closer to: we solved the problem we set out to solve, genuinely, and we paid a real, ongoing price for it that was larger and more persistent than we estimated, because we didn't evaluate the decision on all the axes that actually mattered — only the one that was loudest at the time we made it. That's the part worth remembering, more than any verdict on whether the decision itself was ultimately right or wrong.
