So erstellen Sie einen eingebetteten Linux-Gerätetreiber (10)

10. Erstellen Sie einen Gerätetreiber mit I2C

Über diese Serie

HowTo-Artikel zum Entwickeln eingebetteter Linux-Gerätetreiber als Kernelmodule. Der gesamte Inhalt dieses Artikels kann auf Raspberry Pi ausgeführt werden.

Der gesamte Quellcode, der in diesem Artikel angezeigt wird

https://github.com/take-iwiw/DeviceDriverLesson/tree/master/10_01 https://github.com/take-iwiw/DeviceDriverLesson/tree/master/10_02 https://github.com/take-iwiw/DeviceDriverLesson/tree/master/10_03 https://github.com/take-iwiw/DeviceDriverLesson/tree/master/10_04

Inhalt dieser Zeit

Letztes Mal habe ich einen Gerätetreiber erstellt, der LEDs / Tasten mithilfe von GPIO-Steuerfunktionen steuert. Dieses Mal erstellen wir einen Gerätetreiber für das über I2C verbundene Gerät. Es ist nicht der Gerätetreiber von I2C selbst. Ähnlich wie beim vorherigen GPIO wird das I2C-Gerät selbst von SoC-Herstellern vorbereitet. Und der Gerätetreiber kann über die in <linux / i2c.h> deklarierte Standardfunktion aufgerufen werden. Daher sollten Sie es positiv verwenden. Erhöht die Portabilität, Vielseitigkeit und Zuverlässigkeit.

Das Zielboard ist Raspberry Pi 2. Das Ziel-I2C-Gerät wird als Beschleunigungssensor (LIS3DH) bezeichnet. Dies ist jedoch nicht besonders wichtig, da es nicht dazu gedacht ist, das Gerät selbst zu steuern. Ich benutze einen Beschleunigungssensor, aber diesmal bekomme ich nur die Geräte-ID. Das liegt daran, dass ich nur die Kommunikation überprüfen möchte. keine besondere Bedeutung. Wenn es andere Informationen gibt, die leicht zu überprüfen sind, ist das in Ordnung. Bitte korrigieren Sie nur die folgenden Informationen im Voraus.

Vorbereitungen und Bestätigung (für Raspeltorte)

Bitte aktivieren Sie I2C mit raspi-config. Überprüfen Sie anschließend mit dem folgenden Befehl, ob eine Kommunikation möglich ist. Sie können die Informationen aller an I2C_1 angeschlossenen Geräte mit "i2cdetect" überprüfen. Ich erhalte die Geräte-ID von LIS3DH mit "i2cget". Wenn 0x33 ausgegeben wird, ist es OK. Wenn nicht, stimmt etwas nicht und Sie sollten die Verbindung usw. überprüfen.

i2cdetect -y 1
i2cget -y 1 0x18 0x0f b
  0x33

Geschichte um I2C

Wie bei GPIO wäre es schön, wenn es mit einem einzigen Funktionsaufruf leicht gesteuert werden könnte, aber I2C ist etwas kompliziert. Vorkenntnisse sind erforderlich.

Die Steuerung des I2C-Geräts erfolgt durch Module, die in mehrere Schichten unterteilt sind. Beginnen wir mit der Hardware. Erstens gibt es I2C selbst (I2C-Bus). Im Fall von Raspberry Pi gibt es insgesamt drei I2Cs, I2C_0, I2C_1 und I2C_2, die separate I2C-Busse sind. An jeden Bus sind I2C-Geräte angeschlossen. Wenn Sie beispielsweise einen Beschleunigungssensor (LIS3DH, Adresse = 0x18) an den Bus von I2C_1 anschließen möchten, handelt es sich um einen I2C-Client.

Schauen wir uns die Softwareseite von unten an. Erstens gibt es den I2C-Adapter, der den I2C-Bus steuert. Die Steuerung des I2C selbst ist ein SoC-abhängiger Prozess. Daher gibt es für jeden SoC einen eigenen Quellcode. Im Fall von Raspetorte ist es i2c-bcm2835.c. Es gibt auch i2c-algo, das die während der Kommunikation verwendeten Algorithmen beschreibt. i2c-core.c ist der Hauptprozess und bietet allgemeine Funktionen wie "i2c_smbus_read_byte ()". Die I2C-Client-Treiber steuern jedes I2C-Gerät mit seinem "i2c_smbus_read_byte ()" und so weiter. Es gibt auch i2c-dev.c, um eine generische i2c-Gerätedatei (/ dev / i2c-1) vorzubereiten. Dies sind alles kernelseitige Codes.

Schicht Name Erläuterung
SW I2C Client Drivers Gerätetreiber für jedes I2C-Gerät, i2c-dev
i2c-core Hauptverarbeitung der I2C-Kommunikation
i2c-algo-XXX Kommunikationsalgorithmus
I2C Adapters Wo kann man den I2C selbst steuern? Register optimieren und chipabhängige Verarbeitung
HW - I2C Bus Zum Beispiel I2C_0、I2C_1
- I2C Client Jedes I2C-Gerät(Zum Beispiel Beschleunigungssensor(LIS3DH))

