I. Platform Devices và drivers.
Platform device là gì ? Một số người cho rằng platform device là các device được tích hợp sẵn trong các SoC. Tuy nhiên thực tế điều này không hoàn toàn đúng. Các platform device là các thiết bị không thể liệt kê được bởi hệ thống (tức là not-hotplug, khi bạn cắm một thiết bị mới vào thì OS không hề biết rằng bạn vừa thêm vào 1 cái gì đấy, khác với hotplug như USB hay PCI), do đó kernel cần phải biết về những thiết bị này từ trước lúc khởi động (tức là chúng ta phải đăng ký những thiết bị này với kernel lúc build kernel). Một số ví dụ về các thiết bị này gồm có: I2C, UART, SPI, …
Nhìn từ góc độc kernel, những thiết bị này là các thiết bị gốc, và không kết nối đến bất kỳ cái gì cả (tức là nó là 1 phần của kernel). Trong kernel, nó sử dụng các pseudo platform bus, còn được gọi là các platform bus như là một bus ảo của kernel cho các thiết bị không nằm trên các Bus vật lý mà kernel có thể biết đến.
Trong file header linux/platform_device.h
có định nghĩa 2 driver model interface cho platform bus nêu ở trên, gồm có:
1. Platform drivers
Platform driver tuân theo quy ước chuẩn của driver model, ở đó việc phát hiện/liệt kê các thiết bị được thực hiện bên ngoài các driver, và các driver cung cấp hàm probe()
và remove
. Chúng cung cấp các công cụ quản lý năng lượng theo tiêu chuẩn cơ bản.
lưu ý rằng hàm probe() nói chung nên xác thực xem phần cứng của thiết bị đã được chỉ ra thực sự tồn tại. Probing có thể sử dụng device resource bao gồm clock, và device platform_data.
Platform drivers đăng ký nó với hệ thống bằng cách sau:
Hoặc sử dụng hàm sau trong init section:
Các kernel module có thể được bao gồm nhiều platform drivers. Platform core cung cấp các hàm trợ giúp để đăng ký và hủy đăng ký một mảng các driver:
Nếu một trong các driver đăng ký thất bại thì tất cả các drivers trong batch đều bị unregister. (Transaction)
Lưu ý là không phải tất cả platform device được được handle bằng platform driver.
Platform driver phải hiện thực một hàm probe()
, được gọi bởi kernel khi module được inserted hoặc khi một device yêu cầu driver đó. Khi phát triển một platform driver, cấu trúc chính mà chúng ta cần sử dụng là struct platform_driver
, bạn cần định nghĩa các trường (functions) cơ bản của cấu trúc này trước khi đăng ký nó với platform bus core.
Ví dụ
- probe(): Đây là hàm được gọi khi thiết bị đòi hỏi driver ở lần đầu tiên, nó được khai báo như sau:
- remove(): hàm này được gọi khi device không còn sử dụng driver nữa, khai báo như sau:
Đăng ký platform driver với kernel rất bằng cách gọi hàm platform_driver_register()
hoặc platform_driver_probe()
trong hàm init. Hai hàm này khác nhau ở điểm nào?
- platform_driver_register()
đăng ký và đặt driver vào danh sách các driver được bảo trì bởi kernel, do đó hàm probe() của driver có thể được gọi theo yêu cầu (tức là gọi khi có một device mới xuất hiện và match với signature).
- palatform_driver_probe()
, kernel ngay lập tức chạy một vòng lặp, kiểm tra xem nếu có một platform device match với driver (bằng trường .name) và sau đó gọi hàm probe()
nếu nó thấy device match. Điều này có nghĩa là device phải đang kết nối với bus slot khi đăng ký, ngược lại nó sẽ bị bỏ qua. Hàm này nên đặt trong __init() section để đảm bảo là nó sẽ bị giải phóng sau khi quá trình khởi động kết thúc (ngược lại vòng lặp tiếp tục chạy sẽ rất tốn tài nguyên)
Tiếp theo chúng ta sẽ thử viết một platform driver đơn giản, nhiệm vụ của nó là tự đăng ký nó với kernel.
Hãy compile vào insert nó vào kernel xem có chuyện gì xảy ra.
2. Platform device
Bao gồm: các device sử dụng I/O port truyền thống, các host bridges đến các peripheral bus, và hầu hết các controller được tích hợp vào SoC.
Platform device không kết nối đến các bus vật lý mà nó chỉ kết nối đến psuedo bus.
Điểm chung của các thiết bị này là: đánh địa chỉ trực tiếp từ một CPU bus. Đôi khi (rất hiếm), một platform_device
sẽ được kết nối thông qua một hoặc một số loại bus khác; nhưng các thanh ghi cảu nó vẫn được đánh địa chỉ một cách trực tiếp.
Các platform device có một tên (name field), được sử dụng trong driver binding, và một danh sách các tài nguyên chẳng hạn như addresses và IRQs. Sau khi bạn đã thực hiện xong driver, bạn sẽ phải đưa cho kernel một hoặc nhiều device sẽ cần đến driver đó. Một platform driver được biểu diễn trong kernel như một thực thể của cấu trúc sau:
Trường name
cần phải trùng với trường name
của platform_driver ở phần 1.
2.1 Tài nguyên và thông tin dữ liệu của platform device
Đối với các hot-pluggable devices, kernel biết rằng device đó là gì, có khả năng gì và cần gì để có thể hoạt động tốt, ngược lại, kernel không hề biết về các platform device, do đó, việc cung cấp thêm các thông tin cho kernel là cần thiết. Có hai phương thức để thông tin cho kernel về các tài nguyên (irq, dma, mem, region, bus, I/O ports) và dữ liệu (những dữ liệu riêng tư mà driver có thể cần đến) là device provisioning và [?]
2.1.2. Device provisioning (cũ và không khuyên dùng)
Khai báo trong mach files
a. Tài nguyên (resource).
Nhìn từ góc độ phần cứng, tài nguyên hay resource là tất cả các thành phần đặc tả cho đevice, và đấy là những gì device cần cho mục địch cài đặt và hoạt động chính xác. Có 6 loại resource trong kernel, được liệt kê trong header include/linux/ioport.h
, các loại resource này được define và sử dụng như các cờ để định loài cho resouce.
Trong kernel, một resource được biểu diễn như là một thực thể của cấu trúc sau:
Ở đây: start/end : là vị trí bắt đầu và kết thúc của resource. ĐỐi với I/O hoặc memory regions, thì đây chính là nơi chúng bắt đầu/kết thúc (address). ĐỐi với các kiểu resource IRQ lines, buses hay DMA channels thì start và end phải có cùng giá trị. Flags là một mặt nạ để định loại cho resource (các IORESOURCE_* đã nêu ở trên). Tên là định danh của resource.
Khi chúng ta khai báo resource cho platform device thì chúng ta cần lấy nó ra trong driver code để làm việc với nó. Hàm probe()
là một vị trí thích hợp cho việc này. Tham số *pdev
trong hàm probe()
được điền một cách tự động bởi kernel với đầy đủ data và resource đã khai báo ở trên.
Để lấy resource ra từ pdev chúng ta sử dụng hàm sau:
Nếu resource là một IRQ, thì chúng ta có thể dùng cách đơn giản hơn, là sử dụng hàm sau:
b. Data (Dữ liệu).
Bao gồm các dữ liệu không nằm trong các Resource type ở trên, ví dụ như GPIO.
Như chúng ta đã biết struct platform_device
có chứa trường dev có kiểu struct device
, cấu trúc này là chứa một trường có kiểu struct platform_data
, đây là nơi chúng ta lưu platform data.
THử tìm hiểu một ví dụ sau (code này mình tóm từ trên mạng về, hí hí):
Để lấy platform_data đã khai báo, chúng ta làm như sau:
II. Kernel Kết hợp platform device với platform driver như thế nào?
Làm thế nào kernel biết được device nào được điều khiển bởi driver nào? Câu trả lời là MODULE_DEVICE_TABLE
. Macro này sẽ giúp driver đưa ra một bảng ID của nó, bảng này mô tả những device mà nó có thể hỗ trợ. Trong khi chờ đợi, nếu như driver có thể được biên dịch như là một module (thay vì built-in), trường driver.name
nên trùng với module name. Nếu không, module sẽ không thể load một cách tự động, trừ khi chúng ta đã sử dụng MODULE_ALIAS
macro để thêm một tên khác cho module. Ở thời điểm biên dịch, những thông tin này sẽ được đọc từ tất cả các driver để tạo ra một bảng device table. Khi kernel phải tìm kiếm driver cho một device, kernel sẽ duyệt qua device table. Nếu kernel tìm thấy một driver (table row) phù hợp với compatible
, hoặc device/vendor id, hoặc name của device được thêm vào, thì module đã cung cấp thông tin để tạo nên row đó sẽ được load, và probe
function sẽ được gọi với tham số truyền vào là device tương ứng.
Ở đây trường type
có thể là i2c, spi, of, platform, usb, pci
hoặc bất kỳ bus nào khác đã được định nghĩa bởi kernel trong file mod_devicetable.h
. Nó phụ thuộc vào bus mà device ngồi, và phục thuộc vào kỹ thuật matching bạn muốn dùng. CÒn trường name
là một con trỏ đến một mảng xxx_device_id, được sử dụng cho mục đích matching device. xxx chính là tên của bus bạn muốn dùng, hoặc of_device_id nếu bạn dùng device tree.
Kernel thực hiện việc match platform device với driver thông qua hàm platform_match
được định nghĩa trong file /drivers/base/platform.c:
Thật ra mấy cái hàm matching trên chỉ là so sánh xâu mà thôi.
Bây giờ, mình sẽ viết một platform device để phối kết hợp với cái platform driver ở trên.
Mình sẽ viết platform device trong một file mới đặt tên là oni_pdev
.
Trước hết chúng ta sẽ định nghĩa ra một struct để lưu giữ platform data. Cấu trúc này mình sẽ định nghĩa ra 2 trường là tên và tuổi (y như bài tập hồi sing viên :v). Tuy nhiên thay vì định nghĩa vào file oni_pdev.c, mình thực hiện điều này trong một file header mới tên là oni_pp.h
vì chúng ta cần sử dụng struct này trong file platform driver.
Quay lại file oni_pdev.c
, điều đầu tiên tất nhiên là include file header ở trên vào. Tiếp theo chúng ta khai báo platform data instance và resource cho device như sau:
Sau khi đã có data và resource, mình sẽ khai báo platform_device của mình:
Tiếp theo mình sẽ đăng ký platform device này ở trong hàm init của module và insmod module vào hệ thống.
Bây giờ mình sửa lại file oni_pdriver.c ở phần I một chút để có thể hiển thị rõ hơn việc pdriver và pdevice đã được matching với nhau.
Trong hàm my_pdriver_probe()
mình sẽ thay dòng code
bằng đoạn code sau:
Quên mất là phải include file header đã tạo vào nữa.
Sau đấy rebuild nó và insmod vào kernel. Kiểm tra dmesg, bạn sẽ thấy dòng log sau:
Our device has name Phi Nguyen and age 25
Đây là data chúng ta đã khai báo cho platform device.
Leave a Comment