Linux设备驱动开发-SPI驱动开发详解(包含设备树处理详细过程)

基础知识及 SPI 相关结构体介绍

引脚:MISO(master 输入,slave 输出),MOSI(master 输出,slave 输入),片选引脚,SCK(时钟)

控制寄存器:可以设置这CPOL 和 CPHA两个参数,CPOL 代表 SCK 初始电平,CPHA 代表相位(第一/第二个时钟沿采集数据),

SPI 状态寄存器:分辨数据是否发送完了,使能中断

波特率寄存器:设置 SCK 频率

数据寄存器:连接移位器收发数据

驱动程序编写方法:Master 和 slave 都需要驱动程序,采用总线设备驱动模型编写,设备树->spi_dev,驱动程序->spi_driver

/ include / linux / spi / spi.h中定义了 spi_master,里面有 transfer 函数指针

/* Compatibility layer */
#define spi_master			spi_controllerstruct spi_controller {struct device	dev;struct list_head list;/* other than negative (== assign one dynamically), bus_num is fully* board-specific.  usually that simplifies to being SOC-specific.* example:  one SOC has three SPI controllers, numbered 0..2,* and one board's schematics might show it using SPI-2.  software* would normally use bus_num=2 for that controller.*/s16			bus_num;/* chipselects will be integral to many controllers; some others* might use board-specific GPIOs.*/u16			num_chipselect;/* some SPI controllers pose alignment requirements on DMAable* buffers; let protocol drivers know about these requirements.*/u16			dma_alignment;/* spi_device.mode flags understood by this controller driver */u32			mode_bits;/* spi_device.mode flags override flags for this controller */u32			buswidth_override_bits;/* bitmask of supported bits_per_word for transfers */u32			bits_per_word_mask;
#define SPI_BPW_MASK(bits) BIT((bits) - 1)
#define SPI_BPW_RANGE_MASK(min, max) GENMASK((max) - 1, (min) - 1)/* limits on transfer speed */u32			min_speed_hz;u32			max_speed_hz;/* other constraints relevant to this driver */u16			flags;
#define SPI_CONTROLLER_HALF_DUPLEX	BIT(0)	/* can't do full duplex */
#define SPI_CONTROLLER_NO_RX		BIT(1)	/* can't do buffer read */
#define SPI_CONTROLLER_NO_TX		BIT(2)	/* can't do buffer write */
#define SPI_CONTROLLER_MUST_RX		BIT(3)	/* requires rx */
#define SPI_CONTROLLER_MUST_TX		BIT(4)	/* requires tx */#define SPI_MASTER_GPIO_SS		BIT(5)	/* GPIO CS must select slave *//* flag indicating if the allocation of this struct is devres-managed */bool			devm_allocated;/* flag indicating this is an SPI slave controller */bool			slave;/** on some hardware transfer / message size may be constrained* the limit may depend on device transfer settings*/size_t (*max_transfer_size)(struct spi_device *spi);size_t (*max_message_size)(struct spi_device *spi);/* I/O mutex */struct mutex		io_mutex;/* Used to avoid adding the same CS twice */struct mutex		add_lock;/* lock and mutex for SPI bus locking */spinlock_t		bus_lock_spinlock;struct mutex		bus_lock_mutex;/* flag indicating that the SPI bus is locked for exclusive use */bool			bus_lock_flag;/* Setup mode and clock, etc (spi driver may call many times).** IMPORTANT:  this may be called when transfers to another* device are active.  DO NOT UPDATE SHARED REGISTERS in ways* which could break those transfers.*/int			(*setup)(struct spi_device *spi);/** set_cs_timing() method is for SPI controllers that supports* configuring CS timing.** This hook allows SPI client drivers to request SPI controllers* to configure specific CS timing through spi_set_cs_timing() after* spi_setup().*/int (*set_cs_timing)(struct spi_device *spi);/* bidirectional bulk transfers** + The transfer() method may not sleep; its main role is*   just to add the message to the queue.* + For now there's no remove-from-queue operation, or*   any other request management* + To a given spi_device, message queueing is pure fifo** + The controller's main job is to process its message queue,*   selecting a chip (for masters), then transferring data* + If there are multiple spi_device children, the i/o queue*   arbitration algorithm is unspecified (round robin, fifo,*   priority, reservations, preemption, etc)** + Chipselect stays active during the entire message*   (unless modified by spi_transfer.cs_change != 0).* + The message transfers use clock and SPI mode parameters*   previously established by setup() for this device*/int			(*transfer)(struct spi_device *spi,struct spi_message *mesg);/* called on release() to free memory provided by spi_controller */void			(*cleanup)(struct spi_device *spi);/** Used to enable core support for DMA handling, if can_dma()* exists and returns true then the transfer will be mapped* prior to transfer_one() being called.  The driver should* not modify or store xfer and dma_tx and dma_rx must be set* while the device is prepared.*/bool			(*can_dma)(struct spi_controller *ctlr,struct spi_device *spi,struct spi_transfer *xfer);struct device *dma_map_dev;/** These hooks are for drivers that want to use the generic* controller transfer queueing mechanism. If these are used, the* transfer() function above must NOT be specified by the driver.* Over time we expect SPI drivers to be phased over to this API.*/bool				queued;struct kthread_worker		*kworker;struct kthread_work		pump_messages;spinlock_t			queue_lock;struct list_head		queue;struct spi_message		*cur_msg;bool				idling;bool				busy;bool				running;bool				rt;bool				auto_runtime_pm;bool                            cur_msg_prepared;bool				cur_msg_mapped;char				last_cs;bool				last_cs_mode_high;bool                            fallback;struct completion               xfer_completion;size_t				max_dma_len;int (*prepare_transfer_hardware)(struct spi_controller *ctlr);int (*transfer_one_message)(struct spi_controller *ctlr,struct spi_message *mesg);int (*unprepare_transfer_hardware)(struct spi_controller *ctlr);int (*prepare_message)(struct spi_controller *ctlr,struct spi_message *message);int (*unprepare_message)(struct spi_controller *ctlr,struct spi_message *message);int (*slave_abort)(struct spi_controller *ctlr);/** These hooks are for drivers that use a generic implementation* of transfer_one_message() provided by the core.*/void (*set_cs)(struct spi_device *spi, bool enable);int (*transfer_one)(struct spi_controller *ctlr, struct spi_device *spi,struct spi_transfer *transfer);void (*handle_err)(struct spi_controller *ctlr,struct spi_message *message);/* Optimized handlers for SPI memory-like operations. */const struct spi_controller_mem_ops *mem_ops;const struct spi_controller_mem_caps *mem_caps;/* gpio chip select */struct gpio_desc	**cs_gpiods;bool			use_gpio_descriptors;s8			unused_native_cs;s8			max_native_cs;/* statistics */struct spi_statistics	statistics;/* DMA channels for use with core dmaengine helpers */struct dma_chan		*dma_tx;struct dma_chan		*dma_rx;/* dummy data for full duplex devices */void			*dummy_rx;void			*dummy_tx;int (*fw_translate_cs)(struct spi_controller *ctlr, unsigned cs);/** Driver sets this field to indicate it is able to snapshot SPI* transfers (needed e.g. for reading the time of POSIX clocks)*/bool			ptp_sts_supported;/* Interrupt enable state during PTP system timestamping */unsigned long		irq_flags;
};