Dieses Mal erstellen wir den Gerätetreiber für den I2C-Client-Treiber in der obigen Tabelle, insbesondere den mit I2C verbundenen Beschleunigungssensor (LIS3DH). Sie könnten also denken: "Sie rufen einfach die i2c-Core-Funktion auf, oder?", Aber es ist nicht so einfach. Wir werden sehen, was später passiert, aber jetzt verstehen Sie einfach die Beziehungen in der obigen Tabelle.

Einfacher I2C-Gerät Gerätetreiber

Der grundlegendste Gerätetreibercode für I2C-Geräte finden Sie unten. Es gibt verschiedene Dinge, mit denen ich nicht vertraut bin. Beginnen wir am Ende des Codes. mydevice_init () und mydevice_exit () sind die Prozesse, wenn dieser Gerätetreiber geladen / entladen wird. Darin nennen wir "i2c_add_driver ()" und "i2c_del_driver ()". Irgendwie wird eine Tabelle als Argument übergeben.

I2C ist eine Schnittstelle, die mit "bus" verbunden ist. Nur weil dieser Gerätetreiber geladen ist, bedeutet dies nicht, dass das Ziel-I2C-Gerät (in diesem Fall der Beschleunigungssensor) immer angeschlossen ist. Es ist schwer, sich I2C vorzustellen, aber ich denke, es ist einfacher zu verstehen, wenn Sie sich USB vorstellen. Daher ist eine Verarbeitung erforderlich, wenn ein Gerät an den I2C-Bus angeschlossen / von diesem getrennt wird. Es ist "i2c_add_driver ()" und "i2c_del_driver ()", um die Verarbeitung zu diesem Zeitpunkt zu registrieren / abzubrechen. Der zu registrierende Tabellentyp ist "struct i2c_driver". Registrieren Sie die von diesem Gerätetreiber unterstützten I2C-Geräteinformationen in ".id_table". Der registrierte Inhalt ist die am Anfang deklarierte struct i2c_device_id mydevice_i2c_idtable []. Das erste Mitglied ist der Name des entsprechenden Geräts. Der Kernel sucht nach einem entsprechenden Gerät mit diesem Namen (char name []) und ist ein sehr wichtiges Mitglied (keine eindeutige Nummer!). Das zweite Mitglied sind die privaten Daten, die in diesem Gerätetreiber verwendet werden. Alles ist in Ordnung, aber normalerweise scheint es eine Identifikationsnummer zu enthalten. Registrieren Sie die Funktionen, die der Kernel beim Verbinden / Trennen von I2C-Geräten aufruft, in ".probe" und ".remove". Registrieren Sie den Namen dieses Gerätetreibers in .driver.

In "mydevice_i2c_probe ()" wird die Funktion "i2c_smbus_read_byte_data ()" aufgerufen, um mit dem erkannten I2C-Gerät zu kommunizieren. In diesem Fall wird davon ausgegangen, dass LIS3DH angeschlossen ist, sodass der Wert an der Adresse 0x0F (Geräte-ID) gelesen wird. Das erste an "i2c_smbus_read_byte_data ()" übergebene Argument ist "struct i2c_client", in dem die I2C-Businformationen (zu verwendender I2C-Adapter) und die Slave-Adresse gespeichert sind. Diese Informationen sind verfügbar, wenn diese Funktion mydevice_i2c_probe () aufgerufen wird.

myDeviceDriver.c


#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/sched.h>
#include <linux/device.h>
#include <linux/i2c.h>
#include <asm/uaccess.h>

/***Informationen zu diesem Gerät***/
MODULE_LICENSE("Dual BSD/GPL");
#define DRIVER_NAME "MyDevice"				/* /proc/Gerätename in Geräten usw. angezeigt.*/

/*Registrieren Sie eine Tabelle, in der Geräte aufgeführt sind, die von diesem Gerätetreiber verarbeitet werden*/
/*Wichtig ist das Vornamenfeld. Dies bestimmt den Gerätenamen. Die Rückseite enthält Daten, die mit diesem Treiber frei verwendet werden können. Fügen Sie Zeiger und Identifikationsnummern ein*/
static struct i2c_device_id mydevice_i2c_idtable[] = {
	{"MyI2CDevice", 0},
	{ }
};
MODULE_DEVICE_TABLE(i2c, mydevice_i2c_idtable);

static int mydevice_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	printk("i2c_lcd_probe\n");
	printk("id.name = %s, id.driver_data = %d", id->name, id->driver_data);
	printk("slave address = 0x%02X\n", client->addr);

	/*Normalerweise hier, um zu überprüfen, ob das Gerät von diesem Devadora unterstützt wird*/

	int version;
	version = i2c_smbus_read_byte_data(client, 0x0f);
	printk("id = 0x%02X\n", version);

	return 0;
}

static int mydevice_i2c_remove(struct i2c_client *client)
{
	printk("mydevice_i2c_remove\n");
	return 0;
}

static struct i2c_driver mydevice_driver = {
	.driver = {
		.name	= DRIVER_NAME,
		.owner = THIS_MODULE,
	},
	.id_table		= mydevice_i2c_idtable,		//Von diesem Gerätetreiber unterstützte I2C-Geräte
	.probe			= mydevice_i2c_probe,		//Prozess, der aufgerufen wird, wenn das Ziel-I2C-Gerät erkannt wird
	.remove			= mydevice_i2c_remove,		//Prozess wird aufgerufen, wenn das Ziel-I2C-Gerät entfernt wird
};

