目次
2.1 I2C コントローラデバイス -- I2C コントローラもカーネル内のデバイスとみなされます
2.3 platform_driver 構造体のプローブ関数は何をしますか?
2.3.1 質問: i2cdev_notifier_call 関数はどこから来たのですか?
2.3.4 質問: platform_bus_type および I2c_bus_type に関する問題
2.3.5 質問: match 関数とプローブ関数が最終的に i2c_imx_probe 関数で呼び出されるのはなぜですか?
4.3.1 質問: at24_probe に一致とプローブがあるのはなぜですか
5.1 i2c-tools が便利なツールのセットである理由
5.2 i2c-tools もサンプル コードのセットであるのはなぜですか?
8. ファインマン学習法: そこで、I2C サブシステム ドライバー フレームワークを説明する学習ビデオを録画しました。
1 I2Cドライバー全体フレームワーク図
上の図は I2C システムの全体的な枠組みであり、次のように紹介されています。
- 最上位層はアプリケーション層で、ユーザーはオープンな読み取りと書き込みを直接使用してデバイスを操作できます。
- 次に、I2C バスを使用して SOC に接続される一部のセンサーや EEPROM ドライバーなどの周辺機器であるデバイス ドライバー層ですが、これは通常、一般のドライバー エンジニアが担当します。
- さらにその下にある I2C コアはコア層であり、これは Linux カーネル ソース コードにすでに含まれており、主にドライバーとデバイスの登録関数と i2c_transfer 関数が含まれています。
- 次は I2C コントローラ ドライバです。これは通常、元のチップ工場のプログラマによって作成されます。
- 次に、特定のハードウェアが登場します。
上の図は I2C ドライバー ソフトウェア フレームワークであり、次のように紹介されています。
- まず、一番右側は I2C デバイス ドライバーで、i2c-client と i2c-driver に分かれており、i2c デバイス ドライバーは i2c_bus_type にマウントされ、i2c-client はデバイス ツリー ファイルから取得され、 of_i2c_register_devices(adap); function.client、そしてバスのデバイス リストに追加され、登録関数を通じて i2c_driver 構造体がバスのドライバ リストに追加されます。ドライバまたはデバイスが追加されると、バスの mach 関数が呼び出されます。マッチングのために、ドライバーのプローブ関数が呼び出されます。プローブ関数に構造体を追加すると、この構造体にはデバイスの読み取りおよび書き込み関数が含まれます。
- 一番左は I2C コントローラー ドライバーで、デバイス ツリーの i2c ノードが platform_device に変換され、platform_bus_type のデバイス リストに追加されます。さらに、platform_driver ドライバー構造体があり、これが platform_bus_type のドライバー リストに登録されます。 platform_bus_type を指定し、デバイスとドライバーを追加すると、platform_match 関数が呼び出され、一致した後、platform_driver ドライバーの i2x_imx_probe 関数が呼び出されます。
- 中央は、i2x_imx_probe 関数で行われる作業です。この関数は、最初に device_register を呼び出して、アダプタを i2c_bus_type のデバイス構造に追加します。platform_bus_type ではなく、i2c_bus_type であることに注意してください。アダプタには、アルゴリズム メンバーが含まれています。このアルゴリズムには、master_xfer 関数が含まれています。 i2c- コアの i2c_transfer 関数は、呼び出されるアルゴリズムの master_xfer 関数であり、その後、i2x_imx_probe 関数も of_i2c_register_device を呼び出して i2c-client を追加します。
ファインマン学習法によれば、知識ポイントを口に出すことで知識ポイントの理解が深まるため、I2C サブシステムでビデオを録画し、ステーション B に送信しました。
Linux カーネル I2C driver_bilibili_bilibili の全体的なフレームワークを 8 分で説明
上記は i2c ドライバーの全体的な紹介であり、以下では i2c コントローラー、i2c コア、および i2c デバイス ドライバーの関連内容を紹介します。
2 I2C コントローラー
2.1 I2C コントローラデバイス -- I2C コントローラもカーネル内のデバイスとみなされます
まず i2c コントローラー デバイスを確認します。./Linux-4.9.88/arch/arm/boot/dts/imx6ull.dtsi デバイス ツリー ファイルに i2c ノードが表示されます。
i2c1: i2c@021a0000 { #address-cells = <1>; #size-cells = <0>; 互換性 = "fsl,imx6ul-i2c", "fsl,imx21-i2c"; reg = <0x021a0000 0x4000>; 割り込み= <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>; Clocks = <&clks IMX6UL_CLK_I2C1>; status = "disabled"; #実際に使用する場合はここを「ok」に変更してください。 };
of_platform_default_populate(NULL, NULL,parent); この関数は、I2C ノードを platform_device に変換し、デバイスを追加します。具体的な関数呼び出し関係は次のとおりです。
of_platform_default_populate(NULL, NULL, 親);
of_platform_populate(root, of_default_bus_match_table, lookup,parent);
of_find_node_by_path("/")//デバイス ツリーのルート ノードを検索します
of_platform_bus_create//この関数は周期的に呼び出されます
of_platform_device_create_pdata
of_device_alloc(np, バス ID, 親);
of_device_add(dev)
device_add(&ofdev->dev);
バス_追加_デバイス(dev);
klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);リンクされたリストに追加
Blocking_notifier_call_chain(&dev->bus->p->bus_notifier,调用bus_notifier
i2cdev_notifier_call
i2cdev_attach_adapter
if (dev->type != &i2c_adapter_type)// device_create を呼び出さずに直接返します
0を返します。
device_create//i2c-%d ノードを追加バス_プローブ_デバイス(dev);
デバイス_初期_プローブ(dev);
__device_attach(dev, true);
Bus_for_each_drv(dev->bus, NULL, &data, __device_attach_driver);
__device_attach_driver
driver_match_device(drv, dev);
drv->バス->一致? drv->bus->match(dev, drv) : 1;
driver_probe_device(drv, dev);
real_probe(dev, drv);
dev->バス->プローブ(dev);またはdrv->プローブ(dev)
最後に、platform_driver 構造体の i2c_imx_probe 関数を呼び出します。i2c_imx_probe 関数は後で分析されます。ここでは、platform_device デバイスが最初に表示されます。
static struct platform_driver i2c_imx_driver = {
.probe = i2c_imx_probe,
.remove = i2c_imx_remove,
.driver = {
.name = DRIVER_NAME,
.pm = I2C_IMX_PM_OPS,
.of_match_table = i2c_imx_dt_ids,
},
.id_table = imx_i2c_devtype,
};
2.2 i2c コントローラードライバー
対応するドライバー ファイルは、前の i2c1 ノードの互換性のある属性値を通じて Linux ソース コードで見つけることができます。ここでの i2c1 ノードの互換性のある属性値は 2 つあります:「fsl,imx6ul-i2c」と「fsl,imx21-i2c」。Linux ソース コードでこれら 2 つの文字列を検索して、対応するドライバー ファイルを見つけます。I.MX6U の I2C アダプタ ドライバ ファイルは drivers/i2c/busses/i2c-imx.c です。上記の i2c コントローラ デバイスは最終的に platform_device に変換されるため、i2c コントローラ ドライバも platform_driver を使用し、platform_bus_type にマウントされます。
ドライバーを調べるときは、エントリー関数から始めます。drivers/i2c/busses/i2c-imx.c ファイルに i2c_adap_imx_init 関数があります。まず、platform_driver_register(&i2c_imx_driver) を呼び出して、i2c_imx_driver 構造体を登録します。特定の関数呼び出し関係は次のとおりであり、一致した場合、関数はドライバーがデバイスと一致することを検出すると、ドライバー内のプローブ関数 (i2c_imx_probe 関数) を呼び出します。
platform_driver_register(&i2c_imx_driver);
__platform_driver_register(drv, THIS_MODULE)
drv->driver.owner = 所有者;
drv->driver.bus = &platform_bus_type;
drv->driver.probe = platform_drv_probe;
drv->driver.remove = platform_drv_remove;
drv->driver.shutdown = platform_drv_shutdown;
driver_register(&drv->driver);
バス_追加_ドライバー(drv);
klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);ドライバーを klist_driver に入れます
ドライバー_アタッチ(drv);
bus_for_each_dev(drv->バス、NULL、drv、__driver_attach);
__driver_attach
driver_match_device(drv, dev);
drv->バス->一致? drv->bus->match(dev, drv) : 1;
driver_probe_device(drv, dev);
ret = real_probe(dev, drv);
dev->バス->プローブ(dev);またはdrv->プローブ(dev)
2.3 platform_driver 構造体のプローブ関数は何をしますか?
新しいデバイスまたはドライバーが追加されると、バスの match 関数が呼び出され、match 関数は互換性のある属性値または名前に従ってデバイスとドライバーを照合します。
* Platform device IDs are assumed to be encoded like this:
* "<name><instance>", where <name> is a short description of the type of
* device, like "pci" or "floppy", and <instance> is the enumerated
* instance of the device, like '0' or '42'. Driver IDs are simply
* "<name>". So, extract the <name> from the platform_device structure,
* and compare it against the name of the driver. Return whether they match
* or not.
*/
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);
/* When driver_override is set, only bind to the matching driver */
if (pdev->driver_override)
return !strcmp(pdev->driver_override, drv->name);
/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
return 1;
/* Then try ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;
/* Then try to match against the id table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);
}
マッチング後、ドライバー構造体のプローブ関数が呼び出されます 次に、struct platform_driver i2c_imx_driver 構造体の i2c_imx_probe 関数の動作を見てみましょう。
static struct platform_driver i2c_imx_driver = {
.probe = i2c_imx_probe,
.remove = i2c_imx_remove,
.driver = {
.name = DRIVER_NAME,
.pm = I2C_IMX_PM_OPS,
.of_match_table = i2c_imx_dt_ids,
},
.id_table = imx_i2c_devtype,
};
具体的な関数呼び出し関係は次のとおりです。
i2c_imx_probe
i2c_add_numbered_adapter
__i2c_add_numbered_adapter
i2c_register_adapter
device_register(&adap->dev);
デバイス追加(dev);
バス_追加_デバイス(dev);
klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);リンクされたリストに追加
Blocking_notifier_call_chain(&dev->bus->p->bus_notifier,调用bus_notifier
i2cdev_notifier_call
i2cdev_attach_adapter
device_create//i2c-%d ノードを追加
バス_プローブ_デバイス(dev);
デバイス_初期_プローブ(dev);
__device_attach(dev, true);
Bus_for_each_drv(dev->bus, NULL, &data, __device_attach_driver);
__device_attach_driver
driver_match_device(drv, dev);
drv->bus->match ? drv->bus->match(dev, drv) : 1;// 一致が失敗した場合は、直接戻ります。
driver_probe_device(drv, dev);上記の一致は失敗したため、ここでは直接呼び出されません。
real_probe(dev, drv);
dev->bus->probe(dev); または drv->probe(dev)、of_i2c_register_devices(adap);
of_i2c_register_device(adap, ノード);
i2c_new_device(adap, &info); クライアントの追加に使用されます
client->dev.parent = &client->adapter->dev;
client->dev.bus = &i2c_bus_type;//これは platform_bus ではなく i2c-bus であることに注意してください
client->dev.type = &i2c_client_type;
client->dev.of_node = 情報->of_node;
client->dev.fwnode = 情報->fwnode;
status = device_register(&client->dev);新しい i2c_client デバイスを登録します
デバイス追加(dev);
バス_追加_デバイス(dev);
klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);
バス_プローブ_デバイス
デバイス_初期_プローブ(dev);
__device_attach(dev, true);
各drvのバス
__device_attach_driver
driver_match_device
drv->バス->一致? drv->bus->match(dev, drv) : 1;
driver_probe_device(drv, dev);
real_probe(dev, drv);
dev->バス->プローブ(dev);またはdrv->プローブ(dev)
Bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter)
__process_new_adapter(struct device_driver *d, void *data)
i2c_do_add_adapter(struct i2c_driver *driver, struct i2c_adapter *adap)
i2c_detect(adap, driver); i2c_detect を使用して、バスに接続されている適切なデバイスがあるかどうかを検出します。
if (!driver->detect || !address_list) return 0; detect または address_list が定義されていない場合は、直接返されます。
i2c_detect_address(temp_client, driver);
err = driver->detect(temp_client, &info); 対応するクライアントに従ってテストデータを送信します. 問題がなければ、クライアントがドライバーが必要とするデバイスであることが証明されます. 最後に、デバイスはリンクされたデバイスに追加されますリストを作成し、最後にbus_probe_deviceを呼び出してドライバーをバインドしようとします。
client = i2c_new_device(adapter, &info); クライアントを増やすために使用されます
device_register(&client->dev);
デバイス追加(dev);
バス_追加_デバイス(dev);
klist_add_tail
バス_プローブ_デバイス(dev);
バス_プローブ_デバイス(dev);
デバイス_初期_プローブ(dev);
__device_attach(dev, true);
各drvのバス
__device_attach_driver
driver_match_device(drv, dev);
drv->バス->一致? drv->bus->match(dev, drv) : 1;
driver_probe_device(drv, dev);
real_probe(dev, drv);
dev->バス->プローブ(dev);またはdrv->プローブ(dev)
カーネルコードを見て上記の関数呼び出し処理を理解したのですが、同時に以下のような疑問や疑問も抱きました。
2.3.1 質問: i2cdev_notifier_call 関数はどこから来たのですか?
上記のプロセスで、なぜblocking_notifier_call_chain(&dev->bus->p->bus_notifier、bus_notifierを呼び出すとi2cdev_notifier_callが呼び出されるのか、その理由はここにあります。
static int i2cdev_notifier_call(struct notifier_block *nb, unsigned long action,
void *data)
{
struct device *dev = data;
switch (action) {
case BUS_NOTIFY_ADD_DEVICE:
return i2cdev_attach_adapter(dev, NULL);
case BUS_NOTIFY_DEL_DEVICE:
return i2cdev_detach_adapter(dev, NULL);
}
return 0;
}
static struct notifier_block i2cdev_notifier = {
.notifier_call = i2cdev_notifier_call,
};
static int __init i2c_dev_init(void)
{
...
res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);
...
}
2.3.2 質問: なぜ 2 つのプローブがあるのですか?
platform_driver 構造体にはプローブ関数があります。
static struct platform_driver i2c_imx_driver = {
.probe = i2c_imx_probe,
.remove = i2c_imx_remove,
.driver = {
.name = DRIVER_NAME,
.pm = I2C_IMX_PM_OPS,
.of_match_table = i2c_imx_dt_ids,
},
.id_table = imx_i2c_devtype,
};
しかし、このドライバーを登録するときに、なぜ platform_drv_probe 関数が含まれるのでしょうか?
*/
int __platform_driver_register(struct platform_driver *drv,
struct module *owner)
{
drv->driver.owner = owner;
drv->driver.bus = &platform_bus_type;
drv->driver.probe = platform_drv_probe;
drv->driver.remove = platform_drv_remove;
drv->driver.shutdown = platform_drv_shutdown;
return driver_register(&drv->driver);
}
コードを確認したところ、これは、外側の platform_drv_probe が実際に platform_driver 内のプローブを呼び出しているためであることがわかりました。
static int platform_drv_probe(struct device *_dev)
{
struct platform_driver *drv = to_platform_driver(_dev->driver);
struct platform_device *dev = to_platform_device(_dev);
int ret;
ret = of_clk_set_defaults(_dev->of_node, false);
if (ret < 0)
return ret;
ret = dev_pm_domain_attach(_dev, true);
if (ret != -EPROBE_DEFER) {
if (drv->probe) {
ret = drv->probe(dev); //在这个地方调用了platform_driver的probe函数
if (ret)
dev_pm_domain_detach(_dev, true);
} else {
/* don't fail if just dev_pm_domain_attach failed */
ret = 0;
}
}
if (drv->prevent_deferred_probe && ret == -EPROBE_DEFER) {
dev_warn(_dev, "probe deferral not supported\n");
ret = -ENXIO;
}
return ret;
}
2.3.3 質問: of_i2c_register_devices(adap);とbus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter) の関数は重複していますか?
上記の関数呼び出しプロセスを確認すると、of_i2c_register_devices(adap);関数とbus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter)関数が両方ともi2c_new_deviceを呼び出してi2c-client を追加していることがわかりました。これは関数の重複ではありませんか? よく見てください。コードを読んだ後、of_i2c_register_devices(adap); はデバイス ツリー ノードからデバイス情報を取得し、i2c クライアントを登録し、実際には Bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter) のようにする必要があることがわかりました。i2c_detect を呼び出し、ドライバーで定義された detect 関数と address_list に基づいて、バス上の i2c クライアントを検出します。これは、クライアントを追加するさまざまな方法と同等です。詳細については、カーネルのこのドキュメントを参照してください: いくつかのメソッドLinuxカーネルでの i2c デバイスのインスタンス化について -- --./Linux-4.9.88/Documentation/i2c/instantiating-devices ファイル translation_Chen Honwei のブログ-CSDN ブログ
2.3.4 質問: platform_bus_type および I2c_bus_type に関する問題
関数 i2c_adap_imx_init 内にあることに注意してください。
static int __init i2c_adap_imx_init(void)
{
return platform_driver_register(&i2c_imx_driver);
}
次に、さらに __platform_driver_register を呼び出します。この時点のバスは platform_bus_type です。
int __platform_driver_register(struct platform_driver *drv,
struct module *owner)
{
drv->driver.owner = owner;
drv->driver.bus = &platform_bus_type;
drv->driver.probe = platform_drv_probe;
drv->driver.remove = platform_drv_remove;
drv->driver.shutdown = platform_drv_shutdown;
return driver_register(&drv->driver);
}
ただし、ドライバ内のプローブにアダプタ(コントローラ)を登録する際には、i2c_add_numbered_adapter インターフェースが呼び出され、このときのバスは i2c_bus_type となります。
static int i2c_register_adapter(struct i2c_adapter *adap)
{
...
dev_set_name(&adap->dev, "i2c-%d", adap->nr);
//BUS指向I2C
adap->dev.bus = &i2c_bus_type;
adap->dev.type = &i2c_adapter_type;
res = device_register(&adap->dev);
...
コードをよく見て理解しましたが、実際はこんな感じです デバイスツリーノードの I2C ノードは確かに platform_device に変換されて platform_bus バスにマウントされています そして platform_bus_type の match 関数がデバイスを検出するととドライバーが一致する場合、それはドライバー構造体のプローブ関数と呼ばれ、プローブ関数内でアダプターを構築して追加し、アダプターが i2c_bus_type に追加されます。
2.3.5 質問: match 関数とプローブ関数が最終的に i2c_imx_probe 関数で呼び出されるのはなぜですか?
この i2c_imx_probe 関数は、plarform_bus_type の match 関数がコントローラー ドライバーとコントローラー デバイスが一致することを検出し、それにアダプターを追加するときに呼び出されますが、i2c_imx_probe 関数内のレイヤーに drv->bus-> があるのはなぜですか? match ? drv->bus->match(dev, drv): 1; および dev->bus->probe(dev); または drv->probe(dev) 関数 なぜプローブが再びプローブ内で呼び出されるのですか?内部関数はどうですか?プローブは何に使用されますか?
i2c_imx_probe
i2c_add_numbered_adapter
__i2c_add_numbered_adapter
i2c_register_adapter
device_register(&adap->dev);
デバイス追加(dev);
バス_追加_デバイス(dev);
klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);リンクされたリストに追加
Blocking_notifier_call_chain(&dev->bus->p->bus_notifier,调用bus_notifier
i2cdev_notifier_call
i2cdev_attach_adapter
device_create//i2c-%d ノードを追加
バス_プローブ_デバイス(dev);
デバイス_初期_プローブ(dev);
__device_attach(dev, true);
Bus_for_each_drv(dev->bus, NULL, &data, __device_attach_driver);
__device_attach_driver
driver_match_device(drv, dev);
drv->バス->一致? drv->bus->match(dev, drv) : 1;
driver_probe_device(drv, dev);
real_probe(dev, drv);
dev->バス->プローブ(dev);またはdrv->プローブ(dev)
なぜプローブが入っているのでしょうか?理解できず、難しいです。カーネルコードを見て、i2c_imx_probe 関数の呼び出しプロセスを説明しました。こうあるべきであることがわかりましたが、そうではありません私が理解したことが正しいかどうかは確かです。アダプターを登録する際のバスは i2c_bus_tyupe になりますので、上記のアダプターを追加しても問題ありません。
static int i2c_register_adapter(struct i2c_adapter *adap)
{
...
adap->dev.bus = &i2c_bus_type;
adap->dev.type = &i2c_adapter_type;
res = device_register(&adap->dev);
...
}
次に、drv->bus->match になると、ここではバスは i2c_bus_type であるため、呼び出しは次のようになります。
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
};
それなら、それは
static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
struct i2c_client *client = i2c_verify_client(dev);
struct i2c_driver *driver;
if (!client)
return 0;
/* Attempt an OF style match */
if (of_driver_match_device(dev, drv))
return 1;
/* Then ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;
driver = to_i2c_driver(drv);
/* match on an id table if there is one */
if (driver->id_table)
return i2c_match_id(driver->id_table, client) != NULL;
return 0;
}
ここで追加されるのはアダプターデバイスであるため、(!client) がまったく true ではないため、ここでの一致は 0 になります。
static int __device_attach_driver(struct device_driver *drv, void *_data)
{
struct device_attach_data *data = _data;
struct device *dev = data->dev;
bool async_allowed;
int ret;
/*
* Check if device has already been claimed. This may
* happen with driver loading, device discovery/registration,
* and deferred probe processing happens all at once with
* multiple threads.
*/
if (dev->driver)
return -EBUSY;
ret = driver_match_device(drv, dev);
if (ret == 0) {
/* no match */
return 0;
} else if (ret == -EPROBE_DEFER) {
dev_dbg(dev, "Device match requests probe deferral\n");
driver_deferred_probe_add(dev);
} else if (ret < 0) {
dev_dbg(dev, "Bus failed to match device: %d", ret);
return ret;
} /* ret > 0 means positive match */
async_allowed = driver_allows_async_probing(drv);
if (async_allowed)
data->have_async = true;
if (data->check_async && async_allowed != data->want_async)
return 0;
return driver_probe_device(drv, dev);
}
driver_match_device(drv, dev); 関数は直接 0 を返すため、__device_attach_driver 関数は直接戻り、driver_probe_device(drv, dev); 関数は呼び出されません。
3 i2c コア
I2C デバイスとドライバーのマッチング プロセスは I2C コアによって完了します。drivers/i2c/i2c-core.c は I2C のコア部分です。I2C コアは、前述したような
特定のハードウェアとは関係のないいくつかの API 関数を提供します。 before:
1 , i2c_adapter 登録/登録解除関数
int i2c_add_adapter(struct i2c_adapter *adapter)
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
void i2c_del_adapter(struct i2c_adapter * adap)
2. i2c_driver 登録/登録解除関数
int i2c_register_driver (構造体モジュール *オーナー、構造体 i2c_driver *driver)
int i2c_add_driver (struct i2c_driver *driver)
void i2c_del_driver (struct i2c_driver *driver)
デバイスとドライバー間のマッチング プロセスも I2C バスによって完了します。I2C バスのデータ構造は i2c_bus_type であり、次のように定義されます
。 drivers/i2c/i2c-core.c ファイル。
さらに、i2c-core には i2c_transfer 関数があり、その i2c_transfer 関数がデバイス ドライバーで直接データを送信するために使用され、この i2c_transfer 関数が最終的にアダプター内のアルゴリズムで master_xfer 関数を呼び出していることもわかります。ここから、i2c-core が役割を果たすのは、デバイス ドライバーとコントローラー ドライバーを接続する、前と次の間のリンクです。
4 i2c デバイス
4.1 i2c クライアント
i2c-client はデバイス ツリー ファイルから取得され、通常は、以下の ap3216 デバイスなど、i2c ノードの子ノードに配置されます。
&i2c1 { ap3216c@1e { possible = "lite-on,ap3216c"; reg = <0x1e>; };/*i2c の子ノードは i2c デバイスを表すために使用されます*/ };
&i2c1 { クロック周波数 = <100000>; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_i2c1>; status = "okay"; };/*これは、i2c デバイスではなく、i2c コントローラーを表すために使用されます * /
i2c バス ノードの下の子ノードは platform_device に変換されません。それらは I2C バス ドライバーによって処理されます。I2C の下のデバイス ノードのクライアントへの変換は、実際には i2c コントローラー ドライバーのプローブ関数によって行われます。プローブ関数の内部処理を解析すると、中間部分の of_i2c_register_devices 関数を使用して i2c-client を追加します。
i2c_imx_probe
i2c_add_numbered_adapter
__i2c_add_numbered_adapter
i2c_register_adapter
device_register(&adap->dev);
デバイス追加(dev);
バス_追加_デバイス(dev);
klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);リンクされたリストに追加
Blocking_notifier_call_chain(&dev->bus->p->bus_notifier,调用bus_notifier
i2cdev_notifier_call
i2cdev_attach_adapter
device_create//i2c-%d ノードを追加
バス_プローブ_デバイス(dev);
デバイス_初期_プローブ(dev);
__device_attach(dev, true);
Bus_for_each_drv(dev->bus, NULL, &data, __device_attach_driver);
__device_attach_driver
driver_match_device(drv, dev);
drv->バス->一致? drv->bus->match(dev, drv) : 1;
driver_probe_device(drv, dev);
real_probe(dev, drv);
dev->バス->プローブ(dev);またはdrv->プローブ(dev)
of_i2c_register_devices(adap);
of_i2c_register_device(adap, ノード);
i2c_new_device(adap, &info); クライアントの追加に使用されます
client->dev.parent = &client->adapter->dev;
client->dev.bus = &i2c_bus_type;//これは platform_bus ではなく i2c-bus であることに注意してください
client->dev.type = &i2c_client_type;
client->dev.of_node = 情報->of_node;
client->dev.fwnode = 情報->fwnode;
status = device_register(&client->dev);新しい i2c_client デバイスを登録します
デバイス追加(dev);
バス_追加_デバイス(dev);
klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);
バス_プローブ_デバイス
デバイス_初期_プローブ(dev);
__device_attach(dev, true);
各drvのバス
__device_attach_driver
driver_match_device
drv->バス->一致? drv->bus->match(dev, drv) : 1;
driver_probe_device(drv, dev);
real_probe(dev, drv);
dev->バス->プローブ(dev);またはdrv->プローブ(dev)
Bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter)
__process_new_adapter(struct device_driver *d, void *data)
i2c_do_add_adapter(struct i2c_driver *driver, struct i2c_adapter *adap)
i2c_detect(adap, driver); i2c_detect を使用して、バスに接続されている適切なデバイスがあるかどうかを検出します。
if (!driver->detect || !address_list) return 0; detect または address_list が定義されていない場合は、直接返されます。
i2c_detect_address(temp_client, driver);
err = driver->detect(temp_client, &info); 対応するクライアントに従ってテストデータを送信します. 問題がなければ、クライアントがドライバーが必要とするデバイスであることが証明されます. 最後に、デバイスはリンクされたデバイスに追加されますリストを作成し、最後にbus_probe_deviceを呼び出してドライバーをバインドしようとします。
client = i2c_new_device(adapter, &info); クライアントを増やすために使用されます
device_register(&client->dev);
デバイス追加(dev);
バス_追加_デバイス(dev);
klist_add_tail
バス_プローブ_デバイス(dev);
バス_プローブ_デバイス(dev);
デバイス_初期_プローブ(dev);
__device_attach(dev, true);
各drvのバス
__device_attach_driver
driver_match_device(drv, dev);
drv->バス->一致? drv->bus->match(dev, drv) : 1;
driver_probe_device(drv, dev);
real_probe(dev, drv);
dev->バス->プローブ(dev);またはdrv->プローブ(dev)
4.2 i2c ドライバー
i2c_driver はこのバス構造を使用します
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
};
まずエントリ関数 module_init(at24_init); を見てください、これは i2c_add_driver(&at24_driver); を呼び出し、次に i2c_register_driver(THIS_MODULE, driver) を呼び出し、次に driver_register(&driver->driver); を呼び出し、次にそれを Bus_add_driver(drv); 内で呼び出します。次に、 driver_attach(drv) の呼び出しを続けます; 次に、bus_for_each_dev(drv->bus, NULL, drv, __driver_attach) の呼び出しを続けます; この関数は、デバイスごとに __driver_attach 関数を呼び出し、次に進みます __driver_attach 関数は、2 つの重要な関数があることを発見しました初期化
- driver_match_device(drv, dev);
- driver_probe_device(drv, dev);
driver_match_device(drv, dev); はさらに、i2c_bus_type の match 関数である drv->bus->match(dev, drv) を呼び出します。
driver_probe_device(drv, dev); これはさらに real_probe(dev, drv); を呼び出し、さらに dev->bus->probe(dev); を呼び出します。これは、i2c_bus_type のプローブ関数です。
i2c_add_driver(&at24_driver)
i2c_register_driver(THIS_MODULE、ドライバー)
driver_register(&driver->driver)
バス追加ドライバー(drv)
klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers) はドライバーを klist_driver に置きます
ドライバー_アタッチ(drv)
bus_for_each_dev(drv->バス、NULL、drv、__driver_attach);
__driver_attach(struct device *dev, void *data)
driver_match_device(drv, dev);
drv->バス->一致? drv->bus->match(dev, drv) : 1;
driver_probe_device(drv, dev);
real_probe(dev, drv);
dev->バス->プローブ(dev);またはdrv->プローブ(dev)
/* すでに存在するアダプターをウォークします */
i2c_for_each_dev(driver, __process_new_driver);//
__process_new_driver //次のコードは呼び出されず、ここから直接返されます。
i2c_do_add_adapter(struct i2c_driver *driver,struct i2c_adapter *adap)
i2c_detect(adap, ドライバー);
if (!driver->detect || !address_list) return 0; detect または address_list が定義されていない場合は、直接戻ります
i2c_detect_address(temp_client, driver);
err = driver->detect(temp_client, &info);
client = i2c_new_device(adapter, &info);
device_register(&client->dev);
デバイス追加(dev);
バス_追加_デバイス(dev);
klist_add_tail
バス_プローブ_デバイス(dev);
バス_プローブ_デバイス(dev);
デバイス_初期_プローブ(dev);
__device_attach(dev, true);
各drvのバス
__device_attach_driver
driver_match_device(drv, dev);
drv->バス->一致? drv->bus->match(dev, drv) : 1;
driver_probe_device(drv, dev);
real_probe(dev, drv);
dev->バス->プローブ(dev);またはdrv->プローブ(dev)
4.2.1 質問: i2c_register_driver 関数では、ドライバーを追加するには driver_register(&driver->driver); 関数を呼び出すだけで十分ですが、なぜ i2c_for_each_dev(driver, __process_new_driver); とも呼ばれるのでしょうか。
i2c_add_driver(&at24_driver) 関数を見ていると、実際には driver_register 関数を呼び出すことでドライバの登録作業が完了していることがわかり、次に、何のために i2c_for_each_dev(driver, __process_new_driver); も呼び出したところ、この関数の内部が変わりました。コードをもう一度見てみると、__process_new_driver 関数が呼び出されていなかったことがわかりました。
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
int res;
/* Can't register until after driver model init */
if (WARN_ON(!is_registered))
return -EAGAIN;
/* add the driver to the list of i2c drivers in the driver core */
driver->driver.owner = owner;
driver->driver.bus = &i2c_bus_type;
INIT_LIST_HEAD(&driver->clients);
/* When registration returns, the driver core
* will have called probe() for all matching-but-unbound devices.
*/
res = driver_register(&driver->driver);
if (res)
return res;
pr_debug("driver [%s] registered\n", driver->driver.name);
/* Walk the adapters that are already present */
i2c_for_each_dev(driver, __process_new_driver);
return 0;
}
その理由はここにあるのですが、
static int __process_new_driver(struct device *dev, void *data)
{
if (dev->type != &i2c_adapter_type)
return 0;
return i2c_do_add_adapter(data, to_i2c_adapter(dev));
}
ここで if (dev->type != &i2c_adapter_type) の判定があるため、後続の関数は一切呼び出されず、問題は解決されます。
4.3 プローブ機能は何をするのですか?
デバイスまたはドライバーが追加されると、i2c_bus_type の match 関数が呼び出されます。match の後、ドライバーのプローブ関数が呼び出されます。ドライバーのプローブ関数が何をするかを見てみましょう。
at24_プローブ
....
at24->nvmem_config.name = dev_name(&client->dev);
at24->nvmem_config.dev = &client->dev;
at24->nvmem_config.read_only = !書き込み可能;
at24->nvmem_config.root_only = true;
at24->nvmem_config.owner = THIS_MODULE;
at24->nvmem_config.compat = true;
at24->nvmem_config.base_dev = &client->dev;
at24->nvmem_config.reg_read = at24_read;//関数の読み取り
at24->nvmem_config.reg_write = at24_write;//書き込み関数
at24->nvmem_config.priv = at24;
at24->nvmem_config.stride = 1;
at24->nvmem_config.word_size = 1;
at24->nvmem_config.size = チップ.バイト_レン;
at24->nvmem = nvmem_register(&at24->nvmem_config);
....
nvmem->id = rval;
nvmem->所有者 = 構成->所有者;
nvmem->stride = config->stride;
nvmem->word_size = config->word_size;
nvmem->size = config->size;
nvmem->dev.type = &nvmem_provider_type;
nvmem->dev.bus = &nvmem_bus_type;//ここに注目してください。
nvmem->dev.parent = config->dev;
nvmem->priv = config->priv;
nvmem->reg_read = config->reg_read;
nvmem->reg_write = config->reg_write;
np = config->dev->of_node;
nvmem->dev.of_node = np;
rval = device_add(&nvmem->dev);//この device_add 関数はこれまでに何度も見たことがありますが、同じものにすぎません。
バス_追加_デバイス(dev);
バス_プローブ_デバイス(dev);
デバイス_初期_プローブ(dev);
__device_attach(dev, true);
Bus_for_each_drv(dev->bus, NULL, &data,__device_attach_driver);
__device_attach_driver
driver_match_device(drv, dev);
return drv->bus->match? drv->bus->match(dev, drv) : 1;
driver_probe_device(drv, dev);
driver_probe_device(drv, dev);
real_probe(dev, drv);
dev->バス->プローブ(dev);またはdrv->プローブ(dev)
4.3.1 質問: at24_probe に一致とプローブがあるのはなぜですか
私の理解では、at24_probe 関数は file_operation 構造体の実装に似ている必要があり、その後に特定の読み取り関数と書き込み関数があるということですが、それだけでは十分ではないでしょうか? しかし、上記のプロセスから、at24_probe 関数はまた、 match 関数とプローブ関数。OK、続けてください。カーネル コードを見ると、私の混乱は解決します。。。。。。。。
まずは見てみる
static struct bus_type nvmem_bus_type = {
.name = "nvmem",
};
次に、ここでは match 関数が定義されていないことがわかったので、match 関数は空で、 return drv->bus->match? drv->bus->match(dev, drv): 1; Return 1 を直接実行してから、呼び出し driver_probe_device( drv, dev); 関数、
static int really_probe(struct device *dev, struct device_driver *drv)
{
...
if (dev->bus->probe) {
ret = dev->bus->probe(dev);
if (ret)
goto probe_failed;
} else if (drv->probe) {
ret = drv->probe(dev);
if (ret)
goto probe_failed;
}
...
}
ここでの dev->bus->probe 呼び出しが空の場合、ドライバー構造のプローブ関数が呼び出されます。その後、カーネル コード内で nvmem ドライバー構造を探しますが、見つかりません。その後、Bing AI に助けを求めます。 。
それで
struct nvmem_device {
const char *name;
struct module *owner;
struct device dev;
int stride;
int word_size;
int ncells;
int id;
int users;
size_t size;
bool read_only;
int flags;
struct bin_attribute eeprom;
struct device *base_dev;
nvmem_reg_read_t reg_read;
nvmem_reg_write_t reg_write;
void *priv;
};
そこにはプローブ関数がないため、else if (drv->probe) も無効です。
ここまでで、I2C ドライバのフレームワークが実際に完成しましたので、その他 I2C ドライバに関連するものを簡単に紹介します。
5 i2c ツール
i2c-tools は、便利なツールとサンプル コードのセットです。
5.1 i2c-tools が便利なツールのセットである理由
i2c-tools が便利なツールのセットである理由は、i2cdetect 検出機能、i2cget 読み取り機能、i2cset 書き込み機能、および i2ctransfer 送信機能が実装されているためです。これらのコマンドを使用して、次のような I2C デバイスを操作またはデバッグできます。
5.2 i2c-tools もサンプル コードのセットであるのはなぜですか?
i2c-tools がサンプル コードのセットでもあるのはなぜですか? たとえば、送信に I2C バスを使用する場合、./tools/i2ctransfer.c でそのコード実装を確認できます。
その後、彼のプロセスを模倣して独自の I2C デバイスを操作できます。set_slave_addr などの上記の関数の具体的な実装は ./tools/i2cbusses.c にあり、コードを記述するときに ./tools/i2cbusses.c ファイルをインクルードする必要があります。
SMBus バスを使用して送信する場合、i2cget.c および i2cset.c のサンプルコードは次のとおりです。
次に、SMBus バスを使用して i2c デバイスを操作したい場合は、彼のコードを模倣することができます。たとえば、上記の i2c_smbus_access 関数の具体的な実装は ./lib/smbus.c ファイルにあるため、コードを記述するときに./lib/smbus.c ファイルを含める必要があります。
たとえば、EEPROM の読み書きを行うテスト プログラムを作成します。
#include <sys/ioctl.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <i2c/smbus.h>
#include "i2cbusses.h"
#include <time.h>
/* ./at24c02 <i2c_bus_number> w "100ask.taobao.com"
* ./at24c02 <i2c_bus_number> r
*/
int main(int argc, char **argv)
{
unsigned char dev_addr = 0x50;
unsigned char mem_addr = 0;
unsigned char buf[32];
int file;
char filename[20];
unsigned char *str;
int ret;
struct timespec req;
if (argc != 3 && argc != 4)
{
printf("Usage:\n");
printf("write eeprom: %s <i2c_bus_number> w string\n", argv[0]);
printf("read eeprom: %s <i2c_bus_number> r\n", argv[0]);
return -1;
}
file = open_i2c_dev(argv[1][0]-'0', filename, sizeof(filename), 0);
if (file < 0)
{
printf("can't open %s\n", filename);
return -1;
}
if (set_slave_addr(file, dev_addr, 1))
{
printf("can't set_slave_addr\n");
return -1;
}
if (argv[2][0] == 'w')
{
// write str: argv[3]
str = argv[3];
req.tv_sec = 0;
req.tv_nsec = 20000000; /* 20ms */
while (*str)
{
// mem_addr, *str
// mem_addr++, str++
ret = i2c_smbus_write_byte_data(file, mem_addr, *str);
if (ret)
{
printf("i2c_smbus_write_byte_data err\n");
return -1;
}
// wait tWR(10ms)
nanosleep(&req, NULL);
mem_addr++;
str++;
}
ret = i2c_smbus_write_byte_data(file, mem_addr, 0); // string end char
if (ret)
{
printf("i2c_smbus_write_byte_data err\n");
return -1;
}
}
else
{
// read
ret = i2c_smbus_read_i2c_block_data(file, mem_addr, sizeof(buf), buf);
if (ret < 0)
{
printf("i2c_smbus_read_i2c_block_data err\n");
return -1;
}
buf[31] = '\0';
printf("get data: %s\n", buf);
}
return 0;
}
6 i2c_dev.c ユニバーサルドライバー
i2c_dev.c は実際にはユニバーサル ドライバーまたはユニバーサル ドライバーであり、
static const struct file_operations i2cdev_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.read = i2cdev_read,
.write = i2cdev_write,
.unlocked_ioctl = i2cdev_ioctl,
.open = i2cdev_open,
.release = i2cdev_release,
};
ユニバーサルドライバー i2c_dev.c を使用すると、i2c_client と i2c_driver を追加する必要がなく、アプリケーション層で i2c コントローラーを直接操作して、I2C バスにマウントされたスレーブデバイスと通信できます。動作と同等 特定のハードウェア タイミングがアプリケーションに実装されるため、アプリケーション開発者は特定のハードウェア動作タイミングと I2C バス プロトコルの両方を理解する必要があります。赤い線画の方向です
7 GPIO アナログ I2C
./Linux-4.9.88_just_for_read/drivers/i2c/busses/i2c-gpio.c ファイルを簡単に見て、エントリ関数から始めましょう。
static struct platform_driver i2c_gpio_driver = {
.driver = {
.name = "i2c-gpio",
.of_match_table = of_match_ptr(i2c_gpio_dt_ids),
},
.probe = i2c_gpio_probe,
.remove = i2c_gpio_remove,
};
static int __init i2c_gpio_init(void)
{
int ret;
ret = platform_driver_register(&i2c_gpio_driver);
if (ret)
printk(KERN_ERR "i2c-gpio: probe failed: %d\n", ret);
return ret;
}
関数呼び出し関係は同じものに過ぎません
i2c_gpio_init
platform_driver_register
__platform_driver_register
drv->driver.owner = 所有者;
drv->driver.bus = &platform_bus_type;
drv->driver.probe = platform_drv_probe;
drv->driver.remove = platform_drv_remove;
drv->driver.shutdown = platform_drv_shutdown;
driver_register(&drv->driver);
バス追加ドライバー
ドライバー_アタッチ(drv);
__driver_attach
driver_match_device(drv, dev);
drv->バス->一致? drv->bus->match(dev, drv) : 1;
driver_probe_device(drv, dev);
real_probe(dev, drv);
dev->bus->probe または drv->probe(dev)
一致後、ドライバー構造内の i2c_gpio_probe 関数が呼び出され、次に of_i2c_gpio_get_props 関数が最初に呼び出されて、デバイス ツリーから GPIO 情報といくつかの属性、つまり周波数とオープン ドレイン設定を取得し、次に sda ピンとscl ピンを取得し、デバイス ツリーで取得した値を使用してアダプターを設定し、i2c_bit_add_numbered_bus を使用してアダプターを登録し、アルゴ アルゴリズムが設定されている i2c_bit_add_numbered_bus の __i2c_bit_add_bus を呼び出してから、add_adapter を呼び出します。
i2c_gpio_probe
of_i2c_gpio_get_pins
devm_gpio_request(&pdev->dev, sda_pin, "sda");
devm_gpio_request(&pdev->dev, scl_pin, "scl");
i2c_bit_add_numbered_bus(adap);
__i2c_bit_add_bus(adap, i2c_add_numbered_adapter);
adapt->something = &i2c_bit_something;
適応 -> 再試行 = 3;
if (bit_adap->getscl == NULL)
adap->quirks = &i2c_bit_quirk_no_clk_stretch;
ret = add_adapter(adap);//add_adapterつまりi2c_add_numbered_adapter
後続の呼び出しについては調べませんが、同様の呼び出しはこれまでに何度も分析しました。
8. ファインマン学習法: そこで、I2C サブシステム ドライバー フレームワークを説明する学習ビデオを録画しました。
Linux カーネル I2C driver_bilibili_bilibili の全体的なフレームワークを 8 分で説明
参考文献:
時間厳守の Atomic ドライバー開発マニュアル
魏東山先生によるドライバー開発に関する包括的な学習ビデオ
Linux4.9.88カーネルソースコード
7. プラットフォーム デバイス ドライバー - [Wildfire] 組み込み Linux ドライバー開発の実践ガイド - i.MX6ULL シリーズのドキュメントに基づく
I2C ドライバー実装の 2 つのアイデア (i2c-dev.c および i2c-core.c)
https://www.cnblogs.com/happybirthdaytoyou/p/13594060.html
[I2C] 一般ドライバー i2c-dev Analysis_i2c_dev_init_ZHONGCAI0901 のブログ - CSDN ブログ
LinuxカーネルのI2Cサブシステムを詳しく解説 - この記事を読めば十分 - プログラマーが求めた
https://www.cnblogs.com/burnk/p/17454052.html
Linux I2C ソフトウェア アーキテクチャを理解するには 10 分_哔哩哔哩_bilibili
I2C——i2c_driver 登録とプローブ検出関数呼び出し process_i2c プローブ_lxllinux のブログ-CSDN ブログ
カーネルによるデバイス ツリー __device_node の処理は、platform_device_initcall_from_entry_Chen Honwei のブログ - CSDN ブログ https://www.cnblogs.com/schips/p/linux_driver_device_node_to_platform_device.htmlに変換されます。
Linux デバイス モデルの device_add - プログラマーが求めた
https://www.cnblogs.com/yangjiguang/p/6220600.html
I2c デバイスの追加、ドライバーの読み込みとデバイスのマッチング_Android i2c 心拍数デバイスの追加_bruk_spp のブログ - CSDN ブログ