spi_device 结构体,可以看到这里结构体里面包含spi_controller,由设备树转换得到,其中bits_per_word 是每次传输的位数

struct spi_device {struct device		dev;struct spi_controller	*controller;struct spi_controller	*master;	/* compatibility layer */u32			max_speed_hz;u8			chip_select;u8			bits_per_word;bool			rt;
#define SPI_NO_TX	BIT(31)		/* no transmit wire */
#define SPI_NO_RX	BIT(30)		/* no receive wire *//** All bits defined above should be covered by SPI_MODE_KERNEL_MASK.* The SPI_MODE_KERNEL_MASK has the SPI_MODE_USER_MASK counterpart,* which is defined in 'include/uapi/linux/spi/spi.h'.* The bits defined here are from bit 31 downwards, while in* SPI_MODE_USER_MASK are from 0 upwards.* These bits must not overlap. A static assert check should make sure of that.* If adding extra bits, make sure to decrease the bit index below as well.*/
#define SPI_MODE_KERNEL_MASK	(~(BIT(30) - 1))u32			mode;int			irq;void			*controller_state;void			*controller_data;char			modalias[SPI_NAME_SIZE];const char		*driver_override;struct gpio_desc	*cs_gpiod;	/* chip select gpio desc */struct spi_delay	word_delay; /* inter-word delay *//* CS delays */struct spi_delay	cs_setup;struct spi_delay	cs_hold;struct spi_delay	cs_inactive;/* the statistics */struct spi_statistics	statistics;/** likely need more hooks for more protocol options affecting how* the controller talks to each chip, like:*  - memory packing (12 bit samples into low bits, others zeroed)*  - priority*  - chipselect delays*  - ...*/
};

spi_driver 结构体

struct spi_driver {const struct spi_device_id *id_table;int			(*probe)(struct spi_device *spi);void			(*remove)(struct spi_device *spi);void			(*shutdown)(struct spi_device *spi);struct device_driver	driver;
};

设备树结构:

spi{

spi master 配置(address-cell,size-cell,reg,interrupts,interrupt-parent,cs-gpios,compatible,spi-cpol,spi-cpha......)

slave{spi_device}

slave{spi_device}

}

设备树处理过程

首先根据spi 的compatible 属性找到 master 驱动,然后由 master 驱动解析子节点

看看内核的处理过程,设备树匹配之后 调用 probe 函数

/ drivers / spi / spi-gpio.c

static struct platform_driver spi_gpio_driver = {.driver = {.name	= DRIVER_NAME,.of_match_table = of_match_ptr(spi_gpio_dt_ids),},.probe		= spi_gpio_probe,
};
module_platform_driver(spi_gpio_driver);

首先设置 master 的参数

static int spi_gpio_probe(struct platform_device *pdev)
{int				status;struct spi_master		*master;struct spi_gpio			*spi_gpio;struct device			*dev = &pdev->dev;struct spi_bitbang		*bb;master = devm_spi_alloc_master(dev, sizeof(*spi_gpio));if (!master)return -ENOMEM;if (pdev->dev.of_node)status = spi_gpio_probe_dt(pdev, master);elsestatus = spi_gpio_probe_pdata(pdev, master);if (status)return status;spi_gpio = spi_master_get_devdata(master);status = spi_gpio_request(dev, spi_gpio);if (status)return status;master->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 32);master->mode_bits = SPI_3WIRE | SPI_3WIRE_HIZ | SPI_CPHA | SPI_CPOL |SPI_CS_HIGH | SPI_LSB_FIRST;if (!spi_gpio->mosi) {/* HW configuration without MOSI pin** No setting SPI_MASTER_NO_RX here - if there is only* a MOSI pin connected the host can still do RX by* changing the direction of the line.*/master->flags = SPI_MASTER_NO_TX;}master->bus_num = pdev->id;master->setup = spi_gpio_setup;master->cleanup = spi_gpio_cleanup;bb = &spi_gpio->bitbang;bb->master = master;/** There is some additional business, apart from driving the CS GPIO* line, that we need to do on selection. This makes the local* callback for chipselect always get called.*/master->flags |= SPI_MASTER_GPIO_SS;bb->chipselect = spi_gpio_chipselect;bb->set_line_direction = spi_gpio_set_direction;if (master->flags & SPI_MASTER_NO_TX) {bb->txrx_word[SPI_MODE_0] = spi_gpio_spec_txrx_word_mode0;bb->txrx_word[SPI_MODE_1] = spi_gpio_spec_txrx_word_mode1;bb->txrx_word[SPI_MODE_2] = spi_gpio_spec_txrx_word_mode2;bb->txrx_word[SPI_MODE_3] = spi_gpio_spec_txrx_word_mode3;} else {bb->txrx_word[SPI_MODE_0] = spi_gpio_txrx_word_mode0;bb->txrx_word[SPI_MODE_1] = spi_gpio_txrx_word_mode1;bb->txrx_word[SPI_MODE_2] = spi_gpio_txrx_word_mode2;bb->txrx_word[SPI_MODE_3] = spi_gpio_txrx_word_mode3;}bb->setup_transfer = spi_bitbang_setup_transfer;status = spi_bitbang_init(&spi_gpio->bitbang);if (status)return status;return devm_spi_register_master(&pdev->dev, master);
}

/ include / linux / spi / spi.h

#define devm_spi_register_master(_dev, _ctlr) \devm_spi_register_controller(_dev, _ctlr)

/ drivers / spi / spi.c

/*** devm_spi_register_controller - register managed SPI master or slave*	controller* @dev:    device managing SPI controller* @ctlr: initialized controller, originally from spi_alloc_master() or*	spi_alloc_slave()* Context: can sleep** Register a SPI device as with spi_register_controller() which will* automatically be unregistered and freed.** Return: zero on success, else a negative error code.*/
int devm_spi_register_controller(struct device *dev,struct spi_controller *ctlr)
{struct spi_controller **ptr;int ret;ptr = devres_alloc(devm_spi_unregister, sizeof(*ptr), GFP_KERNEL);if (!ptr)return -ENOMEM;ret = spi_register_controller(ctlr);if (!ret) {*ptr = ctlr;devres_add(dev, ptr);} else {devres_free(ptr);}return ret;
}
EXPORT_SYMBOL_GPL(devm_spi_register_controller);

注册 master

