提供一种刷新X410内部EMMC存储器的方法

USRP X410内部采用了16G的EMMC存储器,内有内核和文件系统。官方站[注1]提供了多个版本的EMMC映像文件,并提供了多种刷新方法[注2]。

1,如果内核还能运行只是文件系统破坏,可以从外接USB盘,之后使用mount挂载U盘,使dd指令恢复映像到EMMC。

2,如果EMMC损坏无法进入LINUX系统,按照官方的说明,将USB-OTG和调试口的TYPEC接口都接入电脑,可以使用JTAG模式下载并运行U-BOOT,输入指令使X410的内部EMMC存储器映射成一个移动硬盘,之后将映像烧写到这个影射的盘里。这个烧写方式再linux下可以使用dd指令,在win下可以使用专门烧写软件。

官方网页还提供了其他更加上层一些的烧写方式,我没有仔细分析。

无论第一种方法还是第二种方法,我们都是直接EMMC的扇区进行写,不同是第一种是X410进行写,而第二种是外接的用户电脑进行写。我就在想我们完全可以把X410当做一个开发板,之后自己写程序读扇区进行写,而要写的内容可以通过网口收过来。

我分析了X410的原理图,觉得应该可以像使用开发板一样玩起来实现上述功能。我首先配置了DDR4内存以及串口,对DDR4进行了检测,看到没有问题。之后配置了EMMC和GMAC,使用SDK的的LWIP例子运行起来了tcp echo server.这里注意X410使用的PHY收发器是KSZ9031,我从网上搜索了一下才找到了修改寄存器以支持SDK的LWIP的配置。

这里上几张X410的PS配置的截图。

下图是DDR4的配置。

下图是EMMC的配置,注意SD1实际可以不配置,因为在X410里面SD卡部分电路实际没有焊接~

串口和TTC设置:

网口设置

其中网口用到的PHY芯片是KSZ9031,来我们所做的X410克隆版本的原理图。

SDK中默认代码不支持,经过网络搜索我找到了支持的代码如下:

cpp 复制代码
u32_t phymapemac0[32];
u32_t phymapemac1[32];
static u32_t get_KSZ_phy_speed(XEmacPs *xemacpsp, u32_t phy_addr)
{
	xil_printf("Start PHY autonegotiation get_KSZ_phy_speed  \r\n");

	u16_t temp;
		u16_t control;
		u16_t status;
		u16_t status_speed;
		u32_t timeout_counter = 0;
		u32_t temp_speed;
		u32_t phyregtemp;
		u16_t phy_clk_delay_reg;
		u16_t phy_rx_delay_reg;

		xil_printf("Start PHY autonegotiation \r\n");

		XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_MMD_ACCESS_CONTROL_REG, 0x0002);//set up register address for MMD-Device Address 2h
		XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_MMD_ACCESS_ADDRESS_DATA_REG, 0x0008);//select register 08h for MMD-Device address 2h
		XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_MMD_ACCESS_CONTROL_REG, 0x4002);//select register data for MMD-Device Address 2h,Register 08h
		XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_MMD_ACCESS_ADDRESS_DATA_REG, 0x01ef);//default
		XEmacPs_PhyRead(xemacpsp, phy_addr, IEEE_MMD_ACCESS_ADDRESS_DATA_REG, &phy_clk_delay_reg);

		XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_MMD_ACCESS_CONTROL_REG, 0x0002);//set up register address for MMD-Device Address 2h
		XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_MMD_ACCESS_ADDRESS_DATA_REG, 0x0005);//select register 05h for MMD-Device address 2h
		XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_MMD_ACCESS_CONTROL_REG, 0x4002);
		//XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_MMD_ACCESS_ADDRESS_DATA_REG, 0x0000);//-0.42ns
		//XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_MMD_ACCESS_ADDRESS_DATA_REG, 0xcccc);//+0.3ns
		XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_MMD_ACCESS_ADDRESS_DATA_REG, 0x7777);//default
		XEmacPs_PhyRead(xemacpsp, phy_addr, IEEE_MMD_ACCESS_ADDRESS_DATA_REG, &phy_rx_delay_reg);


		xil_printf("The clk delay register is:%x\r\n",phy_clk_delay_reg);
		xil_printf("The rx delay register is:%x\r\n",phy_rx_delay_reg);


		//Auto-negotiation Advertisement reg
		XEmacPs_PhyRead(xemacpsp, phy_addr, IEEE_AUTONEGO_ADVERTISE_REG, &control);//reg 0x04
		control |= IEEE_ASYMMETRIC_PAUSE_MASK;//0x0800 流控
		control |= IEEE_PAUSE_MASK;//0x0400
		control |= ADVERTISE_100;
		control |= ADVERTISE_10;
		XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_AUTONEGO_ADVERTISE_REG, control);
		//1000Basic-T Control reg
		XEmacPs_PhyRead(xemacpsp, phy_addr, IEEE_1000_ADVERTISE_REG_OFFSET,
						&control);
		control |= ADVERTISE_1000;
		XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_1000_ADVERTISE_REG_OFFSET,
						control);


		//basic control
		XEmacPs_PhyRead(xemacpsp, phy_addr, IEEE_CONTROL_REG_OFFSET, &control);//reg 00
		control |= IEEE_CTRL_AUTONEGOTIATE_ENABLE;  //bit12
		control |= IEEE_STAT_AUTONEGOTIATE_RESTART; //bit9
		XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_CONTROL_REG_OFFSET, control);

		//basic control
		XEmacPs_PhyRead(xemacpsp, phy_addr, IEEE_CONTROL_REG_OFFSET, &control);
		control |= IEEE_CTRL_RESET_MASK;//software PHY reset,
		XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_CONTROL_REG_OFFSET, control);

		while (1) {
			XEmacPs_PhyRead(xemacpsp, phy_addr, IEEE_CONTROL_REG_OFFSET, &control);
			if (control & IEEE_CTRL_RESET_MASK)//this bit is self-cleared after a "1" is written to it
				continue;
			else
				break;
		}

		XEmacPs_PhyRead(xemacpsp, phy_addr, IEEE_STATUS_REG_OFFSET, &status);

		xil_printf("Waiting for PHY to complete autonegotiation.\r\n");

		while ( !(status & IEEE_STAT_AUTONEGOTIATE_COMPLETE) ) {
			sleep(1);
			XEmacPs_PhyRead(xemacpsp, phy_addr,
							IEEE_COPPER_SPECIFIC_STATUS_REG_2,  &temp);
			xil_printf("Link Status is:%x \r\n",temp);
			timeout_counter++;

			if (timeout_counter == 30) {
				xil_printf("Auto negotiation error \r\n");
				return ;
			}
			XEmacPs_PhyRead(xemacpsp, phy_addr, IEEE_STATUS_REG_OFFSET, &status);
		}
		xil_printf("autonegotiation complete \r\n");

		XEmacPs_PhyRead(xemacpsp, phy_addr,0x1f,
						&status_speed);

		if ( (status_speed & 0x40) == 0x40)/* 1000Mbps */
			return 1000;
		else if ( (status_speed & 0x20) == 0x20)/* 100Mbps */
			return 100;
		else if ( (status_speed & 0x10) == 0x10)/* 10Mbps */
			return 10;
		else
			return 0;
		return XST_SUCCESS;
 }

这里多了几个专门寄存器的设置,之后在获取速度的函数调用一下:

cpp 复制代码
static u32_t get_IEEE_phy_speed(XEmacPs *xemacpsp, u32_t phy_addr)
{
	u16_t phy_identity;
	u32_t RetStatus;

	XEmacPs_PhyRead(xemacpsp, phy_addr, PHY_IDENTIFIER_1_REG,
					&phy_identity);
	if (phy_identity == PHY_TI_IDENTIFIER) {
		RetStatus = get_TI_phy_speed(xemacpsp, phy_addr);
	} else if (phy_identity == PHY_REALTEK_IDENTIFIER) {
		RetStatus = get_Realtek_phy_speed(xemacpsp, phy_addr);
	} else {
		//RetStatus = get_Marvell_phy_speed(xemacpsp, phy_addr);
		RetStatus = get_KSZ_phy_speed(xemacpsp, phy_addr); //liwei
	}

	return RetStatus;
}

对扇区的读写用到下面的代码。

cpp 复制代码
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xsdps.h"

static XSdPs SdIn;

#define EMMC1_SD0 0