/*Straße(insmod)Funktionen, die manchmal aufgerufen werden*/
static int mydevice_init(void)
{
	printk("mydevice_init\n");
	
	/*Registrieren Sie diesen Gerätetreiber als Gerätetreiber, der den I2C-Bus verwendet*/
	i2c_add_driver(&mydevice_driver);
	return 0;
}

/*Entladen(rmmod)Funktionen, die manchmal aufgerufen werden*/
static void mydevice_exit(void)
{
	printk("mydevice_exit\n");
	i2c_del_driver(&mydevice_driver);
}

module_init(mydevice_init);
module_exit(mydevice_exit);

Hinweis: Vereinfachen Sie die Routineverarbeitung

In den meisten Gerätetreibern für I2C-Geräte müssen Sie beim Laden / Entladen nur die Struktur "i2c_driver" registrieren. Daher kann es wie folgt vereinfacht werden.

module_i2c_driver(mydevice_driver);

Versuche dich zu bewegen

Erstellen, laden und betrachten Sie das Protokoll wie unten gezeigt.

make && sudo insmod MyDeviceModule.ko
dmesg
[ 8589.545480] mydevice_init

Im Protokoll wurde nur "mydevice_init ()" aufgerufen, nicht "mydevice_i2c_probe ()". Dies liegt daran, dass, wie bereits erläutert, "mydevice_i2c_probe ()" aufgerufen wird, wenn das Gerät erkannt wird, aber zu diesem Zeitpunkt kein I2C-Gerät angeschlossen ist. Wenn es sich um USB handelt, wird es wahrscheinlich automatisch erkannt. Wenn es sich jedoch um I2C handelt, müssen Sie dies manuell mitteilen. Lassen Sie den Kernel das neue Gerät erkennen, indem Sie einen Wert in "/ sys / bus / i2c / Geräte / i2c-1 / new_device" schreiben. Dieses Mal möchten wir davon ausgehen, dass das Gerät für diesen Gerätetreiber verbunden ist. Nehmen wir also an, dass das Gerät mit dem Namen "MyI2CDevice" mit der Slave-Adresse = 0x18 verbunden ist. Die Informationen in diesem Satz sind der Inhalt des in der obigen Tabelle gezeigten "I2C-Clients".

sudo bash -c 'echo MyI2CDevice 0x18 > /sys/bus/i2c/devices/i2c-1/new_device'
dmesg
[ 9179.410883] mydevice_i2c_probe
[ 9179.410902] id.name = MyI2CDevice, id.driver_data = 0
[ 9179.410912] slave address = 0x18
[ 9179.411301] id = 0x33
[ 9179.412045] i2c i2c-1: new_device: Instantiated device MyI2CDevice at 0x18

Dann wird, wie im obigen Protokoll gezeigt, "mydevice_i2c_probe ()" aufgerufen. Sie können auch sehen, dass der zu diesem Zeitpunkt übergebene Parameter "client" die materialisierten I2C-Client-Informationen enthält. Anhand dieser Informationen (cLient) können Sie dann sehen, dass Sie kommunizieren und die richtige ID erhalten können.

Anmerkung 1

Dieses Mal habe ich beschlossen, "ein Gerät mit dem Namen" MyI2CDevice "zu handhaben". Daher habe ich den Namen "MyI2CDevice" in der Tabelle "struct i2c_device_id" festgelegt und im Kernel registriert. Dann ließ ich den Kernel das Gerät "MyI2CDevice" manuell erkennen, sodass der Gerätetreiber "mydevice_i2c_probe ()" aufgerufen wurde. Da der Kernel nur nach dem Namen beurteilt, versucht er möglicherweise, das falsche Gerät zu steuern, wenn sich der Name mit einem anderen Gerät überschneidet. Bitte beachten Sie, dass probe in mydevice_i2c_probe () steht. Mit dieser Funktion sollten Sie jedoch überprüfen, ob dieser Gerätetreiber dieses Gerät verarbeitet. Wenn es sich um ein kompatibles Gerät handelt, wird 0 zurückgegeben, andernfalls wird -1 zurückgegeben. Um dies zu bestätigen, wird normalerweise die Slave-Adresse bestätigt oder eine Kommunikation durchgeführt, um die Geräte-ID und die Versionsinformationen zu erhalten. Dieses Mal ist es fest und gibt immer 0 zurück.

Anmerkung 2

Verwenden Sie den folgenden Befehl, um das Gerät zu entfernen.

sudo bash -c 'echo  0x18 > /sys/bus/i2c/devices/i2c-1/delete_device'

Erkennen Sie I2C-Geräte im Code

Im vorherigen Beispiel wurde das Gerät manuell aus dem Shell-Skript erkannt. Wahrscheinlich nicht empfohlen, aber Sie können dasselbe mit Ihrem Code wie folgt tun:

myDeviceDriver.Ein Teil von c


