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

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

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

  • Mô-đun thư viện là các thư viện cung cấp API để các mô-đun khác sử dụng. Những 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ã hoá AES, khung remoteproc được biên dịch dưới dạng mô-đun và mô-đun logbuffer. Mã mô-đun trong module_init() chạy để thiết lập cấu trúc dữ liệu, nhưng sẽ không có mã nào khác chạy trừ phi được một mô-đun bên ngoài kích hoạt.

  • Mô-đun trình điều khiển là các 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ề mô-đun trình điều khiển bao gồm UTT, PCIe và phần cứng bộ mã hoá 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ó trên hệ thống.

    • Nếu không có thiết bị, thì 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ị đó, thì mã mô-đun khác có thể chạy.

Sử dụng đúng cách tính năng khởi động và thoát mô-đun

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

  • Đối với các mô-đun có thể được huỷ tải, 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ể huỷ tải, 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ì các mô-đun này đă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 cố gắng 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ý các trình điều khiển riêng. 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 quy định hạn chế đối với module_init()module_exit() vì các mô-đun này 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. Macro này 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 hoá 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 các ví dụ về cách sử dụng macro, hãy tham khảo mã nguồn cấp trên.

Tránh trường hợp CRC không khớp do các loại dữ liệu được khai báo trước

Đừng thêm các tệp tiêu đề để xem được các loại dữ liệu được khai báo trước. 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-A.h) có thể được khai báo trước trong một tệp tiêu đề khác (header-B.h) thường sử dụng con trỏ đến các loại dữ liệu đó. Mẫu mã này có nghĩa là hạt nhân cố ý cố gắng giữ cấu trúc dữ liệu ở chế độ riêng tư cho người dùng header-B.h.

Người dùng header-B.h không nên đưa header-A.h vào để truy cập trực tiếp vào nội bộ của các cấu trúc dữ liệu được khai báo trước này. Việc này gây ra các vấn đề về sự không khớp CRC CONFIG_MODVERSIONS (gây ra các vấn đề về việ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 xác định trong include/linux/fwnode.h, nhưng được khai báo chuyển tiếp dưới dạng struct fwnode_handle; trong include/linux/device.h vì hạt nhân đang cố gắng giữ bí mật thông tin chi tiết về struct fwnode_handle với người dùng include/linux/device.h. Trong trường hợp này, đừ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. Mọi thiết kế mà bạn phải đưa các tệp tiêu đề như vậy vào đều cho thấy một mẫu thiết kế không tốt.

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

Việc trực tiếp truy cập hoặc sửa đổi cấu trúc dữ liệu hạt nhân cốt lõi có thể dẫn đến hành vi không mong muốn, bao gồm cả 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 đá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ác cấu trúc dữ liệu được xác định trong include/linux/soc sẽ được miễn trừ.

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

  • Mô-đun của bạn không khởi chạy cấu trúc dữ liệu. Ví dụ: struct regulator_dev do regulator_register() trả về.

Chỉ truy cập vào các cấu trúc dữ liệu hạt nhân cốt lõi thông qua các hàm do hạt nhân xuất hoặc thông qua các tham số được truyền rõ ràng dưới dạng dữ liệu đầu vào cho các trình bổ trợ của nhà cung cấp. Nếu bạn không có API hoặc trình bổ trợ của 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à do chủ ý 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_*() 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 đường liên kết thiết bị như device_link_add() hoặc device_link_del().

Không phân tích cú pháp các nút devicetree có thuộc tính tương thích

Nếu một nút cây thiết bị (DT) có thuộc tính compatible, thì struct device sẽ được phân bổ cho nút đó một cách tự động hoặc khi of_platform_populate() được gọi trên nút DT mẹ (thường là do trình điều khiển thiết bị của thiết bị mẹ thực hiện). Dự kiến mặc định (ngoại trừ một số thiết bị được khởi chạy sớm cho trình lập lịch biểu) 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 trường hợp ngoại lệ khác đều đã được xử lý bằng 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ổ do người lái xe thăm dò. Nếu một nút DT có thuộc tính compatible nhưng struct device được phân bổ không được thăm dò, thì fw_devlink có thể chặn các thiết bị tiêu dùng thăm dò hoặc chặn các lệnh gọi sync_state() được gọi cho các thiết bị nhà cung cấp.

Nếu trình điều khiể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 một trình điều khiển thiết bị có thể thăm dò thiết bị hoặc xoá thuộc tính compatible (chỉ có thể thực hiện nếu chưa được ngược dòng). Để thảo luận về các giải pháp thay thế, hãy liên hệ với Nhóm nhân kernel Android theo địa chỉ kernel-team@android.com và chuẩn bị lý do cho trường hợp sử dụng của bạn.

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

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

Tình huống cũ (không hỗ trợ DT trong hạt nhân ARM)

Trước đây, trước khi tính năng 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 (chẳng hạn như cơ quan quản lý) bằng cách sử dụng các chuỗi riêng biệt trên toàn hệ thống. Ví dụ: trình điều khiển PMIC ACME 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 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 đúng chuỗi tra cứu cho bộ điều chỉnh cho mỗi bo mạch mà thiết bị cảm ứng được sử dụng.

Tình huống hiện tại (hỗ trợ DT trong hạt nhân ARM)

Sau khi hỗ trợ DT được thêm vào nhân ARM, người dùng có thể xác định nhà cung cấp trong DT bằng cách tham chiếu đến nút cây thiết bị của nhà cung cấp bằng phandle. Người dùng cũng có thể đặt tên cho tài nguyên dựa trên mục đích sử dụng thay vì dựa trên người cung cấp tài nguyên. Ví dụ: trình điều khiển cảm ứng trong ví dụ trước có thể sử dụng regulator_get(dev, "core")regulator_get(dev, "sensor") để lấy các 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 kết cho một thiết bị như vậy tương tự như mã mẫu 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 {
        ...
    };
};

