Nguyên tắc mô-đun nhà cung cấp

Hãy sử dụng các nguyên tắc sau để tăng độ bền và độ tin cậy của mô-đun nhà cung cấp của bạn. Nhiều nguyên tắc, khi được tuân theo, có thể giúp xác định thứ tự tải mô-đun chính xác và thứ tự mà trình điều khiển phải thăm dò thiết bị dễ dàng hơn.

Một module có thể là một thư viện hoặc một trình điều khiển .

  • Các mô-đun thư viện là các thư viện cung cấp API cho các mô-đun khác sử dụng. Các mô-đun như vậy thường không dành riêng cho phần cứng. Ví dụ về mô-đun thư viện bao gồm mô-đun mã hóa AES, khung remoteproc được biên dịch dưới dạng mô-đun và mô-đun bộ đệm nhật ký. Mã mô-đun trong module_init() chạy để thiết lập cấu trúc dữ liệu, nhưng không có mã nào khác chạy trừ khi được mô-đun bên ngoài kích hoạt.

  • Mô-đun trình điều khiển là trình điều khiển thăm dò hoặc liên kết với một loại thiết bị cụ thể. Các mô-đun như vậy là dành riêng cho phần cứng. Ví dụ về các mô-đun trình điều khiển bao gồm phần cứng UART, PCIe và bộ mã hóa video. Các mô-đun trình điều khiển chỉ kích hoạt khi thiết bị liên kết của chúng có mặt trên hệ thống.

    • Nếu thiết bị không có mặt, mã mô-đun duy nhất chạy là mã module_init() đăng ký trình điều khiển với khung lõi trình điều khiển.

    • Nếu thiết bị hiện diện và trình điều khiển thăm dò hoặc liên kết thành công với thiết bị đó, mã mô-đun khác có thể chạy.

Sử dụng mô-đun init/exit chính xác

Các mô-đun trình điều khiển phải đăng ký trình điều khiển trong module_init() và hủy đăng ký trình điều khiển trong module_exit() . Một cách đơn giản để thực thi những hạn chế này là sử dụng macro trình bao bọc, điều này tránh việc sử dụng trực tiếp các macro module_init() , *_initcall() hoặc module_exit() .

  • Đối với các mô-đun có thể được tải xuống, hãy sử dụng module_ subsystem _driver() . Ví dụ: module_platform_driver() , module_i2c_driver()module_pci_driver() .

  • Đối với các mô-đun không thể tải xuống, hãy sử dụng builtin_ subsystem _driver() Ví dụ: builtin_platform_driver() , builtin_i2c_driver()builtin_pci_driver() .

Một số mô-đun trình điều khiển sử dụng module_init()module_exit() vì chúng đăng ký nhiều trình điều khiển. Đối với mô-đun trình điều khiển sử dụng module_init()module_exit() để đăng ký nhiều trình điều khiển, hãy thử kết hợp các trình điều khiển thành một trình điều khiển duy nhất. Ví dụ: bạn có thể phân biệt bằng cách sử dụng chuỗi compatible hoặc dữ liệu phụ trợ của thiết bị thay vì đăng ký trình điều khiển riêng biệt. Ngoài ra, bạn có thể chia mô-đun trình điều khiển thành hai mô-đun.

Các ngoại lệ của hàm khởi tạo và thoát

Các mô-đun thư viện không đăng ký trình điều khiển và được miễn các hạn chế đối với module_init()module_exit() vì chúng có thể cần các hàm này để thiết lập cấu trúc dữ liệu, hàng đợi công việc hoặc luồng nhân.

Sử dụng macro MODULE_DEVICE_TABLE

Các mô-đun trình điều khiển phải bao gồm macro MODULE_DEVICE_TABLE , cho phép không gian người dùng xác định các thiết bị được mô-đun trình điều khiển hỗ trợ trước khi tải mô-đun. Android có thể sử dụng dữ liệu này để tối ưu hóa việc tải mô-đun, chẳng hạn như tránh tải mô-đun cho các thiết bị không có trong hệ thống. Để biết ví dụ về cách sử dụng macro, hãy tham khảo mã ngược dòng.

Tránh CRC không khớp do kiểu dữ liệu được khai báo chuyển tiếp