/*Straße(insmod)Funktionen, die manchmal aufgerufen werden*/
static struct i2c_client *i2c_clie = NULL;
static int mydevice_init(void)
{
	printk("mydevice_init\n");
	
	/*Registrieren Sie diesen Gerätetreiber als Gerätetreiber, der den I2C-Bus verwendet*/
	i2c_add_driver(&mydevice_driver);

	/*Erstellen Sie dynamisch eine Geräteentität(https://www.kernel.org/doc/Documentation/i2c/instantiating-devices) */
	/*Verbunden mit I2C1,"MyI2CDevice"Erstellen Sie ein Gerät mit einer Slave-Adresse von 0x18*/
	struct i2c_adapter *i2c_adap;
	i2c_adap = i2c_get_adapter(1);
	struct i2c_board_info i2c_board_info = {
		I2C_BOARD_INFO("MyI2CDevice", 0x18)
	};
	i2c_clie = i2c_new_device(i2c_adap, &i2c_board_info);
	i2c_put_adapter(i2c_adap);

	return 0;
}

/*Entladen(rmmod)Funktionen, die manchmal aufgerufen werden*/
static void mydevice_exit(void)
{
	printk("mydevice_exit\n");
	
	i2c_del_driver(&mydevice__driver);
	if(i2c_clie) i2c_unregister_device(i2c_clie);
}

module_init(mydevice_init);
module_exit(mydevice_exit);

Diese Methode wurde auch in der Linux-Dokumentation (Instanziieren von Geräten) beschrieben, sodass die Implementierung selbst in Ordnung ist, der Implementierungsspeicherort jedoch nicht gut ist. Welche Art von Gerät angeschlossen ist, hängt von der Karte ab. Daher sollten diese in board_bcm2835.c um bcm2835_init (void) geschrieben werden. Außerdem scheint es besser zu sein, "i2c_register_board_info ()" zu verwenden.

Es scheint auch eine Möglichkeit zu geben, es in den Gerätebaum (.dts) einzufügen, aber das ist noch nicht gut verstanden.

Schnittstelle mit sysfs

Bis zu diesem Zeitpunkt wurden die I2C-Geräteerkennung und die anfängliche Kommunikation abgeschlossen. Erstellen Sie als Nächstes eine Schnittstelle mit dem Benutzer oder dem Programm im Benutzerbereich. Grundsätzlich wird es wie gewohnt über Gerätedateien ausgetauscht. Erstellen Sie zunächst mit sysfs eine benutzerfreundliche Oberfläche. Ich habe sysfs (7. Mal) unter [https://qiita.com/take-iwiw/items/548444999d2dfdc06f46#%E3%83%8E%E3%83%BC%E3%83%882-sysfs] kurz angesprochen. .. Zu diesem Zeitpunkt wurde es als Parameter von module behandelt. Dieses Mal sollte es als Parameter für I2C-Geräte behandelt werden.

Stellen Sie sich eine einfache Schnittstelle vor, die nur die Geräte-ID (Versionsinformationen) erfasst. Die Vorgehensweise ist sehr einfach: (1) Definieren Sie die Handlerfunktion beim Lesen, (2) Legen Sie die Attribute der zu erstellenden Gerätedatei fest und (3) registrieren Sie sie im Kernel. Verwenden Sie für ② ein Hilfsmakro namens "DEVICE_ATTR". Rufen Sie für ③ die Funktion device_create_file () zum Zeitpunkt der Sonde auf.

Unten finden Sie den Code, der eine Datei mit dem Namen "version" erstellt und beim Lesen die Funktion "get_version ()" aufruft. Das erste Argument von "DEVICE_ATTR" ist der zu erstellende Dateiname. Das zweite Argument ist die Zugriffsberechtigung. Das dritte Argument ist die Handlerfunktion beim Lesen und das vierte Argument ist die Handlerfunktion beim Schreiben. Da die Schreiboperation dieses Mal nicht ausgeführt wird, wird NULL angegeben. Wenn get_version () zum Zeitpunkt des Lesens wie zuvor in der Handlerfunktion eingestellt ist, werden die Geräte-ID-Informationen wie zuvor gelesen, in buf gepackt und zurückgegeben. Um die I2C-Funktion verwenden zu können, benötigen wir die Informationen von struct i2c_client, aber struct device wird an diese Funktion übergeben. Da jedoch "struct i2c_client" darin enthalten ist, kann es leicht erhalten werden. Verwenden Sie "device_create_file ()" zum Zeitpunkt der Registrierung der Sonde. Das erste Argument ist auch "struct device". Das zweite Argument ist eine Reihe von Attributen für die zu registrierende Datei. Dies wird früher durch das Makro "DEVICE_ATTR" erstellt. Übergeben Sie den im ersten Argument von "DEVICE_ATTR" festgelegten Namen mit dem Präfix "dev_attr_".

myDeviceDriver.Ein Teil von c


static ssize_t get_version(struct device *dev, struct device_attribute *dev_attr, char * buf)
{
	printk("get_version\n");
	struct i2c_client * client = to_i2c_client(dev);

	int version;
	version = i2c_smbus_read_byte_data(client, 0x0f);
	return sprintf(buf, "id = 0x%02X\n", version);
}
static DEVICE_ATTR(version, S_IRUGO, get_version, NULL);

static int mydevice_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	printk("mydevice_i2c_probe\n");
	printk("id.name = %s, id.driver_data = %d", id->name, (int)(id->driver_data));
	printk("slave address = 0x%02X\n", client->addr);

	/*Normalerweise hier, um zu überprüfen, ob das Gerät von diesem Devadora unterstützt wird*/

	/*Erstellen Sie eine sysfs-Datei zum Lesen und Schreiben von Attributen dieses Gerätetreibers*/
	/* (/sys/devices/platform/soc/3f804000.i2c/i2c-1/1-0018/version)machen*/
	device_create_file(&client->dev, &dev_attr_version);

	return 0;
}

