/* Get basic io resource and map it */ // 获取基本的 IO 资源并映射 mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); rs->regs = devm_ioremap_resource(&pdev->dev, mem); if (IS_ERR(rs->regs)) { ret = PTR_ERR(rs->regs); goto err_put_ctlr; } rs->base_addr_phy = mem->start;
if (!has_acpi_companion(&pdev->dev)) rs->apb_pclk = devm_clk_get(&pdev->dev, "apb_pclk"); if (IS_ERR(rs->apb_pclk)) { dev_err(&pdev->dev, "Failed to get apb_pclk\n"); ret = PTR_ERR(rs->apb_pclk); goto err_put_ctlr; }
if (!has_acpi_companion(&pdev->dev)) rs->spiclk = devm_clk_get(&pdev->dev, "spiclk"); if (IS_ERR(rs->spiclk)) { dev_err(&pdev->dev, "Failed to get spi_pclk\n"); ret = PTR_ERR(rs->spiclk); goto err_put_ctlr; }
rs->sclk_in = devm_clk_get_optional(&pdev->dev, "sclk_in"); if (IS_ERR(rs->sclk_in)) { dev_err(&pdev->dev, "Failed to get sclk_in\n"); ret = PTR_ERR(rs->sclk_in); goto err_put_ctlr; } // 启用 APB PCLK ret = clk_prepare_enable(rs->apb_pclk); if (ret < 0) { dev_err(&pdev->dev, "Failed to enable apb_pclk\n"); goto err_put_ctlr; } // 启用 SPI CLK ret = clk_prepare_enable(rs->spiclk); if (ret < 0) { dev_err(&pdev->dev, "Failed to enable spi_clk\n"); goto err_disable_apbclk; }
ret = clk_prepare_enable(rs->sclk_in); if (ret < 0) { dev_err(&pdev->dev, "Failed to enable sclk_in\n"); goto err_disable_spiclk; } // 禁用 SPI 芯片 spi_enable_chip(rs, false); // 获取平台中断资源 ret = platform_get_irq(pdev, 0); if (ret < 0) goto err_disable_sclk_in; // 请求中断 ret = devm_request_threaded_irq(&pdev->dev, ret, rockchip_spi_isr, NULL, IRQF_ONESHOT, dev_name(&pdev->dev), ctlr); if (ret) goto err_disable_sclk_in;
rs->dev = &pdev->dev;
rs->freq = clk_get_rate(rs->spiclk); if (!rs->freq) { ret = device_property_read_u32(&pdev->dev, "clock-frequency", &rs->freq); if (ret) { dev_warn(rs->dev, "Failed to get clock or clock-frequency property\n"); goto err_disable_sclk_in; } } // 读取接收采样延迟(以纳秒为单位) if (!device_property_read_u32(&pdev->dev, "rx-sample-delay-ns", &rsd_nsecs)) { /* rx sample delay is expressed in parent clock cycles (max 3) */ u32 rsd = DIV_ROUND_CLOSEST(rsd_nsecs * (rs->freq >> 8), 1000000000 >> 8); if (!rsd) { dev_warn(rs->dev, "%u Hz are too slow to express %u ns delay\n", rs->freq, rsd_nsecs); } elseif (rsd > CR0_RSD_MAX) { rsd = CR0_RSD_MAX; dev_warn(rs->dev, "%u Hz are too fast to express %u ns delay, clamping at %u ns\n", rs->freq, rsd_nsecs, CR0_RSD_MAX * 1000000000U / rs->freq); } rs->rsd = rsd; }
if (!device_property_read_u32(&pdev->dev, "csm", &csm)) { if (csm > CR0_CSM_ONE) { dev_warn(rs->dev, "The csm value %u exceeds the limit, clamping at %u\n", csm, CR0_CSM_ONE); csm = CR0_CSM_ONE; } rs->csm = csm; }
rs->version = readl_relaxed(rs->regs + ROCKCHIP_SPI_VERSION); rs->fifo_len = get_fifo_len(rs);// 获取 FIFO 长度 if (!rs->fifo_len) { dev_err(&pdev->dev, "Failed to get fifo length\n"); ret = -EINVAL; goto err_disable_sclk_in; } quirks_cfg = device_get_match_data(&pdev->dev); if (quirks_cfg) rs->max_baud_div_in_cpha = quirks_cfg->max_baud_div_in_cpha; // 设置并启用运行时电源管理 pm_runtime_set_active(&pdev->dev); pm_runtime_enable(&pdev->dev);
ctlr->auto_runtime_pm = true; ctlr->bus_num = pdev->id; ctlr->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LOOP | SPI_LSB_FIRST; if (slave_mode) { ctlr->mode_bits |= SPI_NO_CS; ctlr->slave_abort = rockchip_spi_slave_abort; } else { ctlr->flags = SPI_MASTER_GPIO_SS; ctlr->max_native_cs = ROCKCHIP_SPI_MAX_CS_NUM; /* * rk spi0 has two native cs, spi1..5 one cs only * if num-cs is missing in the dts, default to 1 */ if (device_property_read_u32(&pdev->dev, "num-cs", &num_cs)) num_cs = 1; ctlr->num_chipselect = num_cs; ctlr->use_gpio_descriptors = true; } ctlr->dev.of_node = pdev->dev.of_node; ctlr->bits_per_word_mask = SPI_BPW_MASK(16) | SPI_BPW_MASK(8) | SPI_BPW_MASK(4); ctlr->min_speed_hz = rs->freq / BAUDR_SCKDV_MAX; ctlr->max_speed_hz = min(rs->freq / BAUDR_SCKDV_MIN, MAX_SCLK_OUT);
ctlr->setup = rockchip_spi_setup; ctlr->set_cs = rockchip_spi_set_cs; ctlr->transfer_one = rockchip_spi_transfer_one; ctlr->max_transfer_size = rockchip_spi_max_transfer_size; ctlr->handle_err = rockchip_spi_handle_err; // 请求 TX DMA 通道 ctlr->dma_tx = dma_request_chan(rs->dev, "tx"); if (IS_ERR(ctlr->dma_tx)) { /* Check tx to see if we need defer probing driver */ if (PTR_ERR(ctlr->dma_tx) == -EPROBE_DEFER) { ret = -EPROBE_DEFER; goto err_disable_pm_runtime; } dev_warn(rs->dev, "Failed to request TX DMA channel\n"); ctlr->dma_tx = NULL; } // 请求 RX DMA 通道 ctlr->dma_rx = dma_request_chan(rs->dev, "rx"); if (IS_ERR(ctlr->dma_rx)) { if (PTR_ERR(ctlr->dma_rx) == -EPROBE_DEFER) { ret = -EPROBE_DEFER; goto err_free_dma_tx; } dev_warn(rs->dev, "Failed to request RX DMA channel\n"); ctlr->dma_rx = NULL; } // 如果 TX 和 RX DMA 通道均成功请求 if (ctlr->dma_tx && ctlr->dma_rx) { rs->dma_addr_tx = mem->start + ROCKCHIP_SPI_TXDR; rs->dma_addr_rx = mem->start + ROCKCHIP_SPI_RXDR; ctlr->can_dma = rockchip_spi_can_dma; }
intspi_register_controller(struct spi_controller *ctlr) { structdevice *dev = ctlr->dev.parent; structboardinfo *bi; int status; int id, first_dynamic;
if (!dev) return -ENODEV;
/* * Make sure all necessary hooks are implemented before registering * the SPI controller. */ /* * 在注册 SPI 控制器之前,确保所有必要的操作已实现 */ 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; } elseif (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; else first_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; } INIT_LIST_HEAD(&ctlr->queue); spin_lock_init(&ctlr->queue_lock); spin_lock_init(&ctlr->bus_lock_spinlock); mutex_init(&ctlr->bus_lock_mutex); mutex_init(&ctlr->io_mutex); 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)) { if (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; } else { /* Legacy code path for GPIOs from DT */ status = of_spi_get_gpio_numbers(ctlr); if (status) goto free_bus_id; } }
/* * 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; }
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"); } elseif (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);
/** * of_register_spi_devices() - Register child devices onto the SPI bus * @ctlr: Pointer to spi_controller device * * Registers an spi_device for each child node of controller node which * represents a valid SPI slave. */ staticvoidof_register_spi_devices(struct spi_controller *ctlr) { structspi_device *spi; structdevice_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);// 为该节点注册一个 SPI 设备 if (IS_ERR(spi)) {// 如果注册失败,记录警告信息并清除该节点的已填充标记 dev_warn(&ctlr->dev, "Failed to create SPI device for %pOF\n", nc); of_node_clear_flag(nc, OF_POPULATED); } } }
/* 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; }
/** * spi_write - SPI synchronous write * @spi: device to which data will be written * @buf: data buffer * @len: data buffer size * Context: can sleep * * This function writes the buffer @buf. * Callable only from contexts that can sleep. * * Return: zero on success, else a negative error code. */ staticinlineint spi_write(struct spi_device *spi, constvoid *buf, size_t len) { structspi_transfert = { .tx_buf = buf, .len = len, };
/** * struct spi_device - Controller side proxy for an SPI slave device * @dev: Driver model representation of the device. * @controller: SPI controller used with the device. * @master: Copy of controller, for backwards compatibility. * @max_speed_hz: Maximum clock rate to be used with this chip * (on this board); may be changed by the device's driver. * The spi_transfer.speed_hz can override this for each transfer. * @chip_select: Chipselect, distinguishing chips handled by @controller. * @mode: The spi mode defines how data is clocked out and in. * This may be changed by the device's driver. * The "active low" default for chipselect mode can be overridden * (by specifying SPI_CS_HIGH) as can the "MSB first" default for * each word in a transfer (by specifying SPI_LSB_FIRST). * @bits_per_word: Data transfers involve one or more words; word sizes * like eight or 12 bits are common. In-memory wordsizes are * powers of two bytes (e.g. 20 bit samples use 32 bits). * This may be changed by the device's driver, or left at the * default (0) indicating protocol words are eight bit bytes. * The spi_transfer.bits_per_word can override this for each transfer. * @rt: Make the pump thread real time priority. * @irq: Negative, or the number passed to request_irq() to receive * interrupts from this device. * @controller_state: Controller's runtime state * @controller_data: Board-specific definitions for controller, such as * FIFO initialization parameters; from board_info.controller_data * @modalias: Name of the driver to use with this device, or an alias * for that name. This appears in the sysfs "modalias" attribute * for driver coldplugging, and in uevents used for hotplugging * @driver_override: If the name of a driver is written to this attribute, then * the device will bind to the named driver and only the named driver. * @cs_gpio: LEGACY: gpio number of the chipselect line (optional, -ENOENT when * not using a GPIO line) use cs_gpiod in new drivers by opting in on * the spi_master. * @cs_gpiod: gpio descriptor of the chipselect line (optional, NULL when * not using a GPIO line) * @word_delay: delay to be inserted between consecutive * words of a transfer * * @statistics: statistics for the spi_device * * A @spi_device is used to interchange data between an SPI slave * (usually a discrete chip) and CPU memory. * * In @dev, the platform_data is used to hold information about this * device that's meaningful to the device's protocol driver, but not * to its controller. One example might be an identifier for a chip * variant with slightly different functionality; another might be * information about how this particular board wires the chip's pins. */ structspi_device { structdevicedev;// 通用设备模型的设备结构体 structspi_controller *controller;// 指向控制器的指针 structspi_controller *master;/* compatibility layer */// 兼容层,指向控制器的指针(与 controller 相同) u32 max_speed_hz;// 设备支持的最大速度(以赫兹为单位 u8 chip_select;// 片选编号 u8 bits_per_word;// 每个字的位数 bool rt; u32 mode;// SPI 模式配置(包括时钟相位和极性等) #define SPI_CPHA 0x01 /* clock phase */ #define SPI_CPOL 0x02 /* clock polarity */ #define SPI_MODE_0 (0|0) /* (original MicroWire) */ #define SPI_MODE_1 (0|SPI_CPHA) #define SPI_MODE_2 (SPI_CPOL|0) #define SPI_MODE_3 (SPI_CPOL|SPI_CPHA) #define SPI_CS_HIGH 0x04 /* chipselect active high? */ #define SPI_LSB_FIRST 0x08 /* per-word bits-on-wire */ #define SPI_3WIRE 0x10 /* SI/SO signals shared */ #define SPI_LOOP 0x20 /* loopback mode */ #define SPI_NO_CS 0x40 /* 1 dev/bus, no chipselect */ #define SPI_READY 0x80 /* slave pulls low to pause */ #define SPI_TX_DUAL 0x100 /* transmit with 2 wires */ #define SPI_TX_QUAD 0x200 /* transmit with 4 wires */ #define SPI_RX_DUAL 0x400 /* receive with 2 wires */ #define SPI_RX_QUAD 0x800 /* receive with 4 wires */ #define SPI_CS_WORD 0x1000 /* toggle cs after each word */ #define SPI_TX_OCTAL 0x2000 /* transmit with 8 wires */ #define SPI_RX_OCTAL 0x4000 /* receive with 8 wires */ #define SPI_3WIRE_HIZ 0x8000 /* high impedance turnaround */ int irq; void *controller_state; // 控制器状态的私有数据 void *controller_data; // 控制器数据的私有数据 char modalias[SPI_NAME_SIZE];// 设备别名 constchar *driver_override;// 驱动程序覆盖 int cs_gpio; /* LEGACY: chip select gpio */// 片选 GPIO 引脚 structgpio_desc *cs_gpiod;/* chip select gpio desc */// 片选 GPIO 引脚 structspi_delayword_delay;
/* the statistics */ structspi_statisticsstatistics;// 统计数据
/* * 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_read - SPI synchronous read * @spi: device from which data will be read * @buf: data buffer * @len: data buffer size * Context: can sleep * * This function reads the buffer @buf. * Callable only from contexts that can sleep. * * Return: zero on success, else a negative error code. */ staticinlineint spi_read(struct spi_device *spi, void *buf, size_t len) { structspi_transfert = { .rx_buf = buf, .len = len, };
structspi_transfer { /* it's ok if tx_buf == rx_buf (right?) * for MicroWire, one buffer must be null * buffers must work with dma_*map_single() calls, unless * spi_message.is_dma_mapped reports a pre-existing mapping */ constvoid *tx_buf;// 发送缓冲区 void *rx_buf;// 接收缓冲区 unsigned len;// 传输数据的长度
/** * spi_sync_transfer - synchronous SPI data transfer * @spi: device with which data will be exchanged * @xfers: An array of spi_transfers * @num_xfers: Number of items in the xfer array * Context: can sleep * * Does a synchronous SPI data transfer of the given spi_transfer array. * * For more specific semantics see spi_sync(). * * Return: zero on success, else a negative error code. */ staticinlineint spi_sync_transfer(struct spi_device *spi, struct spi_transfer *xfers, unsignedint num_xfers) { structspi_messagemsg; // 使用给定的传输初始化 SPI 消息 spi_message_init_with_transfers(&msg, xfers, num_xfers); // 同步方式发送 SPI 消息 return spi_sync(spi, &msg); }
/** * 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. */ intspi_sync(struct spi_device *spi, struct spi_message *message) { int ret; // 锁定 SPI 控制器的总线锁互斥体 mutex_lock(&spi->controller->bus_lock_mutex); // 执行同步 SPI 传输 ret = __spi_sync(spi, message); // 解锁 SPI 控制器的总线锁互斥体 mutex_unlock(&spi->controller->bus_lock_mutex);
/* 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);// 锁定总线锁旋转锁并保存中断标志
/** * __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. */ staticvoid __spi_pump_messages(struct spi_controller *ctlr, bool in_kthread) { structspi_transfer *xfer; structspi_message *msg; bool was_busy = false; unsignedlong flags; int ret;
/* 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; }
if (!was_busy && ctlr->auto_runtime_pm) { ret = pm_runtime_get_sync(ctlr->dev.parent); if (ret < 0) { pm_runtime_put_noidle(ctlr->dev.parent); 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);
/** * spi_write_then_read - SPI synchronous write followed by read * @spi: device with which data will be exchanged * @txbuf: data to be written (need not be dma-safe) * @n_tx: size of txbuf, in bytes * @rxbuf: buffer into which data will be read (need not be dma-safe) * @n_rx: size of rxbuf, in bytes * Context: can sleep * * This performs a half duplex MicroWire style transaction with the * device, sending txbuf and then reading rxbuf. The return value * is zero for success, else a negative errno status code. * This call may only be used from a context that may sleep. * * Parameters to this routine are always copied using a small buffer. * Performance-sensitive or bulk transfer code should instead use * spi_{async,sync}() calls with dma-safe buffers. * * Return: zero on success, else a negative error code. */ intspi_write_then_read(struct spi_device *spi, constvoid *txbuf, unsigned n_tx, void *rxbuf, unsigned n_rx) { staticDEFINE_MUTEX(lock);// 定义一个静态互斥锁
int status; structspi_messagemessage; structspi_transferx[2]; u8 *local_buf;
/* Use preallocated DMA-safe buffer if we can. We can't avoid * copying here, (as a pure convenience thing), but we can * keep heap costs out of the hot path unless someone else is * using the pre-allocated buffer or the transfer is too large. */ if ((n_tx + n_rx) > SPI_BUFSIZ || !mutex_trylock(&lock)) { local_buf = kmalloc(max((unsigned)SPI_BUFSIZ, n_tx + n_rx), GFP_KERNEL | GFP_DMA); if (!local_buf) return -ENOMEM; // 如果内存分配失败,返回错误码 } else { local_buf = buf; }
spidev_test 是一个用于测试和调试 SPI 设备的命令行工具,通常在 Linux 系统上使用,它允许用户直接通过 SPI 总线与设备进行通信,可以发送数据并接收来自设备的响应。spidev_test 源码位于topeet Linux 源码的 kernel/tools/spi 目录下,编译要交叉编译
1 2 3 4 5
make CC=/home/topeet/Linux/linux_sdk/prebuilts/gcc/linux-x86/aarch64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-lin ux-gnu/bin/aarch64-linux-gnu-gcc LD=/home/topeet/Linux/linux_sdk/prebuilts/gcc/linux-x86/aarch64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-lin ux-gnu/bin/aarch64-linux-gnu-ld
ifconfig -a # 回环测试 ip linkset can1 down ip linkset can1 type can bitrate 250000 ip linkset can1 type can loopback on ip linkset up can1 candump can1 -L & cansend can1 123#1122334455667788