Không bao gồm các tệp tiêu đề để hiển thị các loại dữ liệu được khai báo chuyển tiếp. Một số cấu trúc, liên kết và các loại dữ liệu khác được xác định trong tệp tiêu đề ( header-Ah ) có thể được khai báo chuyển tiếp trong một tệp tiêu đề khác ( header-Bh ) thường sử dụng con trỏ tới các loại dữ liệu đó. Mẫu mã này có nghĩa là kernel đang cố tình giữ cấu trúc dữ liệu ở chế độ riêng tư đối với người dùng header-Bh .

Người dùng header-Bh không nên bao gồm header-Ah để truy cập trực tiếp vào phần bên trong của các cấu trúc dữ liệu được khai báo chuyển tiếp này. Làm như vậy sẽ gây ra sự cố không khớp CONFIG_MODVERSIONS CRC (tạo ra sự cố tuân thủ ABI) khi một hạt nhân khác (chẳng hạn như hạt nhân GKI) cố gắng tải mô-đun.

Ví dụ: struct fwnode_handle được định nghĩa trong include/linux/fwnode.h nhưng được khai báo chuyển tiếp là struct fwnode_handle; trong include/linux/device.h vì kernel đang cố gắng giữ kín các chi tiết của struct fwnode_handle với người dùng include/linux/device.h . Trong trường hợp này, không thêm #include <linux/fwnode.h> vào mô-đun để có quyền truy cập vào các thành viên của struct fwnode_handle . Bất kỳ thiết kế nào mà bạn phải bao gồm các tệp tiêu đề như vậy đều cho thấy một mẫu thiết kế xấu.

Không truy cập trực tiếp vào cấu trúc lõi lõi

Việc truy cập hoặc sửa đổi trực tiếp cấu trúc dữ liệu hạt nhân lõi có thể dẫn đến hành vi không mong muốn, bao gồm rò rỉ bộ nhớ, sự cố và khả năng tương thích bị hỏng với các bản phát hành hạt nhân trong tương lai. Cấu trúc dữ liệu là cấu trúc dữ liệu hạt nhân cốt lõi khi nó đáp ứng bất kỳ điều kiện nào sau đây:

  • Cấu trúc dữ liệu được xác định trong KERNEL-DIR /include/ . Ví dụ: struct devicestruct dev_links_info . Cấu trúc dữ liệu được xác định trong include/linux/soc được miễn.

  • Cấu trúc dữ liệu được phân bổ hoặc khởi tạo bởi mô-đun nhưng được hiển thị cho kernel bằng cách được truyền gián tiếp (thông qua một con trỏ trong cấu trúc) hoặc trực tiếp, dưới dạng đầu vào trong một hàm được kernel xuất ra. Ví dụ: mô-đun trình điều khiển cpufreq khởi tạo struct cpufreq_driver và sau đó chuyển nó làm đầu vào cho cpufreq_register_driver() . Sau thời điểm này, mô-đun trình điều khiển cpufreq không nên sửa đổi trực tiếp struct cpufreq_driver vì việc gọi cpufreq_register_driver() sẽ làm cho struct cpufreq_driver hiển thị với kernel.

  • Cấu trúc dữ liệu không được mô-đun của bạn khởi tạo. Ví dụ: struct regulator_dev được trả về bởi regulator_register() .

Chỉ truy cập các cấu trúc dữ liệu lõi của kernel thông qua các hàm do kernel xuất hoặc thông qua các tham số được truyền rõ ràng dưới dạng đầu vào cho hook của nhà cung cấp. Nếu bạn không có API hoặc móc nối nhà cung cấp để sửa đổi các phần của cấu trúc dữ liệu hạt nhân cốt lõi thì đó có thể là hành động cố ý và bạn không nên sửa đổi cấu trúc dữ liệu từ các mô-đun. Ví dụ: không sửa đổi bất kỳ trường nào bên trong struct device hoặc struct device.links .

  • Để sửa đổi device.devres_head , hãy sử dụng hàm devm_*() chẳng hạn như devm_clk_get() , devm_regulator_get() hoặc devm_kzalloc() .

  • Để sửa đổi các trường bên trong struct device.links , hãy sử dụng API liên kết thiết bị như device_link_add() hoặc device_link_del() .