Trường hợp xấu nhất

Một số trình điều khiển được chuyển từ các hạt nhân cũ bao gồm hành vi cũ trong DT, lấy phần tệ nhất của lược đồ cũ và buộc nó vào lược đồ mới nhằm giúp mọi thứ dễ dàng hơn. Trong các trình điều khiển như vậy, trình điều khiển người dùng đọc chuỗi cần dùng để tra cứu bằng cách sử dụ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 sẽ dùng để đăng ký tài nguyên nhà cung cấp, sau đó người dùng và nhà cung cấp tiếp tục sử dụng cùng một lược đồ cũ về việc sử dụng chuỗi để tra cứu nhà cung cấp. Trong trường hợp xấu nhất:

  • 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 thiết bị đang cố gắng thăm dò nhưng không thể tại thời điểm này và hạt nhân sẽ thử lại thăm dò sau. Để đảm bảo rằng hàm .probe() của thiết bị không hoạt độ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ờ được thăm dò.

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

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

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

Hãy chủ ý huỷ liên kết trình điều khiển thiết bị và không để trạng thái huỷ liên kết không xác định vì trạng thái 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 huỷ liên kết trình điều khiển thiết bị hoặc tắt rõ ràng tính năng huỷ liên kết trình điều khiển thiết bị.

Triển khai việc huỷ liên kết trình điều khiển thiết bị

Khi chọn triển khai đầy đủ tính năng huỷ liên kết trình điều khiển thiết bị, hãy huỷ 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 và các vấn đề về 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 hàm probe() của trình điều khiển và huỷ liên kết mộ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 có hàm remove() nào, hạt nhân vẫn có thể huỷ 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 làm sạch khi huỷ liên kết với thiết bị. Trình điều khiển không liên kết với thiết bị không cần thực hiện bất kỳ thao tác dọn dẹp rõ ràng nào khi cả hai điều sau đều đúng:

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

  • Thiết bị phần cứng không cần trình tự tắt hoặc chuyển sang trạng thái yên tĩnh.

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

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

  • Triển khai thao tác trình điều khiển remove() trong cùng một 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().

Tắt rõ ràng tính năng huỷ liên kết trình điều khiển thiết bị (không nên làm)