int spi_register_controller(struct spi_controller *ctlr)
{struct device		*dev = ctlr->dev.parent;struct boardinfo	*bi;int			status;int			id, first_dynamic;if (!dev)return -ENODEV;/** Make sure all necessary hooks are implemented before registering* the SPI controller.*/status = spi_controller_check_ops(ctlr);if (status)return status;if (ctlr->bus_num >= 0) {/* devices with a fixed bus num must check-in with the num */mutex_lock(&board_lock);id = idr_alloc(&spi_master_idr, ctlr, ctlr->bus_num,ctlr->bus_num + 1, GFP_KERNEL);mutex_unlock(&board_lock);if (WARN(id < 0, "couldn't get idr"))return id == -ENOSPC ? -EBUSY : id;ctlr->bus_num = id;} else if (ctlr->dev.of_node) {/* allocate dynamic bus number using Linux idr */id = of_alias_get_id(ctlr->dev.of_node, "spi");if (id >= 0) {ctlr->bus_num = id;mutex_lock(&board_lock);id = idr_alloc(&spi_master_idr, ctlr, ctlr->bus_num,ctlr->bus_num + 1, GFP_KERNEL);mutex_unlock(&board_lock);if (WARN(id < 0, "couldn't get idr"))return id == -ENOSPC ? -EBUSY : id;}}if (ctlr->bus_num < 0) {first_dynamic = of_alias_get_highest_id("spi");if (first_dynamic < 0)first_dynamic = 0;elsefirst_dynamic++;mutex_lock(&board_lock);id = idr_alloc(&spi_master_idr, ctlr, first_dynamic,0, GFP_KERNEL);mutex_unlock(&board_lock);if (WARN(id < 0, "couldn't get idr"))return id;ctlr->bus_num = id;}ctlr->bus_lock_flag = 0;init_completion(&ctlr->xfer_completion);if (!ctlr->max_dma_len)ctlr->max_dma_len = INT_MAX;/** Register the device, then userspace will see it.* Registration fails if the bus ID is in use.*/dev_set_name(&ctlr->dev, "spi%u", ctlr->bus_num);if (!spi_controller_is_slave(ctlr) && ctlr->use_gpio_descriptors) {status = spi_get_gpio_descs(ctlr);if (status)goto free_bus_id;/** A controller using GPIO descriptors always* supports SPI_CS_HIGH if need be.*/ctlr->mode_bits |= SPI_CS_HIGH;}/** Even if it's just one always-selected device, there must* be at least one chipselect.*/if (!ctlr->num_chipselect) {status = -EINVAL;goto free_bus_id;}/* setting last_cs to -1 means no chip selected */ctlr->last_cs = -1;status = device_add(&ctlr->dev);if (status < 0)goto free_bus_id;dev_dbg(dev, "registered %s %s\n",spi_controller_is_slave(ctlr) ? "slave" : "master",dev_name(&ctlr->dev));/** If we're using a queued driver, start the queue. Note that we don't* need the queueing logic if the driver is only supporting high-level* memory operations.*/if (ctlr->transfer) {dev_info(dev, "controller is unqueued, this is deprecated\n");} else if (ctlr->transfer_one || ctlr->transfer_one_message) {status = spi_controller_initialize_queue(ctlr);if (status) {device_del(&ctlr->dev);goto free_bus_id;}}/* add statistics */spin_lock_init(&ctlr->statistics.lock);mutex_lock(&board_lock);list_add_tail(&ctlr->list, &spi_controller_list);list_for_each_entry(bi, &board_list, list)spi_match_controller_to_boardinfo(ctlr, &bi->board_info);mutex_unlock(&board_lock);/* Register devices from the device tree and ACPI */of_register_spi_devices(ctlr);acpi_register_spi_devices(ctlr);return status;free_bus_id:mutex_lock(&board_lock);idr_remove(&spi_master_idr, ctlr->bus_num);mutex_unlock(&board_lock);return status;
}
EXPORT_SYMBOL_GPL(spi_register_controller);

可以看到这里遍历子节点然后区进行注册

static void of_register_spi_devices(struct spi_controller *ctlr)
{struct spi_device *spi;struct device_node *nc;if (!ctlr->dev.of_node)return;for_each_available_child_of_node(ctlr->dev.of_node, nc) {if (of_node_test_and_set_flag(nc, OF_POPULATED))continue;spi = of_register_spi_device(ctlr, nc);if (IS_ERR(spi)) {dev_warn(&ctlr->dev,"Failed to create SPI device for %pOF\n", nc);of_node_clear_flag(nc, OF_POPULATED);}}
}
static struct spi_device *
of_register_spi_device(struct spi_controller *ctlr, struct device_node *nc)
{struct spi_device *spi;int rc;/* Alloc an spi_device */spi = spi_alloc_device(ctlr);if (!spi) {dev_err(&ctlr->dev, "spi_device alloc error for %pOF\n", nc);rc = -ENOMEM;goto err_out;}/* Select device driver */rc = of_modalias_node(nc, spi->modalias,sizeof(spi->modalias));if (rc < 0) {dev_err(&ctlr->dev, "cannot find modalias for %pOF\n", nc);goto err_out;}rc = of_spi_parse_dt(ctlr, spi, nc);if (rc)goto err_out;/* Store a pointer to the node in the device structure */of_node_get(nc);spi->dev.of_node = nc;spi->dev.fwnode = of_fwnode_handle(nc);/* Register the new device */rc = spi_add_device(spi);if (rc) {dev_err(&ctlr->dev, "spi_device register error %pOF\n", nc);goto err_of_node_put;}return spi;err_of_node_put:of_node_put(nc);
err_out:spi_dev_put(spi);return ERR_PTR(rc);
}

spidev 驱动源码解析

insmod 之后会调用 init 函数

/ drivers / spi / spidev.c

这里面注册了一个字符设备驱动和 spi_driver

static int __init spidev_init(void)
{int status;/* Claim our 256 reserved device numbers.  Then register a class* that will key udev/mdev to add/remove /dev nodes.  Last, register* the driver which manages those device numbers.*/status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops);if (status < 0)return status;spidev_class = class_create(THIS_MODULE, "spidev");if (IS_ERR(spidev_class)) {unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);return PTR_ERR(spidev_class);}status = spi_register_driver(&spidev_spi_driver);if (status < 0) {class_destroy(spidev_class);unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);}return status;
}
module_init(spidev_init);

设备树匹配之后 probe 函数被调用

static struct spi_driver spidev_spi_driver = {.driver = {.name =		"spidev",.of_match_table = spidev_dt_ids,.acpi_match_table = spidev_acpi_ids,},.probe =	spidev_probe,.remove =	spidev_remove,.id_table =	spidev_spi_ids,/* NOTE:  suspend/resume methods are not necessary here.* We don't do anything except pass the requests to/from* the underlying controller.  The refrigerator handles* most issues; the controller driver handles the rest.*/
};

来看看 probe 函数,这里首先记录 spi 设备,然后找到一个次设备号并创建设备节点,创建之后可以在/dev/找到这个字符设备,用户访问时通过下面的device_list 找出spidev_data(有spi_device),spi_device 中又包含了 spi_master,这样就可以通过spi_master 读写设备了