static int mydevice_i2c_remove(struct i2c_client *client)
{
	printk("mydevice_i2c_remove\n");
	device_remove_file(&client->dev, &dev_attr_version);
	return 0;
}

Erstellen, laden und erkennen Sie das Gerät anschließend wie folgt. Anschließend wird die Datei "/ sys / device / platform / soc / 3f804000.i2c / i2c-1 / 1-0018 / version" erstellt. Wenn Sie sie mit cat lesen, können Sie sehen, dass die ID erhalten wurde.

make && sudo insmod MyDeviceModule.ko
sudo bash -c 'echo MyI2CDevice 0x18 > /sys/bus/i2c/devices/i2c-1/new_device'
cat /sys/devices/platform/soc/3f804000.i2c/i2c-1/1-0018/version
   id = 0x33

Schnittstelle mit / dev

Obwohl sysfs einfach und bequem ist, möchten Sie möglicherweise auch das übliche Öffnen / Schließen / Lesen / Schreiben / Ioctl (+ Auswählen / Abrufen / Suchen usw.) verwenden. Dazu müssen Sie die Gerätedatei auf die gleiche Weise wie zuvor erstellen. Bisher wurde die Gerätedatei zum Zeitpunkt des Ladens erstellt, diesmal jedoch zum Zeitpunkt der Prüfung. Außerdem ist "struct i2c_client" erforderlich, um die I2C-Steuerfunktion zu verwenden. Es ist notwendig, die zum Zeitpunkt der Sonde empfangene struct i2c_client irgendwo zu belassen und beim Lesen oder Schreiben darauf zu verweisen. Dies kann leicht erreicht werden, indem man es in einer statischen Variablen hält, aber ich werde es etwas besser machen. Um dies zu erreichen, verwenden wir ein Hilfsmakro namens "container_of ()".

Vorherige Kenntniss

container_of container_of () ist ein unten definiertes Hilfsmakro, das zur Kompilierungszeit ausgeführt wird.

#define container_of(ptr, type, member) ({			\
	const typeof(((type *)0)->member) * __mptr = (ptr);	\
	(type *)((char *)__mptr - offsetof(type, member)); })
#endif

Geben Sie den Strukturnamen in type und den Mitgliedsnamen in die Struktur in member ein. Setzen Sie dann den tatsächlichen Zeiger dieses Elements in "ptr". Dann gibt es die Startadresse der Struktur zurück, in der das "ptr" gespeichert ist.

i2c_set_clientdata i2c_set_clientdata () ist eine unten definierte Funktion. Sie können die mit struct i2c_client verknüpften Informationen frei speichern. Diese Informationen können beliebig sein, enthalten jedoch normalerweise einen Zeiger auf die eigene Struktur des Gerätetreibers, die während der Prüfung zugewiesen wurde. Verwenden Sie "i2c_get_clientdata ()", um es abzurufen.

void i2c_set_clientdata(struct i2c_client *dev, void *data)

Code

Nachfolgend finden Sie den Code, der es ermöglicht, auf die zum Zeitpunkt der Prüfung zum Zeitpunkt des Öffnens / Schließens / Lesens / Schreibens empfangene struct i2c_client zu verweisen. Zunächst werde ich den Code in Eile bringen.

myDeviceDriver.c


#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/sched.h>
#include <linux/device.h>
#include <linux/i2c.h>
#include <asm/uaccess.h>

/***Informationen zu diesem Gerät***/
MODULE_LICENSE("Dual BSD/GPL");
#define DRIVER_NAME "MyDevice"				/* /proc/Gerätename in Geräten usw. angezeigt.*/
static const unsigned int MINOR_BASE = 0;	/*Startnummer und Anzahl der in diesem Gerätetreiber verwendeten Nebennummern(=Anzahl der Geräte) */
static const unsigned int MINOR_NUM  = 1;	/*Die kleine Zahl ist 0*/

/***Informationen zur I2C-Geräteverwaltung***/
/*I2C-Geräte, die von diesem Gerätetreiber verarbeitet werden*/
enum mydevice_i2c_model {
	MYDEVICE_MODEL_A = 0,
	MYDEVICE_MODEL_NUM,
};

/*Registrieren Sie eine Tabelle, in der Geräte aufgeführt sind, die von diesem Gerätetreiber verarbeitet werden*/
/*Wichtig ist das Vornamenfeld. Dies bestimmt den Gerätenamen. Die Rückseite enthält Daten, die mit diesem Treiber frei verwendet werden können. Fügen Sie Zeiger und Identifikationsnummern ein*/
static struct i2c_device_id mydevice_i2c_idtable[] = {
	{"MyI2CDevice", MYDEVICE_MODEL_A},
	{ }
};
MODULE_DEVICE_TABLE(i2c, mydevice_i2c_idtable);