Không phân tích các nút cây thiết bị có thuộc tính tương thích

Nếu nút cây thiết bị (DT) có thuộc tính compatible , thì struct device sẽ được phân bổ tự động cho nó hoặc khi of_platform_populate() được gọi trên nút DT gốc (thường là bởi trình điều khiển thiết bị của thiết bị gốc). Kỳ vọng mặc định (ngoại trừ một số thiết bị được khởi tạo sớm cho bộ lập lịch) là nút DT có thuộc tính compatiblestruct device và trình điều khiển thiết bị phù hợp. Tất cả các ngoại lệ khác đã được xử lý bởi mã ngược dòng.

Ngoài ra, fw_devlink (trước đây gọi là of_devlink ) coi các nút DT có thuộc tính compatible là các thiết bị có struct device được phân bổ được trình điều khiển thăm dò. Nếu nút DT có thuộc tính compatible nhưng struct device được phân bổ không được thăm dò, fw_devlink có thể chặn việc thăm dò các thiết bị tiêu dùng của nó hoặc có thể chặn lệnh gọi sync_state() được gọi cho các thiết bị của nhà cung cấp.

Nếu trình điều khiển của bạn sử dụng hàm of_find_*() (chẳng hạn như of_find_node_by_name() hoặc of_find_compatible_node() ) để trực tiếp tìm nút DT có thuộc tính compatible rồi phân tích cú pháp nút DT đó, hãy sửa mô-đun bằng cách viết trình điều khiển thiết bị có thể thăm dò thiết bị hoặc xóa thuộc tính compatible (chỉ có thể nếu nó chưa được cập nhật ngược dòng). Để thảo luận về các lựa chọn thay thế, hãy liên hệ với Nhóm hạt nhân Android theo địa chỉ kernel-team@android.com và sẵn sàng giải thích các trường hợp sử dụng của bạn.

Sử dụng DT phandle để tra cứu nhà cung cấp

Tham khảo nhà cung cấp bằng cách sử dụng phẩn (tham chiếu/con trỏ tới nút DT) trong DT bất cứ khi nào có thể. Việc sử dụng các liên kết và phân đoạn DT tiêu chuẩn để tham chiếu đến các nhà cung cấp sẽ cho phép fw_devlink (trước đây of_devlink ) tự động xác định các mối quan hệ phụ thuộc giữa các thiết bị bằng cách phân tích cú pháp DT khi chạy. Sau đó, hạt nhân có thể tự động thăm dò các thiết bị theo đúng thứ tự, loại bỏ nhu cầu sắp xếp thứ tự tải mô-đun hoặc MODULE_SOFTDEP() .

Kịch bản kế thừa (không hỗ trợ DT trong nhân ARM)

Trước đây, trước khi hỗ trợ DT được thêm vào nhân ARM, người tiêu dùng như thiết bị cảm ứng đã tra cứu các nhà cung cấp như cơ quan quản lý bằng cách sử dụng các chuỗi duy nhất trên toàn cầu. Ví dụ: trình điều khiển ACME PMIC có thể đăng ký hoặc quảng cáo nhiều bộ điều chỉnh (chẳng hạn như acme-pmic-ldo1 đến acme-pmic-ldo10 ) và trình điều khiển cảm ứng có thể tra cứu bộ điều chỉnh bằng cách sử dụng regulator_get(dev, "acme-pmic-ldo10") . Tuy nhiên, trên một bo mạch khác, LDO8 có thể cung cấp thiết bị cảm ứng, tạo ra một hệ thống cồng kềnh trong đó cùng một trình điều khiển cảm ứng cần xác định chuỗi tra cứu chính xác cho bộ điều chỉnh cho từng bo mạch mà thiết bị cảm ứng được sử dụng.

Kịch bản hiện tại (hỗ trợ DT trong nhân ARM)