static int spidev_probe(struct spi_device *spi)
{int (*match)(struct device *dev);struct spidev_data	*spidev;int			status;unsigned long		minor;match = device_get_match_data(&spi->dev);if (match) {status = match(&spi->dev);if (status)return status;}/* Allocate driver data */spidev = kzalloc(sizeof(*spidev), GFP_KERNEL);if (!spidev)return -ENOMEM;/* Initialize the driver data */spidev->spi = spi;spin_lock_init(&spidev->spi_lock);mutex_init(&spidev->buf_lock);INIT_LIST_HEAD(&spidev->device_entry);/* If we can allocate a minor number, hook up this device.* Reusing minors is fine so long as udev or mdev is working.*/mutex_lock(&device_list_lock);minor = find_first_zero_bit(minors, N_SPI_MINORS);if (minor < N_SPI_MINORS) {struct device *dev;spidev->devt = MKDEV(SPIDEV_MAJOR, minor);dev = device_create(spidev_class, &spi->dev, spidev->devt,spidev, "spidev%d.%d",spi->master->bus_num, spi->chip_select);status = PTR_ERR_OR_ZERO(dev);} else {dev_dbg(&spi->dev, "no minor number available!\n");status = -ENODEV;}if (status == 0) {set_bit(minor, minors);list_add(&spidev->device_entry, &device_list);}mutex_unlock(&device_list_lock);spidev->speed_hz = spi->max_speed_hz;if (status == 0)spi_set_drvdata(spi, spidev);elsekfree(spidev);return status;
}

用户就使用这里字符设备驱动程序提供的接口去操作 spidev 了,其中 spi 读写操作的实现都在其中

static const struct file_operations spidev_fops = {.owner =	THIS_MODULE,/* REVISIT switch to aio primitives, so that userspace* gets more complete API coverage.  It'll simplify things* too, except for the locking.*/.write =	spidev_write,.read =		spidev_read,.unlocked_ioctl = spidev_ioctl,.compat_ioctl = spidev_compat_ioctl,.open =		spidev_open,.release =	spidev_release,.llseek =	no_llseek,
};

内核应用程序参考代码

/ tools / spi / spidev_fdx.c

// SPDX-License-Identifier: GPL-2.0
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>#include <linux/types.h>
#include <linux/spi/spidev.h>static int verbose;static void do_read(int fd, int len)
{unsigned char	buf[32], *bp;int		status;/* read at least 2 bytes, no more than 32 */if (len < 2)len = 2;else if (len > sizeof(buf))len = sizeof(buf);memset(buf, 0, sizeof buf);status = read(fd, buf, len);if (status < 0) {perror("read");return;}if (status != len) {fprintf(stderr, "short read\n");return;}printf("read(%2d, %2d): %02x %02x,", len, status,buf[0], buf[1]);status -= 2;bp = buf + 2;while (status-- > 0)printf(" %02x", *bp++);printf("\n");
}static void do_msg(int fd, int len)
{struct spi_ioc_transfer	xfer[2];unsigned char		buf[32], *bp;int			status;memset(xfer, 0, sizeof xfer);memset(buf, 0, sizeof buf);if (len > sizeof buf)len = sizeof buf;buf[0] = 0xaa;xfer[0].tx_buf = (unsigned long)buf;xfer[0].len = 1;xfer[1].rx_buf = (unsigned long) buf;xfer[1].len = len;status = ioctl(fd, SPI_IOC_MESSAGE(2), xfer);if (status < 0) {perror("SPI_IOC_MESSAGE");return;}printf("response(%2d, %2d): ", len, status);for (bp = buf; len; len--)printf(" %02x", *bp++);printf("\n");
}static void dumpstat(const char *name, int fd)
{__u8	lsb, bits;__u32	mode, speed;if (ioctl(fd, SPI_IOC_RD_MODE32, &mode) < 0) {perror("SPI rd_mode");return;}if (ioctl(fd, SPI_IOC_RD_LSB_FIRST, &lsb) < 0) {perror("SPI rd_lsb_fist");return;}if (ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits) < 0) {perror("SPI bits_per_word");return;}if (ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed) < 0) {perror("SPI max_speed_hz");return;}printf("%s: spi mode 0x%x, %d bits %sper word, %d Hz max\n",name, mode, bits, lsb ? "(lsb first) " : "", speed);
}int main(int argc, char **argv)
{int		c;int		readcount = 0;int		msglen = 0;int		fd;const char	*name;while ((c = getopt(argc, argv, "hm:r:v")) != EOF) {switch (c) {case 'm':msglen = atoi(optarg);if (msglen < 0)goto usage;continue;case 'r':readcount = atoi(optarg);if (readcount < 0)goto usage;continue;case 'v':verbose++;continue;case 'h':case '?':
usage:fprintf(stderr,"usage: %s [-h] [-m N] [-r N] /dev/spidevB.D\n",argv[0]);return 1;}}if ((optind + 1) != argc)goto usage;name = argv[optind];fd = open(name, O_RDWR);if (fd < 0) {perror("open");return 1;}dumpstat(name, fd);if (msglen)do_msg(fd, msglen);if (readcount)do_read(fd, readcount);close(fd);return 0;
}

spi 发送源码解析

/ drivers / spi / spidev.c

/* Write-only message with current device setup */
static ssize_t
spidev_write(struct file *filp, const char __user *buf,size_t count, loff_t *f_pos)
{struct spidev_data	*spidev;ssize_t			status;unsigned long		missing;/* chipselect only toggles at start or end of operation */if (count > bufsiz)return -EMSGSIZE;spidev = filp->private_data;mutex_lock(&spidev->buf_lock);missing = copy_from_user(spidev->tx_buffer, buf, count);if (missing == 0)status = spidev_sync_write(spidev, count);elsestatus = -EFAULT;mutex_unlock(&spidev->buf_lock);return status;
}
static inline ssize_t
spidev_sync_write(struct spidev_data *spidev, size_t len)
{struct spi_transfer	t = {.tx_buf		= spidev->tx_buffer,.len		= len,.speed_hz	= spidev->speed_hz,};struct spi_message	m;spi_message_init(&m);spi_message_add_tail(&t, &m);return spidev_sync(spidev, &m);
}
static ssize_t
spidev_sync(struct spidev_data *spidev, struct spi_message *message)
{int status;struct spi_device *spi;spin_lock_irq(&spidev->spi_lock);spi = spidev->spi;spin_unlock_irq(&spidev->spi_lock);if (spi == NULL)status = -ESHUTDOWN;elsestatus = spi_sync(spi, message);if (status == 0)status = message->actual_length;return status;
}

/ drivers / spi / spi.c

/*** spi_sync - blocking/synchronous SPI data transfers* @spi: device with which data will be exchanged* @message: describes the data transfers* Context: can sleep** This call may only be used from a context that may sleep.  The sleep* is non-interruptible, and has no timeout.  Low-overhead controller* drivers may DMA directly into and out of the message buffers.** Note that the SPI device's chip select is active during the message,* and then is normally disabled between messages.  Drivers for some* frequently-used devices may want to minimize costs of selecting a chip,* by leaving it selected in anticipation that the next message will go* to the same chip.  (That may increase power usage.)** Also, the caller is guaranteeing that the memory associated with the* message will not be freed before this call returns.** Return: zero on success, else a negative error code.*/
int spi_sync(struct spi_device *spi, struct spi_message *message)
{int ret;mutex_lock(&spi->controller->bus_lock_mutex);ret = __spi_sync(spi, message);mutex_unlock(&spi->controller->bus_lock_mutex);return ret;
}
EXPORT_SYMBOL_GPL(spi_sync);