/*Jedes I2C-Gerät(client)Informationen im Zusammenhang mit. Zur Sondenzeit i2c einstellen_set_In Kundendaten aufbewahren*/
struct mydevice_device_info {
	struct cdev        cdev;			/*geprüftes I2C-Gerät(client)Erforderlich, um cdev mit zu verknüpfen. Behälter offen_Suche nach von*/
	unsigned int       mydevice_major;	/*Hauptnummer dieses Gerätetreibers(Dynamisch entscheiden) */
	struct class      *mydevice_class;	/*Gerätetreiberklassenobjekt*/
	struct i2c_client *client;
	/*Fügen Sie bei Bedarf weitere hinzu. Mutex etc.*/
};


/*Funktion beim Öffnen aufgerufen*/
static int mydevice_open(struct inode *inode, struct file *file)
{
	printk("mydevice_open");

	/*Cdev mit diesem offen(inode->i_cdev)Mit meinem Gerät_device_Informationen finden*/
	struct mydevice_device_info *dev_info;
	dev_info = container_of(inode->i_cdev, struct mydevice_device_info, cdev);
	if (dev_info  == NULL || dev_info->client  == NULL) {
		printk(KERN_ERR "container_of\n");
		return -EFAULT;
	}
	file->private_data = dev_info;
	printk("i2c address = %02X\n",dev_info->client->addr);

	return 0;
}

/*Funktion wird beim Schließen aufgerufen*/
static int mydevice_close(struct inode *inode, struct file *file)
{
	printk("mydevice_close");
	return 0;
}

/*Funktion beim Lesen aufgerufen*/
static ssize_t mydevice_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
	printk("mydevice_read");

	struct mydevice_device_info *dev_info = filp->private_data;
	struct i2c_client * client = dev_info->client;

	int version;
	version = i2c_smbus_read_byte_data(client, 0x0f);
	return sprintf(buf, "id = 0x%02X\n", version);
}

/*Funktion zum Zeitpunkt des Schreibens aufgerufen*/
static ssize_t mydevice_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
	printk("mydevice_write");
	return count;
}

/*Handlertabelle für verschiedene Systemaufrufe*/
struct file_operations s_mydevice_fops = {
	.open    = mydevice_open,
	.release = mydevice_close,
	.read    = mydevice_read,
	.write   = mydevice_write,
};


static int mydevice_i2c_create_cdev(struct mydevice_device_info *dev_info)
{
	int alloc_ret = 0;
	int cdev_err = 0;
	dev_t dev;

	/*Sichern Sie sich eine kostenlose Hauptnummer*/
	alloc_ret = alloc_chrdev_region(&dev, MINOR_BASE, MINOR_NUM, DRIVER_NAME);
	if (alloc_ret != 0) {
		printk(KERN_ERR  "alloc_chrdev_region = %d\n", alloc_ret);
		return -1;
	}

	/*Erhaltene dev( =Hauptnummer+Kleine Zahl)Holen Sie sich und behalten Sie die Hauptnummer von*/
	dev_info->mydevice_major = MAJOR(dev);
	dev = MKDEV(dev_info->mydevice_major, MINOR_BASE);	/*Nicht notwendig? */

	/*Initialisierung der cdev-Struktur und Registrierung der Systemaufruf-Handler-Tabelle*/
	cdev_init(&dev_info->cdev, &s_mydevice_fops);
	dev_info->cdev.owner = THIS_MODULE;

	/*Dieser Gerätetreiber(cdev)Zum Kernel*/
	cdev_err = cdev_add(&dev_info->cdev, dev, MINOR_NUM);
	if (cdev_err != 0) {
		printk(KERN_ERR  "cdev_add = %d\n", alloc_ret);
		unregister_chrdev_region(dev, MINOR_NUM);
		return -1;
	}

	/*Registrieren Sie die Klasse dieses Geräts(/sys/class/mydevice/machen) */
	dev_info->mydevice_class = class_create(THIS_MODULE, "mydevice");
	if (IS_ERR(dev_info->mydevice_class)) {
		printk(KERN_ERR  "class_create\n");
		cdev_del(&dev_info->cdev);
		unregister_chrdev_region(dev, MINOR_NUM);
		return -1;
	}

	/* /sys/class/mydevice/mydevice*machen*/
	for (int minor = MINOR_BASE; minor < MINOR_BASE + MINOR_NUM; minor++) {
		device_create(dev_info->mydevice_class, NULL, MKDEV(dev_info->mydevice_major, minor), NULL, "mydevice%d", minor);
	}

	return 0;
}

static void mydevice_i2c_delete_cdev(struct mydevice_device_info *dev_info)
{
	dev_t dev = MKDEV(dev_info->mydevice_major, MINOR_BASE);
	
	/* /sys/class/mydevice/mydevice*Löschen*/
	for (int minor = MINOR_BASE; minor < MINOR_BASE + MINOR_NUM; minor++) {
		device_destroy(dev_info->mydevice_class, MKDEV(dev_info->mydevice_major, minor));
	}

	/*Entfernen Sie die Klassenregistrierung für dieses Gerät(/sys/class/mydevice/Löschen) */
	class_destroy(dev_info->mydevice_class);

	/*Dieser Gerätetreiber(cdev)Aus dem Kernel*/
	cdev_del(&dev_info->cdev);

	/*Entfernen Sie die von diesem Gerätetreiber verwendete Hauptnummernregistrierung*/
	unregister_chrdev_region(dev, MINOR_NUM);
}