Sau khi hỗ trợ DT được thêm vào nhân ARM, người tiêu dùng có thể xác định nhà cung cấp trong DT bằng cách tham khảo nút cây thiết bị của nhà cung cấp bằng cách sử dụng một phân đoạn . Người tiêu dùng cũng có thể đặt tên tài nguyên dựa trên mục đích sử dụng thay vì ai cung cấp tài nguyên đó. Ví dụ: trình điều khiển cảm ứng từ ví dụ trước có thể sử dụng regulator_get(dev, "core")regulator_get(dev, "sensor") để tìm nhà cung cấp cấp nguồn cho lõi và cảm biến của thiết bị cảm ứng. DT liên quan cho thiết bị như vậy tương tự như mẫu mã sau:

touch-device {
    compatible = "fizz,touch";
    ...
    core-supply = <&acme_pmic_ldo4>;
    sensor-supply = <&acme_pmic_ldo10>;
};

acme-pmic {
    compatible = "acme,super-pmic";
    ...
    acme_pmic_ldo4: ldo4 {
        ...
    };
    ...
    acme_pmic_ldo10: ldo10 {
        ...
    };
};

Kịch bản tồi tệ nhất của cả hai thế giới

Một số trình điều khiển được chuyển từ các nhân cũ hơn bao gồm hành vi cũ trong DT chiếm phần tồi tệ nhất của sơ đồ cũ và buộc nó phải sử dụng sơ đồ mới hơn nhằm giúp mọi việc dễ dàng hơn. Trong các trình điều khiển như vậy, trình điều khiển dành riêng cho người tiêu dùng đọc chuỗi để sử dụng cho việc tra cứu bằng thuộc tính DT dành riêng cho thiết bị, nhà cung cấp sử dụng một thuộc tính khác dành riêng cho nhà cung cấp để xác định tên dùng để đăng ký tài nguyên của nhà cung cấp, sau đó người tiêu dùng và nhà cung cấp tiếp tục sử dụng cùng một kế hoạch cũ là sử dụng chuỗi để tra cứu nhà cung cấp. Trong kịch bản tồi tệ nhất của cả hai thế giới này:

  • Trình điều khiển cảm ứng sử dụng mã tương tự như mã sau:

    str = of_property_read(np, "fizz,core-regulator");
    core_reg = regulator_get(dev, str);
    str = of_property_read(np, "fizz,sensor-regulator");
    sensor_reg = regulator_get(dev, str);
    
  • DT sử dụng mã tương tự như sau:

    touch-device {
      compatible = "fizz,touch";
      ...
      fizz,core-regulator = "acme-pmic-ldo4";
      fizz,sensor-regulator = "acme-pmic-ldo4";
    };
    acme-pmic {
      compatible = "acme,super-pmic";
      ...
      ldo4 {
        regulator-name = "acme-pmic-ldo4"
        ...
      };
      ...
      acme_pmic_ldo10: ldo10 {
        ...
        regulator-name = "acme-pmic-ldo10"
      };
    };
    

Không sửa đổi lỗi API khung

Các API khung, chẳng hạn như regulator , clocks , irq , gpio , physextcon , trả về -EPROBE_DEFER dưới dạng giá trị trả về lỗi để cho biết rằng một thiết bị đang cố gắng thăm dò nhưng không thể thăm dò vào lúc này và hạt nhân sẽ thử lại thăm dò sau đó. Để đảm bảo rằng chức năng .probe() của thiết bị của bạn không thành công như mong đợi trong những trường hợp như vậy, đừng thay thế hoặc ánh xạ lại giá trị lỗi. Việc thay thế hoặc ánh xạ lại giá trị lỗi có thể khiến -EPROBE_DEFER bị loại bỏ và khiến thiết bị của bạn không bao giờ bị thăm dò.

Sử dụng các biến thể API devm_*()

Khi thiết bị lấy tài nguyên bằng API devm_*() , tài nguyên sẽ tự động được hạt nhân giải phóng nếu thiết bị không thăm dò được hoặc thăm dò thành công và sau đó không bị ràng buộc. Chức năng này làm cho mã xử lý lỗi trong probe() sạch hơn vì nó không yêu cầu bước nhảy goto để giải phóng các tài nguyên mà devm_*() thu được và đơn giản hóa các thao tác hủy liên kết trình điều khiển.

Xử lý việc hủy liên kết trình điều khiển thiết bị