int sdio_init(int id )
{
	static int init = 0 ;
	if (init != 0) return 0  ; init = 1;
	XSdPs_Config *SdConfig;
	int Status;

	if(id==0) SdConfig = XSdPs_LookupConfig(XPAR_XSDPS_0_DEVICE_ID);
	else SdConfig = XSdPs_LookupConfig(XPAR_XSDPS_1_DEVICE_ID);
	if (NULL == SdConfig) {printf("error XSdPs_LookupConfig\r\n");return XST_FAILURE;}

	Status = XSdPs_CfgInitialize(&SdIn, SdConfig,SdConfig->BaseAddress);
	if (Status != XST_SUCCESS) {printf("error XSdPs_CfgInitialize\r\n");return XST_FAILURE;}

	Status = XSdPs_CardInitialize(&SdIn);
	if (Status != XST_SUCCESS) {printf("error XSdPs_CardInitialize\r\n");return XST_FAILURE;}
}

void read_sector(uint32_t sector, uint8_t*buffer)
{
	sdio_init(0);
	int Status = XSdPs_ReadPolled(&SdIn, sector, 1,buffer);
	if(Status != XST_SUCCESS){printf("Error readingsector %d\r\n",sector);}
}

void write_sector( uint32_t sector, const uint8_t *data)
{
	sdio_init(0);
	int Status = XSdPs_WritePolled(&SdIn, sector, 1, data);
	if(Status != XST_SUCCESS){printf("Error writingsector  %d\r\n",sector);}
}

关于EMMC或者SD卡扇区的读写,我同事小周试验成功并写过一篇blog[注4]。

以上硬件和板级支持软件都做好后就开始写正式的应用了。

我们再次梳理一下思路:

1,为了简单起见,我们使用TCP协议,这样就可以不必关心流的控制。

2,PC段负责读映像文件,不断地发送给X410。

3,X410每收到512就顺序写入扇区。

4,设置一个计数器,每写一个扇区就加一。这个计数器也需要清零。我们规定在最初连接上发发送一个32字节指示X410进行计数器清零,并要求得到X410的恢复确认。

思路很简单,我们看PC端代码:

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

#define SERVER_IP "192.168.5.136"  // 服务器 IP 地址
#define SERVER_PORT 7         // 服务器端口
#define BUFFER_SIZE 1024        // 缓冲区大小




#include <stdio.h>
#include <stdlib.h>

#define SECTOR_SIZE 512
#define N 1
//printf("sector=%d\r",sector++);

unsigned char * get_one_block( int len  )
{
    static  unsigned  int  buff[1024 ] ;
    static  unsigned  int  sbuff[1024 ] = {0} ;
    static FILE *fp=NULL;/// = fopen(file_name, "rb");


    static  unsigned int sector = 0 ;


    if (fp==NULL) {
        fp = fopen("usrp_x4xx_fs.sdimg", "rb");
        if (fp == NULL) {
            perror("Error opening file");
            return NULL;
        }
    }
    size_t bytesRead;
    if  ((bytesRead = fread(buff, 1, len, fp)) > 0) {
        return  buff ;
    }
    else
        return NULL ;
}



int  rcv_blocking ( int this_sock )
{
    unsigned char b [2048] ;
    int bytes_received = recv(this_sock, b, 2047, 0);
    if (bytes_received < 0) {
        perror("Receive failed");
        return -1;
    }
    return 0 ;
}


int main()
{
    int sock;
    struct sockaddr_in server_addr;
    char buffer[BUFFER_SIZE];

    // 创建 socket
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("Socket creation failed");
        exit(EXIT_FAILURE);
    }

    // 设置服务器地址结构
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT);

    // 将 IPv4 地址从文本转换为二进制
    if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) {
        perror("Invalid address/ Address not supported");
        exit(EXIT_FAILURE);
    }

    // 连接到服务器
    if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("Connection failed");
        exit(EXIT_FAILURE);
    }

    // 发送消息



    long long int i,d=0,m=-1,sm=0;
    int  bytes_received ;
    unsigned char *p;
    static unsigned char b[1024] ;
    send(sock,  b,32, 0);  //  reset sector counter
    rcv_blocking(sock) ;


    for(;;) {


        for (i=0; i<N; ++i) {
            p=get_one_block(SECTOR_SIZE);
            if (p==NULL)  exit(1);
            send(sock,  p,SECTOR_SIZE, 0);
        }

        d+=N*512 ;
        m= d/(1024*1024*10) ;
        if (sm!=m)  {
            sm=m;
            printf(  "%dM \n",m*10);
        }
        //   rcv_blocking(sock) ;




    }

    close(sock);
    return 0;
}