注意这里有 __spi_queued_transfer (把消息放入队列,帮你管理,后面调用__spi_pump_messages 发送消息)和spi_async_locked(自己实现 transfer,自己管理) 两种传输方式

static int __spi_sync(struct spi_device *spi, struct spi_message *message)
{DECLARE_COMPLETION_ONSTACK(done);int status;struct spi_controller *ctlr = spi->controller;unsigned long flags;status = __spi_validate(spi, message);if (status != 0)return status;message->complete = spi_complete;message->context = &done;message->spi = spi;SPI_STATISTICS_INCREMENT_FIELD(&ctlr->statistics, spi_sync);SPI_STATISTICS_INCREMENT_FIELD(&spi->statistics, spi_sync);/** If we're not using the legacy transfer method then we will* try to transfer in the calling context so special case.* This code would be less tricky if we could remove the* support for driver implemented message queues.*/if (ctlr->transfer == spi_queued_transfer) {spin_lock_irqsave(&ctlr->bus_lock_spinlock, flags);trace_spi_message_submit(message);status = __spi_queued_transfer(spi, message, false);spin_unlock_irqrestore(&ctlr->bus_lock_spinlock, flags);} else {status = spi_async_locked(spi, message);}if (status == 0) {/* Push out the messages in the calling context if we can */if (ctlr->transfer == spi_queued_transfer) {SPI_STATISTICS_INCREMENT_FIELD(&ctlr->statistics,spi_sync_immediate);SPI_STATISTICS_INCREMENT_FIELD(&spi->statistics,spi_sync_immediate);__spi_pump_messages(ctlr, false);}wait_for_completion(&done);status = message->status;}message->context = NULL;return status;
}

先看 spi_async_locked

static int spi_async_locked(struct spi_device *spi, struct spi_message *message)
{struct spi_controller *ctlr = spi->controller;int ret;unsigned long flags;ret = __spi_validate(spi, message);if (ret != 0)return ret;spin_lock_irqsave(&ctlr->bus_lock_spinlock, flags);ret = __spi_async(spi, message);spin_unlock_irqrestore(&ctlr->bus_lock_spinlock, flags);return ret;

在这里可以看到需要自己提供 transfer 函数,需要自己管理队列,自己触发传输

static int __spi_async(struct spi_device *spi, struct spi_message *message)
{struct spi_controller *ctlr = spi->controller;struct spi_transfer *xfer;/** Some controllers do not support doing regular SPI transfers. Return* ENOTSUPP when this is the case.*/if (!ctlr->transfer)return -ENOTSUPP;message->spi = spi;SPI_STATISTICS_INCREMENT_FIELD(&ctlr->statistics, spi_async);SPI_STATISTICS_INCREMENT_FIELD(&spi->statistics, spi_async);trace_spi_message_submit(message);if (!ctlr->ptp_sts_supported) {list_for_each_entry(xfer, &message->transfers, transfer_list) {xfer->ptp_sts_word_pre = 0;ptp_read_system_prets(xfer->ptp_sts);}}return ctlr->transfer(spi, message);
}

来看看__spi_queued_transfer,如果 transfer 函数是内核提供的话就会调用这个函数

static int __spi_queued_transfer(struct spi_device *spi,struct spi_message *msg,bool need_pump)
{struct spi_controller *ctlr = spi->controller;unsigned long flags;spin_lock_irqsave(&ctlr->queue_lock, flags);if (!ctlr->running) {spin_unlock_irqrestore(&ctlr->queue_lock, flags);return -ESHUTDOWN;}msg->actual_length = 0;msg->status = -EINPROGRESS;list_add_tail(&msg->queue, &ctlr->queue);if (!ctlr->busy && need_pump)kthread_queue_work(ctlr->kworker, &ctlr->pump_messages);spin_unlock_irqrestore(&ctlr->queue_lock, flags);return 0;
}

来看看前面的__spi_pump_messages,这里调用transfer_one_message传输一个消息,就是循环发送消息中的所有 xfer

/*** __spi_pump_messages - function which processes spi message queue* @ctlr: controller to process queue for* @in_kthread: true if we are in the context of the message pump thread** This function checks if there is any spi message in the queue that* needs processing and if so call out to the driver to initialize hardware* and transfer each message.** Note that it is called both from the kthread itself and also from* inside spi_sync(); the queue extraction handling at the top of the* function should deal with this safely.*/
static void __spi_pump_messages(struct spi_controller *ctlr, bool in_kthread)
{struct spi_transfer *xfer;struct spi_message *msg;bool was_busy = false;unsigned long flags;int ret;/* Lock queue */spin_lock_irqsave(&ctlr->queue_lock, flags);/* Make sure we are not already running a message */if (ctlr->cur_msg) {spin_unlock_irqrestore(&ctlr->queue_lock, flags);return;}/* If another context is idling the device then defer */if (ctlr->idling) {kthread_queue_work(ctlr->kworker, &ctlr->pump_messages);spin_unlock_irqrestore(&ctlr->queue_lock, flags);return;}/* Check if the queue is idle */if (list_empty(&ctlr->queue) || !ctlr->running) {if (!ctlr->busy) {spin_unlock_irqrestore(&ctlr->queue_lock, flags);return;}/* Defer any non-atomic teardown to the thread */if (!in_kthread) {if (!ctlr->dummy_rx && !ctlr->dummy_tx &&!ctlr->unprepare_transfer_hardware) {spi_idle_runtime_pm(ctlr);ctlr->busy = false;trace_spi_controller_idle(ctlr);} else {kthread_queue_work(ctlr->kworker,&ctlr->pump_messages);}spin_unlock_irqrestore(&ctlr->queue_lock, flags);return;}ctlr->busy = false;ctlr->idling = true;spin_unlock_irqrestore(&ctlr->queue_lock, flags);kfree(ctlr->dummy_rx);ctlr->dummy_rx = NULL;kfree(ctlr->dummy_tx);ctlr->dummy_tx = NULL;if (ctlr->unprepare_transfer_hardware &&ctlr->unprepare_transfer_hardware(ctlr))dev_err(&ctlr->dev,"failed to unprepare transfer hardware\n");spi_idle_runtime_pm(ctlr);trace_spi_controller_idle(ctlr);spin_lock_irqsave(&ctlr->queue_lock, flags);ctlr->idling = false;spin_unlock_irqrestore(&ctlr->queue_lock, flags);return;}/* Extract head of queue */msg = list_first_entry(&ctlr->queue, struct spi_message, queue);ctlr->cur_msg = msg;list_del_init(&msg->queue);if (ctlr->busy)was_busy = true;elsectlr->busy = true;spin_unlock_irqrestore(&ctlr->queue_lock, flags);mutex_lock(&ctlr->io_mutex);if (!was_busy && ctlr->auto_runtime_pm) {ret = pm_runtime_resume_and_get(ctlr->dev.parent);if (ret < 0) {dev_err(&ctlr->dev, "Failed to power device: %d\n",ret);mutex_unlock(&ctlr->io_mutex);return;}}if (!was_busy)trace_spi_controller_busy(ctlr);if (!was_busy && ctlr->prepare_transfer_hardware) {ret = ctlr->prepare_transfer_hardware(ctlr);if (ret) {dev_err(&ctlr->dev,"failed to prepare transfer hardware: %d\n",ret);if (ctlr->auto_runtime_pm)pm_runtime_put(ctlr->dev.parent);msg->status = ret;spi_finalize_current_message(ctlr);mutex_unlock(&ctlr->io_mutex);return;}}trace_spi_message_start(msg);if (ctlr->prepare_message) {ret = ctlr->prepare_message(ctlr, msg);if (ret) {dev_err(&ctlr->dev, "failed to prepare message: %d\n",ret);msg->status = ret;spi_finalize_current_message(ctlr);goto out;}ctlr->cur_msg_prepared = true;}ret = spi_map_msg(ctlr, msg);if (ret) {msg->status = ret;spi_finalize_current_message(ctlr);goto out;}if (!ctlr->ptp_sts_supported && !ctlr->transfer_one) {list_for_each_entry(xfer, &msg->transfers, transfer_list) {xfer->ptp_sts_word_pre = 0;ptp_read_system_prets(xfer->ptp_sts);}}ret = ctlr->transfer_one_message(ctlr, msg);if (ret) {dev_err(&ctlr->dev,"failed to transfer one message from queue: %d\n",ret);goto out;}out:mutex_unlock(&ctlr->io_mutex);/* Prod the scheduler in case transfer_one() was busy waiting */if (!ret)cond_resched();
}

spi master 驱动分析

/ drivers / spi / spi-gpio.c

首先看 probe 函数,这里先分配一个 spi_master 结构体

static int spi_gpio_probe(struct platform_device *pdev)
{int				status;struct spi_master		*master;struct spi_gpio			*spi_gpio;struct device			*dev = &pdev->dev;struct spi_bitbang		*bb;master = devm_spi_alloc_master(dev, sizeof(*spi_gpio));if (!master)return -ENOMEM;if (pdev->dev.of_node)status = spi_gpio_probe_dt(pdev, master);elsestatus = spi_gpio_probe_pdata(pdev, master);if (status)return status;spi_gpio = spi_master_get_devdata(master);status = spi_gpio_request(dev, spi_gpio);if (status)return status;master->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 32);master->mode_bits = SPI_3WIRE | SPI_3WIRE_HIZ | SPI_CPHA | SPI_CPOL |SPI_CS_HIGH | SPI_LSB_FIRST;if (!spi_gpio->mosi) {/* HW configuration without MOSI pin** No setting SPI_MASTER_NO_RX here - if there is only* a MOSI pin connected the host can still do RX by* changing the direction of the line.*/master->flags = SPI_MASTER_NO_TX;}master->bus_num = pdev->id;master->setup = spi_gpio_setup;master->cleanup = spi_gpio_cleanup;bb = &spi_gpio->bitbang;bb->master = master;/** There is some additional business, apart from driving the CS GPIO* line, that we need to do on selection. This makes the local* callback for chipselect always get called.*/master->flags |= SPI_MASTER_GPIO_SS;bb->chipselect = spi_gpio_chipselect;bb->set_line_direction = spi_gpio_set_direction;if (master->flags & SPI_MASTER_NO_TX) {bb->txrx_word[SPI_MODE_0] = spi_gpio_spec_txrx_word_mode0;bb->txrx_word[SPI_MODE_1] = spi_gpio_spec_txrx_word_mode1;bb->txrx_word[SPI_MODE_2] = spi_gpio_spec_txrx_word_mode2;bb->txrx_word[SPI_MODE_3] = spi_gpio_spec_txrx_word_mode3;} else {bb->txrx_word[SPI_MODE_0] = spi_gpio_txrx_word_mode0;bb->txrx_word[SPI_MODE_1] = spi_gpio_txrx_word_mode1;bb->txrx_word[SPI_MODE_2] = spi_gpio_txrx_word_mode2;bb->txrx_word[SPI_MODE_3] = spi_gpio_txrx_word_mode3;}bb->setup_transfer = spi_bitbang_setup_transfer;status = spi_bitbang_init(&spi_gpio->bitbang);if (status)return status;return devm_spi_register_master(&pdev->dev, master);
}