Hãy có chủ ý về việc hủy liên kết trình điều khiển thiết bị và đừng để việc hủy liên kết không được xác định vì không xác định không có nghĩa là không được phép. Bạn phải triển khai đầy đủ tính năng hủy liên kết trình điều khiển thiết bị hoặc vô hiệu hóa rõ ràng tính năng hủy liên kết trình điều khiển thiết bị.

Thực hiện hủy liên kết trình điều khiển thiết bị

Khi chọn thực hiện đầy đủ việc hủy liên kết trình điều khiển thiết bị, hãy hủy liên kết trình điều khiển thiết bị một cách rõ ràng để tránh rò rỉ bộ nhớ hoặc tài nguyên cũng như các vấn đề bảo mật. Bạn có thể liên kết một thiết bị với trình điều khiển bằng cách gọi probe() của trình điều khiển và hủy liên kết thiết bị bằng cách gọi hàm remove() của trình điều khiển. Nếu không tồn tại hàm remove() thì kernel vẫn có thể hủy liên kết thiết bị; lõi trình điều khiển giả định rằng trình điều khiển không cần thực hiện công việc dọn dẹp khi hủy liên kết khỏi thiết bị. Trình điều khiển không được liên kết khỏi thiết bị không cần thực hiện bất kỳ công việc dọn dẹp rõ ràng nào khi cả hai điều sau đều đúng:

  • Tất cả các tài nguyên mà probe() của trình điều khiển có được đều thông qua API devm_*() .

  • Thiết bị phần cứng không cần trình tự tắt hoặc ngừng hoạt động.

Trong tình huống này, lõi trình điều khiển xử lý việc giải phóng tất cả tài nguyên có được thông qua API devm_*() . Nếu một trong hai câu trên không đúng thì trình điều khiển cần thực hiện dọn dẹp (giải phóng tài nguyên và tắt hoặc tạm dừng phần cứng) khi nó hủy liên kết khỏi thiết bị. Để đảm bảo rằng thiết bị có thể hủy liên kết mô-đun trình điều khiển một cách rõ ràng, hãy sử dụng một trong các tùy chọn sau:

  • Nếu phần cứng không cần trình tự tắt hoặc ngừng hoạt động, hãy thay đổi mô-đun thiết bị để lấy tài nguyên bằng API devm_*() .

  • Triển khai thao tác trình điều khiển remove() trong cùng cấu trúc với hàm probe() , sau đó thực hiện các bước dọn dẹp bằng hàm remove() .

Vô hiệu hóa rõ ràng việc hủy liên kết trình điều khiển thiết bị (không được khuyến nghị)

Khi chọn tắt rõ ràng việc hủy liên kết trình điều khiển thiết bị, bạn cần không cho phép hủy liên kết không cho phép dỡ mô-đun.

  • Để không cho phép hủy liên kết, hãy đặt cờ suppress_bind_attrs thành true trong struct device_driver của trình điều khiển; cài đặt này ngăn các tệp bindunbind hiển thị trong thư mục sysfs của trình điều khiển. Tệp unbind là thứ cho phép không gian người dùng kích hoạt việc hủy liên kết trình điều khiển khỏi thiết bị của nó.

  • Để không cho phép dỡ mô-đun, hãy đảm bảo mô-đun có [permanent] trong lsmod . Bằng cách không sử dụng module_exit() hoặc module_XXX_driver() , mô-đun được đánh dấu là [permanent] .

Không tải chương trình cơ sở từ bên trong chức năng thăm dò

Trình điều khiển không nên tải chương trình cơ sở từ bên trong .probe() vì chúng có thể không có quyền truy cập vào chương trình cơ sở nếu trình điều khiển thăm dò trước khi gắn hệ thống tệp dựa trên flash hoặc bộ nhớ cố định. Trong những trường hợp như vậy, API request_firmware*() có thể bị chặn trong một thời gian dài rồi không thành công, điều này có thể làm chậm quá trình khởi động một cách không cần thiết. Thay vào đó, hãy trì hoãn việc tải chương trình cơ sở khi khách hàng bắt đầu sử dụng thiết bị. Ví dụ: trình điều khiển màn hình có thể tải chương trình cơ sở khi thiết bị hiển thị được mở.