static int mydevice_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	printk("mydevice_i2c_probe\n");
	printk("id.name = %s, id.driver_data = %d", id->name, (int)(id->driver_data));
	printk("slave address = 0x%02X\n", client->addr);

	/*Normalerweise hier, um zu überprüfen, ob das Gerät von diesem Devadora unterstützt wird*/

	/* open/close/read/i2c auch mit schreiben_Der Client wird verwendet, also behalten Sie ihn*/
	struct mydevice_device_info *dev_info;
	dev_info =  (struct mydevice_device_info*)devm_kzalloc(&client->dev, sizeof(struct mydevice_device_info), GFP_KERNEL);
	dev_info->client = client;
	i2c_set_clientdata(client, dev_info);
	

	/*Registrieren Sie diesen Gerätetreiber als Zeichentyp im Kernel.(/sys/class/mydevice/mydevice*machen) */
	if(mydevice_i2c_create_cdev(dev_info)) return -ENOMEM;

	return 0;
}

static int mydevice_i2c_remove(struct i2c_client *client)
{
	printk("mydevice_i2c_remove\n");
	struct mydevice_device_info *dev_info;
	dev_info = i2c_get_clientdata(client);
	mydevice_i2c_delete_cdev(dev_info);

	return 0;
}

static struct i2c_driver mydevice_driver = {
	.driver = {
		.name	= DRIVER_NAME,
		.owner = THIS_MODULE,
	},
	.id_table		= mydevice_i2c_idtable,		//Von diesem Gerätetreiber unterstützte I2C-Geräte
	.probe			= mydevice_i2c_probe,		//Prozess, der aufgerufen wird, wenn das Ziel-I2C-Gerät erkannt wird
	.remove			= mydevice_i2c_remove,		//Prozess wird aufgerufen, wenn das Ziel-I2C-Gerät entfernt wird
};


/*Registrieren Sie diesen Gerätetreiber als Gerätetreiber, der den I2C-Bus verwendet*/
module_i2c_driver(mydevice_driver);

Am Anfang des Codes definieren wir unsere eigene Struktur namens "struct mydevice_device_info". Es speichert "struct i2c_client", dh die Informationen, die jedem I2C-Gerät (Client) zugeordnet sind. ("Assoziieren" bedeutet hier, dass bei Angabe von "struct i2c_client" die gespeicherten Informationen ("struct mydevice_device_info") abgerufen werden können.)

Dieses Mal erstelle ich jedes Mal eine neue Gerätedatei (cdev), wenn ein I2C-Gerät angeschlossen wird, dh zum Zeitpunkt der Prüfung. Dies ist genau der gleiche Vorgang, den Sie beim Laden ausgeführt haben. Der Erstellungsprozess der Gerätedatei wird in die Funktion mydevice_i2c_create_cdev () extrahiert. Bisher wurden struct cdev cdev;, unsigned int mydevice_major; und struct class * mydevice_class; statisch gehalten. Fügen Sie es diesmal in die Struktur struct mydevice_device_info ein. Außerdem wird der Bereich dieser Struktur zum Zeitpunkt der Sonde dynamisch von "devm_kzalloc" zugewiesen. Bei Verwendung von "devm_kzalloc" wird dieser Speicher automatisch freigegeben, wenn das Gerät verschwindet. Speichern Sie außerdem in derselben Struktur "struct mydevice_device_info" die für die I2C-Steuerung erforderliche "struct i2c_client". Verwenden Sie dann "i2c_set_clientdata ()", um diese Struktur "mydevice_device_info" in dem Bereich zu speichern, der diesem I2C-Client zugeordnet ist (wo? Ich denke, der Kernel wird es gut machen). Alle diese Prozesse werden ausgeführt, wenn das Gerät angeschlossen ist (mydevice_i2c_probe ()).

Da ich in mydevice_i2c_probe () eine Zeichengerätedatei (cdev) erstellt habe, kann ich open / close / read / write verwenden. Schauen Sie sich zuerst mydevice_open () an. Ein Zeiger auf cdev wird im ersten Argument struct inode * inode dieser Funktion gespeichert. Sie können mit inode-> i_cdev darauf verweisen. Zuvor hatte cdev beim Erstellen der Gerätedatei zum Testzeitpunkt diese in seiner eigenen Struktur "struct mydevice_device_info" gespeichert. Daher können Sie mit container_of die Startadresse der Struktur ermitteln, in der dieser cdev gespeichert ist. dev_info = container_of (inode-> i_cdev, struct mydevice_device_info, cdev);. Und da auch die Informationen von struct i2c_client enthalten waren, ist damit eine I2C-Steuerung möglich. Eigentlich wird es zum Zeitpunkt des Lesens / Schreibens verwendet, also habe ich es in "private_data" in der "file" -Struktur eingefügt, damit es zum Zeitpunkt des Lesens / Schreibens referenziert werden kann.

Versuche dich zu bewegen