这里最核心的就是 bitbang对于 消息收发处理的设置

来看看 bitbang 怎么获取的

/ include / linux / spi / spi.h

#define spi_master_get_devdata(_ctlr)	spi_controller_get_devdata(_ctlr)
static inline void *spi_controller_get_devdata(struct spi_controller *ctlr)
{return dev_get_drvdata(&ctlr->dev);
}

/ include / linux / device.h

static inline void *dev_get_drvdata(const struct device *dev)
{return dev->driver_data;
}

来看看 bitbang 的具体处理

/ drivers / spi / spi-gpio.c​​​​​​​

/ drivers / spi / spi-bitbang.c

可以看到这里对txrx_bufs 进行了赋值,对transfer_one 也进行了赋值

int spi_bitbang_init(struct spi_bitbang *bitbang)
{struct spi_master *master = bitbang->master;bool custom_cs;if (!master)return -EINVAL;/** We only need the chipselect callback if we are actually using it.* If we just use GPIO descriptors, it is surplus. If the* SPI_MASTER_GPIO_SS flag is set, we always need to call the* driver-specific chipselect routine.*/custom_cs = (!master->use_gpio_descriptors ||(master->flags & SPI_MASTER_GPIO_SS));if (custom_cs && !bitbang->chipselect)return -EINVAL;mutex_init(&bitbang->lock);if (!master->mode_bits)master->mode_bits = SPI_CPOL | SPI_CPHA | bitbang->flags;if (master->transfer || master->transfer_one_message)return -EINVAL;master->prepare_transfer_hardware = spi_bitbang_prepare_hardware;master->unprepare_transfer_hardware = spi_bitbang_unprepare_hardware;master->transfer_one = spi_bitbang_transfer_one;/** When using GPIO descriptors, the ->set_cs() callback doesn't even* get called unless SPI_MASTER_GPIO_SS is set.*/if (custom_cs)master->set_cs = spi_bitbang_set_cs;if (!bitbang->txrx_bufs) {bitbang->use_dma = 0;bitbang->txrx_bufs = spi_bitbang_bufs;if (!master->setup) {if (!bitbang->setup_transfer)bitbang->setup_transfer =spi_bitbang_setup_transfer;master->setup = spi_bitbang_setup;master->cleanup = spi_bitbang_cleanup;}}return 0;
}
EXPORT_SYMBOL_GPL(spi_bitbang_init);