Việc sử dụng .probe() để tải chương trình cơ sở có thể ổn trong một số trường hợp, chẳng hạn như trong trình điều khiển đồng hồ cần chương trình cơ sở để hoạt động nhưng thiết bị không tiếp xúc với không gian người dùng. Có thể có các trường hợp sử dụng thích hợp khác.

Thực hiện thăm dò không đồng bộ

Hỗ trợ và sử dụng tính năng thăm dò không đồng bộ để tận dụng các cải tiến trong tương lai, chẳng hạn như tải mô-đun song song hoặc thăm dò thiết bị để tăng tốc thời gian khởi động, có thể được thêm vào Android trong các bản phát hành trong tương lai. Các mô-đun trình điều khiển không sử dụng tính năng thăm dò không đồng bộ có thể làm giảm hiệu quả của các hoạt động tối ưu hóa đó.

Để đánh dấu trình điều khiển là hỗ trợ và ưu tiên thăm dò không đồng bộ, hãy đặt trường probe_type trong thành viên struct device_driver của trình điều khiển. Ví dụ sau đây cho thấy hỗ trợ như vậy được kích hoạt cho trình điều khiển nền tảng:

static struct platform_driver acme_driver = {
        .probe          = acme_probe,
        ...
        .driver         = {
                .name   = "acme",
                ...
                .probe_type = PROBE_PREFER_ASYNCHRONOUS,
        },
};

Việc làm cho trình điều khiển hoạt động với tính năng thăm dò không đồng bộ không yêu cầu mã đặc biệt. Tuy nhiên, hãy ghi nhớ những điều sau khi thêm hỗ trợ thăm dò không đồng bộ.

  • Đừng đưa ra giả định về các phụ thuộc đã được thăm dò trước đó. Kiểm tra trực tiếp hoặc gián tiếp (hầu hết các lệnh gọi khung) và trả về -EPROBE_DEFER nếu một hoặc nhiều nhà cung cấp chưa sẵn sàng.

  • Nếu bạn thêm thiết bị con vào chức năng thăm dò của thiết bị gốc, đừng cho rằng thiết bị con được thăm dò ngay lập tức.

  • Nếu thăm dò không thành công, hãy thực hiện xử lý lỗi thích hợp và dọn dẹp (xem Sử dụng các biến thể API devm_*() ).

Không sử dụng MODULE_SOFTDEP để đặt hàng đầu dò thiết bị

Hàm MODULE_SOFTDEP() không phải là giải pháp đáng tin cậy để đảm bảo thứ tự của các đầu dò thiết bị và không được sử dụng vì những lý do sau.

  • Thăm dò bị trì hoãn. Khi mô-đun tải, quá trình thăm dò thiết bị có thể bị trì hoãn do một trong các nhà cung cấp của mô-đun chưa sẵn sàng. Điều này có thể dẫn đến sự không khớp giữa thứ tự tải mô-đun và thứ tự đầu dò thiết bị.

  • Một trình điều khiển, nhiều thiết bị. Một mô-đun trình điều khiển có thể quản lý một loại thiết bị cụ thể. Nếu hệ thống bao gồm nhiều phiên bản của một loại thiết bị và mỗi thiết bị đó có yêu cầu thứ tự thăm dò khác nhau thì bạn không thể tôn trọng các yêu cầu đó bằng cách sử dụng thứ tự tải mô-đun.

  • Thăm dò không đồng bộ. Các mô-đun trình điều khiển thực hiện việc thăm dò không đồng bộ sẽ không thăm dò thiết bị ngay lập tức khi mô-đun được tải. Thay vào đó, một luồng song song xử lý việc thăm dò thiết bị, điều này có thể dẫn đến sự không khớp giữa thứ tự tải mô-đun và thứ tự thăm dò thiết bị. Ví dụ: khi mô-đun trình điều khiển chính I2C thực hiện thăm dò không đồng bộ và mô-đun trình điều khiển cảm ứng phụ thuộc vào PMIC trên bus I2C, ngay cả khi trình điều khiển cảm ứng và trình điều khiển PMIC tải theo đúng thứ tự, thì đầu dò của trình điều khiển cảm ứng có thể được thử trước đó đầu dò trình điều khiển PMIC.