这里设置了N本打算发送多块之后再集中写入EMMC后应答给PC,后来觉得TCP自动实现应答确认,上位机一味发就可以,没必要考虑流控。

X410上我们使用SDK的LWIP TCP ECHO 服务器修改过来的,只需要修改一下收到数据的回调函数。

cpp 复制代码
err_t recv_callback(void *arg, struct tcp_pcb *tpcb,
                               struct pbuf *p, err_t err)
{
	/* do not read the packet if we are not in ESTABLISHED state */
	if (!p) {
		tcp_close(tpcb);
		tcp_recv(tpcb, NULL);
		return ERR_OK;
	}
	/* indicate that the packet has been received */
	tcp_recved(tpcb, p->len);
	if ((p->len!=1024 ) )	printf("len=%d not  1024! \n",p->len );
	if (p->len==32 ){  sector = 0 ; printf("len=%d\r" , p->len); tcp_write(tpcb, p->payload, 4, 1);pbuf_free(p); return ERR_OK;}
	if (p->len>=512 )write_sector( sector ++ , p->payload   ) ;
	if ( p->len==1024)	write_sector( sector ++ , p->payload+512   ) ;
///	printf("sector=%d  \r" ,sector);
	pbuf_free(p);
	return ERR_OK;
}

连续发送512的数据包,我在这个回调函数里面收到第一个发送过来的512字节的包,周都是1024的包,是两个包组合在一起发送了。(这期间我尝试修改修改成别的发送长度连续发送,最终每包也是1024字节,这个没有继续实验,在这里提一下。)

注意调试时候保留了上述代码的printf,打印出正在写的扇区号。发现这个占用了时间拉低了烧写速度。我用的是100M的交换机,保留printf烧写速度是2Mb/s,注释掉烧写速度是6Mb/s .

这个实现烧写速度比较慢,16G的映像文件烧写实测用了5个小时的样子(100M局域网,如果用更1G网应该快一点,但是估计也有限,考虑主要消耗在TCP的对话导致通讯效率比较低)。烧写完毕后确实启动成了。如果要求更高速度可以使用UDP,但是那要自己设计对话实现流控制。

设置X410运行在JTAG模式的命令:

1,SCU的命令里先输入reboot关掉ps.此时power灯是黄色。

2,输入zynqmp bootmode jtag.

3,按一下开关键,此时power灯是绿色,表示PS部分已经启动并进入jtag模式。

4,如果要退出jtag模式,我使用的方法是SCU命令执行reboot.之后从先上电。

这里所说的SCU就是X410实现BIOS功能的STM32,官方叫SCU。具体可以看看我另外一篇BLOG[注3]。

by :李伟

通过网盘分享的文件:BRUN_EMMC_RJ45.rar
链接: https://pan.baidu.com/s/15ci9CLgbKOnkPM8MkOYdgg 提取码: 66u1 

注:

1,files.ettus.com:/binaries/cache/x4xx/ 内有EMMC映像的官方链接。

2,USRP X410/X440 Getting Started Guide - Ettus Knowledge Base X410上手介绍

3,X410启动过程串口的显示-CSDN博客

4,TQRFSOC开发板47DR :EMMC和SD卡扇区读写_rfsoc47dr 开发语言-CSDN博客

相关推荐
bohu8340 分钟前
亚博microros小车-原生ubuntu支持系列:8-脸部检测与人脸特效
linux·opencv·ubuntu·dlib·microros·亚博
贾贾20233 小时前
配电自动化系统“三区四层”数字化架构
运维·科技·架构·自动化·能源·制造·智能硬件
小池先生4 小时前
grafana+prometheus监控linux指标
linux·grafana·prometheus
浮梦终焉4 小时前
【嵌入式】总结——Linux驱动开发(三)
linux·驱动开发·qt·嵌入式
远方 hi4 小时前
linux如何修改密码,要在CentOS 7系统中修改密码
linux·运维·服务器
练小杰5 小时前
Linux系统 C/C++编程基础——基于Qt的图形用户界面编程
linux·c语言·c++·经验分享·qt·学习·编辑器
资讯分享周6 小时前
过年远控家里电脑打游戏,哪款远控软件最好用?
运维·服务器·电脑
chaodaibing6 小时前
记录一次k8s起不来的排查过程
运维·服务器·k8s
黑客老李7 小时前
区块链 智能合约安全 | 回滚攻击
服务器·数据仓库·hive·hadoop·区块链·php·智能合约