When you say "zero-copy", do you need the transformation to happen in-place, or is it OK to transform (e.g. encrypt) from one buffer to another buffer as long as the app chooses the buffers?
I'm imagining you do an aio_read() to identify the buffer for the transformed data to be written to, then an O_DIRECT write to identify the buffer containing data to be transformed. The underlying implementation would need to notice the presence of a pending aio_read and place the result directly there rather than in the page cache.
I guess the same thing could do an in-place transformation, but it could get messy.
Of course if O_DIRECT wasn't used it would fall back to the simple case of copying to the page cache, transforming, and copying back out.