Nếu bạn có các mô-đun trình điều khiển sử dụng hàm MODULE_SOFTDEP() , hãy sửa chúng để chúng không sử dụng chức năng đó. Để giúp bạn, nhóm Android đã cập nhật các thay đổi ngược dòng cho phép kernel xử lý các vấn đề về thứ tự mà không cần sử dụng MODULE_SOFTDEP() . Cụ thể, bạn có thể sử dụng fw_devlink để đảm bảo thứ tự thăm dò và (sau khi tất cả người tiêu dùng thiết bị đã thăm dò) sử dụng hàm gọi lại sync_state() để thực hiện mọi tác vụ cần thiết.

Sử dụng #if IS_ENABLED() thay vì #ifdef cho cấu hình

Sử dụng #if IS_ENABLED(CONFIG_XXX) thay vì #ifdef CONFIG_XXX để đảm bảo rằng mã bên trong khối #if tiếp tục biên dịch nếu cấu hình thay đổi thành cấu hình ba trạng thái trong tương lai. Sự khác biệt như sau:

  • #if IS_ENABLED(CONFIG_XXX) đánh giá là true khi CONFIG_XXX được đặt thành mô-đun ( =m ) hoặc tích hợp ( =y ).

  • #ifdef CONFIG_XXX đánh giá là true khi CONFIG_XXX được đặt thành tích hợp ( =y ) , nhưng không đúng khi CONFIG_XXX được đặt thành mô-đun ( =m ). Chỉ sử dụng tùy chọn này khi bạn chắc chắn muốn thực hiện điều tương tự khi cấu hình được đặt thành mô-đun hoặc bị tắt.

Sử dụng macro chính xác để biên dịch có điều kiện

Nếu CONFIG_XXX được đặt thành mô-đun ( =m ), hệ thống xây dựng sẽ tự động xác định CONFIG_XXX_MODULE . Nếu trình điều khiển của bạn được điều khiển bởi CONFIG_XXX và bạn muốn kiểm tra xem trình điều khiển của mình có đang được biên dịch dưới dạng mô-đun hay không, hãy sử dụng các nguyên tắc sau:

  • Trong tệp C (hoặc bất kỳ tệp nguồn nào không phải là tệp tiêu đề) cho trình điều khiển của bạn, không sử dụng #ifdef CONFIG_XXX_MODULE vì nó hạn chế và bị hỏng một cách không cần thiết nếu cấu hình được đổi tên thành CONFIG_XYZ . Đối với mọi tệp nguồn không có tiêu đề được biên dịch thành mô-đun, hệ thống xây dựng sẽ tự động xác định MODULE cho phạm vi của tệp đó. Do đó, để kiểm tra xem tệp C (hoặc bất kỳ tệp nguồn không có tiêu đề nào) có đang được biên dịch như một phần của mô-đun hay không, hãy sử dụng #ifdef MODULE (không có tiền tố CONFIG_ ).

  • Trong các tệp tiêu đề, việc kiểm tra tương tự sẽ khó khăn hơn vì các tệp tiêu đề không được biên dịch trực tiếp thành tệp nhị phân mà được biên dịch như một phần của tệp C (hoặc các tệp nguồn khác). Sử dụng các quy tắc sau cho tệp tiêu đề:

    • Đối với tệp tiêu đề sử dụng #ifdef MODULE , kết quả sẽ thay đổi dựa trên tệp nguồn nào đang sử dụng nó. Điều này có nghĩa là cùng một tệp tiêu đề trong cùng một bản dựng có thể có các phần mã khác nhau được biên dịch cho các tệp nguồn khác nhau (mô-đun so với tệp tích hợp hoặc bị vô hiệu hóa). Điều này có thể hữu ích khi bạn muốn xác định macro cần mở rộng theo một cách cho mã tích hợp và mở rộng theo cách khác cho mô-đun.

    • Đối với tệp tiêu đề cần biên dịch thành một đoạn mã khi một CONFIG_XXX cụ thể được đặt thành mô-đun (bất kể tệp nguồn bao gồm nó có phải là mô-đun hay không), tệp tiêu đề phải sử dụng #ifdef CONFIG_XXX_MODULE .