Khi chọn tắt hẳn tính năng huỷ liên kết trình điều khiển thiết bị, bạn cần cấm huỷ liên kết cấm huỷ tải mô-đun.

  • Để không cho phép huỷ 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; chế độ cài đặt này sẽ ngăn các tệp bindunbind xuất hiện trong thư mục sysfs của trình điều khiển. Tệp unbind là tệp cho phép không gian người dùng kích hoạt việc huỷ liên kết trình điều khiển khỏi thiết bị.

  • Để không cho phép huỷ tải mô-đun, hãy đảm bảo mô-đun có [permanent] trong lsmod. Khi không sử dụng module_exit() hoặc module_XXX_driver(), mô-đun sẽ đượ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 được tải chương trình cơ sở từ trong hàm .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 hệ thống tệp dựa trên bộ nhớ flash hoặc bộ nhớ cố định được gắn. Trong những trường hợp như vậy, API request_firmware*() có thể 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 phần mềm khi ứng dụ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ị đang mở.

Bạn có thể sử dụng .probe() để tải phần mềm trong một số trường hợp, chẳng hạn như trong trình điều khiển đồng hồ cần phần mềm để hoạt động nhưng thiết bị không hiển thị 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.

Triển khai tính năng 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 tính năng nâng cao 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, tính năng này 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 phương pháp tối ưu hoá đó.

Để đánh dấu một 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 phần struct device_driver của trình điều khiển. Ví dụ sau đây cho thấy tính năng hỗ trợ như vậy được bậ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 lưu ý những điều sau khi thêm tính năng hỗ trợ thăm dò không đồng bộ.

  • Đừng giả định về các phần 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 trong hàm thăm dò của thiết bị mẹ, đừng giả định rằng thiết bị con sẽ được thăm dò ngay lập tức.

  • Nếu một đầu dò không thành công, hãy xử lý lỗi thích hợp và dọn dẹp (xem phần 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ột mô-đun tải, quy trình thăm dò thiết bị có thể bị trì hoãn vì một trong các nhà cung cấp chưa sẵn sàng. Điều này có thể dẫn đến việc không khớp giữa thứ tự tải mô-đun và thứ tự thăm dò thiết bị.

  • Một trình điều khiển, nhiều thiết bị. 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 thực thể của một loại thiết bị và mỗi thiết bị đó có một yêu cầu về thứ tự thăm dò khác nhau, thì bạn không thể tuân thủ các yêu cầu đó bằng cách sử dụng thứ tự tải mô-đun.

  • Tìm lỗi không đồng bộ. Các mô-đun trình điều khiển thực hiện hoạt động thăm dò không đồng bộ 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 sẽ 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ột mô-đun trình điều khiển chính I2C thực hiện thăm dò không đồng bộ và một 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ự, bạn có thể thử đầu dò của trình điều khiển cảm ứng 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 các mô-đun đó để không sử dụng hàm đó. Để giúp bạn, nhóm Android đã thực hiện các thay đổi ngược dòng cho phép hạt nhân 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 dùng của một thiết bị đã thăm dò) sử dụng lệnh 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 sẽ 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. Sau đây là những điểm khác biệt:

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

  • #ifdef CONFIG_XXX đánh giá thành true khi CONFIG_XXX được đặt thành tích hợp sẵn (=y) nhưng không đánh giá khi CONFIG_XXX được đặt thành mô-đun (=m). Chỉ sử dụng phương thức này khi bạn chắc chắn rằng bạn muốn làm như vậy khi cấu hình được đặt thành mô-đun hoặc bị tắt.

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

Nếu bạn đặt CONFIG_XXX 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 do CONFIG_XXX kiểm soát 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, đừng sử dụng #ifdef CONFIG_XXX_MODULE vì tệp này gây ra sự hạn chế không cần thiết và bị gián đoạn 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 phải 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 nào không phải là tiêu đề) có đang được biên dịch trong một mô-đun hay không, hãy sử dụng #ifdef MODULE (không có tiền tố CONFIG_).

  • Trong tệp tiêu đề, quy trình kiểm tra tương tự sẽ khó khăn hơn vì 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 dưới dạng một phần của tệp C (hoặc các tệp nguồn khác). Hãy 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 đang sử dụng tệp tiêu đề đó. Đ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ã được biên dịch cho các tệp nguồn khác nhau (mô-đun so với tích hợp sẵn hoặc bị tắt). Điều này có thể hữu ích khi bạn muốn định nghĩa một macro cần mở rộng theo một cách cho mã tích hợp và mở rộng theo một cách khác cho mô-đun.

    • Đối với tệp tiêu đề cần biên dịch trong 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 có bao gồm mô-đun đó hay không), tệp tiêu đề phải sử dụng #ifdef CONFIG_XXX_MODULE.