I2C vốn là một giao thức bus tốc độ thấp, nơi phần lớn các gói tin truyền tải có kích thước rất nhỏ. Chính vì đặc điểm này, I2C không phải là đối tượng ưu tiên hàng đầu cho việc sử dụng DMA (Direct Memory Access). Hiện nay, chỉ khoảng 10% các driver I2C master trong Linux kernel tích hợp hỗ trợ DMA. Đối với các giao dịch cực ngắn, chi phí thiết lập DMA (setup overhead) thường lớn hơn so với hiệu quả mà nó mang lại so với phương pháp PIO (Programmed I/O) truyền thống.
Điều kiện sử dụng DMA trong I2C
Trong kiến trúc Linux kernel, các buffer dành cho thông điệp I2C không bắt buộc phải luôn ở trạng thái DMA-safe. Tuy nhiên, nếu kích thước dữ liệu đủ lớn, việc sử dụng DMA-safe buffer là một khuyến nghị quan trọng để tăng hiệu suất. Thông thường, ngưỡng bắt đầu mang lại lợi ích của DMA là khoảng 8 đến 16 bytes. Ngoài ra, một số cấu trúc phần cứng cụ thể bắt buộc phải dùng DMA, ví dụ như khi một I2C master controller kết nối thông qua bridge USB, do bản thân USB yêu cầu buffer phải tương thích với DMA.
Triển khai phía I2C Clients
Đối với các thiết bị đầu cuối (clients), nếu buffer trong cấu trúc i2c_msg đã được đảm bảo an toàn cho DMA, lập trình viên nên thiết lập cờ I2C_M_DMA_SAFE. Cờ này thông báo cho I2C core và driver controller rằng họ có thể thực hiện các thao tác DMA trực tiếp trên vùng nhớ đó.
Việc sử dụng cờ này là không bắt buộc. Các driver master cũ không hỗ trợ cờ này vẫn sẽ hoạt động theo cơ chế cũ (có thể gây rủi ro nếu truy cập DMA vào vùng nhớ không an toàn). Đối với không gian người dùng (userspace), dữ liệu luôn được copy vào kernel space trước khi xử lý, và I2C core sẽ đảm bảo vùng nhớ đích trong kernel luôn hỗ trợ DMA. Các hàm tiện ích sau đây đã được cung cấp để hỗ trợ truyền tải an toàn:
i2c_master_send_dmasafe()i2c_master_recv_dmasafe()
Nếu sử dụng hàm i2c_transfer(), người dùng phải tự gán cờ I2C_M_DMA_SAFE vào cấu trúc thông điệp một cách thủ công.
Quản lý Buffer phía I2C Masters
Các lập trình viên viết driver cho I2C master có thể tận dụng các hàm hỗ trợ từ I2C core để xử lý buffer DMA. Quy trình điển hình bao gồm việc kiểm tra ngưỡng kích thước và cấp phát bounce buffer nếu cần thiết:
struct i2c_msg *current_msg = &msgs[i];
u8 *dma_buffer;
/* Kiểm tra và lấy buffer an toàn cho DMA với ngưỡng 16 bytes */
dma_buffer = i2c_get_dma_safe_msg_buf(current_msg, 16);
if (dma_buffer) {
/* Thực hiện truyền tải dữ liệu qua DMA sử dụng dma_buffer */
execute_dma_transfer(adapter, dma_buffer, current_msg->len);
} else {
/* Fallback về phương thức PIO nếu không thỏa mãn điều kiện DMA */
execute_pio_transfer(adapter, current_msg->buf, current_msg->len);
}
Sau khi quá trình truyền tải hoàn tất, buffer thu được từ hàm trên cần phải được giải phóng hoặc đồng bộ hóa lại vào thông điệp gốc:
/* Giải phóng buffer và đồng bộ dữ liệu nếu truyền tải thành công */
i2c_put_dma_safe_msg_buf(dma_buffer, current_msg, transfer_success);
Trong đó, tham số cuối cùng quyết định việc dữ liệu có được sao chép ngược lại từ bounce buffer về thông điệp gốc hay không. Nếu xảy ra lỗi trong quá trình DMA và không có dữ liệu nào được nhận, việc đồng bộ hóa là không cần thiết.
Cơ chế xử lý bounce buffer của kernel khá đơn giản: nó sẽ cấp phát một vùng nhớ mới nếu buffer gốc không an toàn cho DMA. Nếu driver yêu cầu các kịch bản phức tạp hơn như tái sử dụng buffer đã cấp phát trước (pre-allocated buffers), lập trình viên cần tự triển khai logic riêng. Một ví dụ tham khảo tốt trong mã nguồn kernel cho việc này là driver i2c-sh_mobile.
Lưu ý về Debugging
Khi phát triển hoặc tích hợp DMA cho driver I2C, việc bật tùy chọn CONFIG_DMA_API_DEBUG trong cấu hình kernel là cực kỳ quan trọng. Công cụ này sẽ giúp phát hiện các lỗi vi phạm vùng nhớ, sai lệch về quyền truy cập hoặc lỗi đồng bộ cache, vốn là những vấn đề rất khó truy vết khi chỉ debug thông thường.