这里就是正真硬件相关读写了

static int spi_bitbang_bufs(struct spi_device *spi, struct spi_transfer *t)
{struct spi_bitbang_cs	*cs = spi->controller_state;unsigned		nsecs = cs->nsecs;struct spi_bitbang	*bitbang;bitbang = spi_master_get_devdata(spi->master);if (bitbang->set_line_direction) {int err;err = bitbang->set_line_direction(spi, !!(t->tx_buf));if (err < 0)return err;}if (spi->mode & SPI_3WIRE) {unsigned flags;flags = t->tx_buf ? SPI_MASTER_NO_RX : SPI_MASTER_NO_TX;return cs->txrx_bufs(spi, cs->txrx_word, nsecs, t, flags);}return cs->txrx_bufs(spi, cs->txrx_word, nsecs, t, 0);
}

来看看spi_bitbang_transfer_one,可以看到这里面调用了txrx_bufs

static int spi_bitbang_transfer_one(struct spi_master *master,struct spi_device *spi,struct spi_transfer *transfer)
{struct spi_bitbang *bitbang = spi_master_get_devdata(master);int status = 0;if (bitbang->setup_transfer) {status = bitbang->setup_transfer(spi, transfer);if (status < 0)goto out;}if (transfer->len)status = bitbang->txrx_bufs(spi, transfer);if (status == transfer->len)status = 0;else if (status >= 0)status = -EREMOTEIO;out:spi_finalize_current_transfer(master);return status;
}

前面提到的transfer_one_message 是在 controller 队列初始化时赋值的

/ drivers / spi / spi.c

static int spi_controller_initialize_queue(struct spi_controller *ctlr)
{int ret;ctlr->transfer = spi_queued_transfer;if (!ctlr->transfer_one_message)ctlr->transfer_one_message = spi_transfer_one_message;/* Initialize and start queue */ret = spi_init_queue(ctlr);if (ret) {dev_err(&ctlr->dev, "problem initializing queue\n");goto err_init_queue;}ctlr->queued = true;ret = spi_start_queue(ctlr);if (ret) {dev_err(&ctlr->dev, "problem starting queue\n");goto err_start_queue;}return 0;err_start_queue:spi_destroy_queue(ctlr);
err_init_queue:return ret;
}

可以看到这里调用了transfer_one

/** spi_transfer_one_message - Default implementation of transfer_one_message()** This is a standard implementation of transfer_one_message() for* drivers which implement a transfer_one() operation.  It provides* standard handling of delays and chip select management.*/
static int spi_transfer_one_message(struct spi_controller *ctlr,struct spi_message *msg)
{struct spi_transfer *xfer;bool keep_cs = false;int ret = 0;struct spi_statistics *statm = &ctlr->statistics;struct spi_statistics *stats = &msg->spi->statistics;spi_set_cs(msg->spi, true, false);SPI_STATISTICS_INCREMENT_FIELD(statm, messages);SPI_STATISTICS_INCREMENT_FIELD(stats, messages);list_for_each_entry(xfer, &msg->transfers, transfer_list) {trace_spi_transfer_start(msg, xfer);spi_statistics_add_transfer_stats(statm, xfer, ctlr);spi_statistics_add_transfer_stats(stats, xfer, ctlr);if (!ctlr->ptp_sts_supported) {xfer->ptp_sts_word_pre = 0;ptp_read_system_prets(xfer->ptp_sts);}if ((xfer->tx_buf || xfer->rx_buf) && xfer->len) {reinit_completion(&ctlr->xfer_completion);fallback_pio:ret = ctlr->transfer_one(ctlr, msg->spi, xfer);if (ret < 0) {if (ctlr->cur_msg_mapped &&(xfer->error & SPI_TRANS_FAIL_NO_START)) {__spi_unmap_msg(ctlr, msg);ctlr->fallback = true;xfer->error &= ~SPI_TRANS_FAIL_NO_START;goto fallback_pio;}SPI_STATISTICS_INCREMENT_FIELD(statm,errors);SPI_STATISTICS_INCREMENT_FIELD(stats,errors);dev_err(&msg->spi->dev,"SPI transfer failed: %d\n", ret);goto out;}if (ret > 0) {ret = spi_transfer_wait(ctlr, msg, xfer);if (ret < 0)msg->status = ret;}} else {if (xfer->len)dev_err(&msg->spi->dev,"Bufferless transfer has length %u\n",xfer->len);}if (!ctlr->ptp_sts_supported) {ptp_read_system_postts(xfer->ptp_sts);xfer->ptp_sts_word_post = xfer->len;}trace_spi_transfer_stop(msg, xfer);if (msg->status != -EINPROGRESS)goto out;spi_transfer_delay_exec(xfer);if (xfer->cs_change) {if (list_is_last(&xfer->transfer_list,&msg->transfers)) {keep_cs = true;} else {spi_set_cs(msg->spi, false, false);_spi_transfer_cs_change_delay(msg, xfer);spi_set_cs(msg->spi, true, false);}}msg->actual_length += xfer->len;}out:if (ret != 0 || !keep_cs)spi_set_cs(msg->spi, false, false);if (msg->status == -EINPROGRESS)msg->status = ret;if (msg->status && ctlr->handle_err)ctlr->handle_err(ctlr, msg);spi_finalize_current_message(ctlr);return ret;
}

spi slave 和 master 区别

master 是主动发送数据,slave 是被动接收数据(先填充数据,等待传输)

master 的 SS、SCK 是输出引脚,slave 的是输入引脚

slave 要使能接收中断,有数据来了会触发中断

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/24089.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Visual Studio 中的 /MD 与 /MT、动态库与静态库的深入解析

文章目录 1. /MD 与 /MT 的区别1.3 调试版本1.4 注意事项 2. 动态库与静态库的联系与区别2.3 联系与区别 3. 结合你的错误分析3.1 错误原因3.2 解决方案3.3 经验教训 4. 总结 在 Visual Studio 中进行 C/C 项目开发时&#xff0c;开发者经常需要对运行时库选项&#xff08;例如…

蓝桥杯备考:贪心算法之矩阵消除游戏

这道题是牛客上的一道题&#xff0c;它呢和我们之前的排座位游戏非常之相似&#xff0c;但是&#xff0c;排座位问题选择行和列是不会改变元素的值的&#xff0c;这道题呢每每选一行都会把这行或者这列清零&#xff0c;所以我们的策略就是先用二进制把选择所有行的情况全部枚举…

绿色节能|暴雨服务器荣获液冷装置新专利授权

近日&#xff0c;暴雨信息宣布“液冷装置及电子设备”专利正式获批&#xff0c;标志着暴雨信息在电子设备散热领域取得重大技术突破&#xff0c;为数据中心及高性能电子设备的散热需求提供了创新性解决方案。 双重密封零漏液保障 传统冷板技术存在液体泄漏风险&#xff0c;严…

萌新学 Python 之闭包函数