Erstellen und laden Sie das I2C-Gerät wie unten gezeigt. Dann wird / dev / mydevice0 erstellt. Wenn Sie dieses Gerät mit cat lesen, können Sie sehen, dass die Funktion mydevice_read () aufgerufen wird, in der die I2C-Kommunikation durchgeführt und der ID-Wert zurückgegeben wird.

make && sudo insmod MyDeviceModule.ko
sudo bash -c 'echo MyI2CDevice 0x18 > /sys/bus/i2c/devices/i2c-1/new_device'
ls /dev/mydevice0
    /dev/mydevice0
cat /dev/mydevice0
    id = 0x33

abschließend

Es ist ein etwas langer Artikel, aber der einfache Code, den ich am Anfang erwähnt habe, ist die Grundform. Ich denke, es ist in Ordnung, wenn Sie ihn zuerst unterdrücken. Ich denke, dass die gleiche Idee grundsätzlich auf Gerätetreiber (SPI, USB, PCI usw.) angewendet werden kann, die andere Busse verwenden. (Ich denke, dass zusätzliche Verarbeitung zum Erkennen und Anhalten erforderlich sein wird)

In dem Artikel gab es auch redundante Ausdrücke wie "Gerätetreiber für Geräte, die über I2C verbunden sind", "Gerätetreiber für I2C-Geräte" und "Gerätetreiber für I2C-Geräte". Dies liegt daran, dass ich es von "dem Gerätetreiber von I2C selbst" unterscheiden wollte. Ich wünschte, ich hätte das Wort irgendwo definiert, aber ich habe es vergessen.

Recommended Posts

So erstellen Sie einen eingebetteten Linux-Gerätetreiber (11)
So erstellen Sie einen eingebetteten Linux-Gerätetreiber (8)
So erstellen Sie einen eingebetteten Linux-Gerätetreiber (4)
So erstellen Sie einen eingebetteten Linux-Gerätetreiber (2)
So erstellen Sie einen eingebetteten Linux-Gerätetreiber (3)
So erstellen Sie einen eingebetteten Linux-Gerätetreiber (6)
So erstellen Sie einen eingebetteten Linux-Gerätetreiber (5)
So erstellen Sie einen eingebetteten Linux-Gerätetreiber (10)
So erstellen Sie einen eingebetteten Linux-Gerätetreiber (9)
So erstellen Sie einen eingebetteten Linux-Gerätetreiber (12) (vollständig)
Wie man Yubico Yubikey von Manjaro Linux erkennt
So erstellen Sie ein interaktives CLI-Tool mit Golang
So erstellen Sie einen HTTPS-Server mit Go / Gin
So erstellen Sie ein Hacking-Labor - Kali Linux (2020.1) VirtualBox 64-Bit Teil 2-
So erstellen Sie ein Hacking-Labor - Kali Linux (2020.1) VirtualBox 64-Bit-Edition -
Wie erstelle ich ein Python-Paket (geschrieben für Praktikanten)
So erstellen Sie eine ISO-Datei (CD-Image) unter Linux
Wie erstelle ich eine japanisch-englische Übersetzung?
Wie man einen lockeren Bot macht
So installieren Sie VMware-Tools unter Linux
So erstellen Sie eine rekursive Funktion
So installieren Sie MBDyn (Linux Ubuntu)
[Blender] So erstellen Sie ein Blender-Plug-In
[Blender] So erstellen Sie Blender-Skripte mehrsprachig
Wie erstelle ich einen Crawler?
So erstellen Sie den MongoDB C-Sprachtreiber
So überprüfen Sie die Linux-Betriebssystemversion
So machen Sie einen String in Python zu einem Array oder ein Array zu einem String
So bringen Sie den Druckertreiber für Oki Mac in Linux
So machen Sie Word Cloud-Zeichen monochromatisch
Wie man Selen so leicht wie möglich macht
[Linux] Unterteilen von Dateien und Ordnern
So erstellen Sie einen LINE-Bot mit künstlicher Intelligenz mit der Flask + LINE Messaging-API
So installieren Sie das aws-session-manager-Plugin unter Manajro Linux
[Python] Wie man eine Klasse iterierbar macht
python3 So installieren Sie ein externes Modul
So erstellen Sie eine NVIDIA Docker-Umgebung
So konvertieren Sie Python in eine exe-Datei
[Linux] Verwendung des Befehls echo
So aktualisieren Sie PHP unter Amazon Linux 2
So zeigen Sie Piktogramme unter Manjaro Linux an
So installieren Sie Pakete unter Alpine Linux
[Cocos2d-x] So erstellen Sie eine Skriptbindung (Teil 2)
So bedienen Sie Linux von der Konsole aus
So schalten Sie Linux unter Ultra96-V2 aus
So aktualisieren Sie die Sicherheit unter CentOS Linux 8
Ich möchte ein Automatisierungsprogramm erstellen!
Wie installiere ich php7.4 unter Linux (Ubuntu)
So machen Sie Multi-Boot-USB (Windows 10-kompatibel)
So erstellen Sie einen benutzerdefinierten Backtrader-Indikator
Wie erstelle ich eine Pelican Site Map?
[Cocos2d-x] So erstellen Sie eine Skriptbindung (Teil 1)
So finden Sie große Dateien unter Linux
Verwendung des JDBC-Treibers mit Redash