闭包函数&#xff1a;在一个函数体内嵌套函数&#xff0c;是一个函数对象&#xff0c;允许在内部函数中修改或引用外部函数的变量 闭包函数对数据有封存功能 闭包函数需要满足以下几个条件&#xff1a; 1.函数必须有一个嵌套函数&#xff0c;在定义函数时&#xff0c;内部再…

【Python修仙编程】(二) Python3灵源初探(2)

第一部分&#xff1a;林羽的修仙之旅——字符串与布尔类型的修炼 林羽站在练气期一阶的起点&#xff0c;望着手中的《Python无极心法》秘籍&#xff0c;心中充满了期待。师傅玄天真人在一旁微笑着说道&#xff1a;“林羽&#xff0c;今天我们要修炼的是‘字符串’和‘布尔类型…

AI大模型(四)基于Deepseek本地部署实现模型定制与调教

AI大模型&#xff08;四&#xff09;基于Deepseek本地部署实现模型定制与调教 DeepSeek开源大模型在榜单上以黑马之姿横扫多项评测&#xff0c;其社区热度指数暴涨、一跃成为近期内影响力最高的话题&#xff0c;这个来自中国团队的模型向世界证明&#xff1a;让每个普通人都能…

2.部署kafka:9092

官方文档&#xff1a;http://kafka.apache.org/documentation.html (虽然kafka中集成了zookeeper,但还是建议使用独立的zk集群) Kafka3台集群搭建环境&#xff1a; 操作系统: centos7 防火墙&#xff1a;全关 3台zookeeper集群内的机器&#xff0c;1台logstash 软件版本: …

IO进程 day05

IO进程 day05 9. 进程9. 9. 守护进程守护进程的特点守护进程创建步骤 10. 线程10.1. 线程的概念10.2. 进程和线程的区别10.2. 线程资源10.3. 线程的函数接口1. pthread_create-创建线程线程函数和普通函数的区别 2. pthread_exit3.线程资源回收函数join和detach的区别 获取线程…

数字IC低功耗后端设计实现之power gating和isolation技术

考虑低功耗设计需求&#xff0c;下图中间那个功能模块是需要做power domain的&#xff0c;即这个模块需要插MTCMOS。需要开启时&#xff0c;外面的VDD会和这个模块的LOCAL VDD形成通路&#xff0c;否则就是断开即power off状态。 这些低功耗设计实现经验&#xff0c;你真的懂了…

使用 Open3D 批量渲染并导出固定视角点云截图

一、前言 在三维点云处理与可视化中&#xff0c;固定视角批量生成点云渲染截图是一个常见的需求。例如&#xff0c;想要将同一系列的点云&#xff08;PCD 文件&#xff09;在同样的视角下生成序列图片&#xff0c;以便后续合成为视频或进行其他可视化演示。本文将介绍如何使用…

c++的继承

封装、继承和多态是c的三大特性&#xff0c;他们的关系甚为紧密 封装的概念简单易懂&#xff0c;其实就是将数据和操作数据的方法结合在一起&#xff0c;形成一个独立的单元&#xff08;类&#xff09;&#xff0c;通过访问控制符&#xff08;如private、protected和public&…

3dtiles平移旋转工具制作

3dtiles平移旋转缩放原理及可视化工具实现 背景 平时工作中&#xff0c;通过cesium平台来搭建一个演示场景是很常见的事情。一般来说&#xff0c;演示场景不需要多完善的功能&#xff0c;但是需要一批三维模型搭建&#xff0c;如厂房、电力设备、园区等。在实际搭建过程中&…

我是如何从 0 到 1 找到 Web3 工作的?

作者&#xff1a;Lotus的人生实验 关于我花了一个月的时间&#xff0c;从 0 到 1 学习 Web3 相关的知识和编程知识。然后找到了一个 Web3 创业公司实习的远程工作。 &#x1f447;&#x1f447;&#x1f447; 我的背景: 计算机科班&#xff0c;学历还可以(大厂门槛水平) 毕业工…

进程状态(R|S|D|t|T|X|Z)、僵尸进程及孤儿进程

文章目录 一.进程状态进程排队状态&#xff1a;运行、阻塞、挂起 二.Linux下的进程状态R 运行状态&#xff08;running&#xff09;S 睡眠状态&#xff08;sleeping)D 磁盘休眠状态&#xff08;Disk sleep&#xff09;t 停止、暂停状态(tracing stopped)T 停止、暂停状态(stopp…

为什么要将PDF转换为CSV?CSV是Excel吗?

在企业和数据管理的日常工作中&#xff0c;PDF文件和CSV文件承担着各自的任务。PDF通常用于传输和展示静态的文档&#xff0c;而CSV因其简洁、易操作的特性&#xff0c;广泛应用于数据存储和交换。如果需要从PDF中提取、分析或处理数据&#xff0c;转换为CSV格式可能是一个高效…

Starlink卫星动力学系统仿真建模第十讲-基于SMC和四元数的卫星姿态控制示例及Python实现

基于四元数与滑模控制的卫星姿态控制 一、基本原理 1. 四元数姿态表示 四元数运动学方程&#xff1a; 3. 滑模控制设计 二、代码实现&#xff08;Python&#xff09; 1. 四元数运算工具 import numpy as npdef quat_mult(q1, q2):"""四元数乘法""…

CSS—引入方式、选择器、复合选择器、文字控制属性、CSS特性

目录 CSS 1.引入方式 2.选择器 3.复合选择器 4.文字控制属性 5.CSS特性 CSS 层叠样式表&#xff0c;是一种样式表语言&#xff0c;用来描述HTML文档的呈现 书写时一般按照顺序&#xff1a;盒子模型属性—>文字样式—>圆角、阴影等修饰属性 1.引入方式 引入方式方…

OpenHarmony-4.基于dayu800 GPIO 实践(2)

基于dayu800 GPIO 进行开发 1.DAYU800开发板硬件接口 LicheePi 4A 板载 2x10pin 插针&#xff0c;其中有 16 个原生 IO&#xff0c;包括 6 个普通 IO&#xff0c;3 对串口&#xff0c;一个 SPI。TH1520 SOC 具有4个GPIO bank&#xff0c;每个bank最大有32个IO&#xff1a;  …

win11 24h2 远程桌面 频繁断开 已失去连接 2025

一、现象 Windows11自升级2025年2月补丁后版本号为系统版本是26100.3194&#xff0c;远程桌面频繁断开连接&#xff0c;尝试连接&#xff0c;尤其在连接旧的server2012 二、临时解决方案 目前经测试&#xff0c;在组策略中&#xff0c;远程桌面连接客户端&#xff0c;关闭客户…

rust学习笔记6-数组练习704. 二分查找

上次说到rust所有权看看它和其他语言比有什么优势&#xff0c;就以python为例 # Python3 def test():a [1, 3, -4, 7, 9]print(a[4])b a # 所有权没有发生转移del b[4]print(a[4]) # 由于b做了删除&#xff0c;导致a再度访问报数组越界if __name__ __main__:test() 运行结…