tcp/quic lab finished
This commit is contained in:
6
network/arpicmplab/start/lib/xnet/CMakeLists.txt
Normal file
6
network/arpicmplab/start/lib/xnet/CMakeLists.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
include_directories(
|
||||
${PROJECT_SOURCE_DIR}/xnet/src/xnet/inc
|
||||
)
|
||||
|
||||
aux_source_directory(. DIR_HELLO_SRCS)
|
||||
add_library(xnet_lib ${DIR_HELLO_SRCS} )
|
||||
317
network/arpicmplab/start/lib/xnet/enc28j60_device.c
Normal file
317
network/arpicmplab/start/lib/xnet/enc28j60_device.c
Normal file
@@ -0,0 +1,317 @@
|
||||
#include <stdio.h>
|
||||
#include "enc28j60_device.h"
|
||||
|
||||
// 一共8KB的以太网缓存
|
||||
static u8 ENC28J60BANK;
|
||||
int NextPacketPtr;
|
||||
|
||||
|
||||
//delay for ms unit
|
||||
//suitable for crystal is 8MHz
|
||||
//static function for ENC28J60
|
||||
static void ENC28J60_delayms(u32 ms)
|
||||
{
|
||||
u16 i=0;
|
||||
while(ms--)
|
||||
{
|
||||
for(i=0;i<8000;i++);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void ENC28J60_cs_delayms(void)
|
||||
{
|
||||
}
|
||||
|
||||
//Reset ENC28J60
|
||||
//Initialize SPI2 and related I/O for ENC28J60
|
||||
static void ENC28J60_SPI2_Init(void)
|
||||
{
|
||||
NVIC_InitTypeDef NVIC_InitStructure;
|
||||
EXTI_InitTypeDef EXTI_InitStructure;
|
||||
SPI_InitTypeDef SPI_InitStructure;
|
||||
GPIO_InitTypeDef GPIO_InitStructure;
|
||||
RCC_APB1PeriphClockCmd( RCC_APB1Periph_SPI2, ENABLE );
|
||||
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB |RCC_APB2Periph_GPIOC, ENABLE );
|
||||
|
||||
// INT A7
|
||||
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
|
||||
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
|
||||
GPIO_Init(GPIOA, &GPIO_InitStructure);
|
||||
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
|
||||
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource7);
|
||||
|
||||
EXTI_InitStructure.EXTI_Line= EXTI_Line7;
|
||||
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
|
||||
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
|
||||
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
|
||||
EXTI_Init(&EXTI_InitStructure);
|
||||
|
||||
NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn; //使能外部中断所在的通道
|
||||
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级 2,
|
||||
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02; //子优先级 2
|
||||
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
|
||||
|
||||
NVIC_Init(&NVIC_InitStructure); //根据结构体信息进行优先级初始化
|
||||
|
||||
//CS pin
|
||||
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
|
||||
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
|
||||
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
|
||||
GPIO_Init(GPIOB, &GPIO_InitStructure);
|
||||
GPIO_SetBits(GPIOB,GPIO_Pin_12);
|
||||
|
||||
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
|
||||
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
|
||||
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
|
||||
GPIO_Init(GPIOB, &GPIO_InitStructure);
|
||||
GPIO_SetBits(GPIOB,GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15);
|
||||
|
||||
//RST pin
|
||||
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
|
||||
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
|
||||
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
|
||||
GPIO_Init(GPIOC, &GPIO_InitStructure);
|
||||
GPIO_SetBits(GPIOC,GPIO_Pin_5);
|
||||
|
||||
//setup SPI2
|
||||
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
|
||||
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
|
||||
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
|
||||
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
|
||||
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
|
||||
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
|
||||
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;
|
||||
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
|
||||
SPI_InitStructure.SPI_CRCPolynomial = 7;
|
||||
SPI_Init(SPI2, &SPI_InitStructure);
|
||||
|
||||
SPI_Cmd(SPI2, ENABLE);
|
||||
|
||||
SPI2_ReadWriteByte(0xff);
|
||||
}
|
||||
|
||||
void ENC28J60_Reset(void)
|
||||
{
|
||||
|
||||
ENC28J60_SPI2_Init(); //re-init SPI2
|
||||
ENC28J60_RST_CLEAR(); //reset ENC28J60
|
||||
ENC28J60_delayms(10);
|
||||
ENC28J60_RST_SET(); //finish reset
|
||||
ENC28J60_delayms(10);
|
||||
}
|
||||
|
||||
//Read ENC28J60 register
|
||||
//op: command
|
||||
//addr: register address
|
||||
//return: read out data
|
||||
u8 ENC28J60_Read_Op(u8 op,u8 addr)
|
||||
{
|
||||
u8 dat=0;
|
||||
|
||||
ENC28J60_SELECT();
|
||||
ENC28J60_cs_delayms();
|
||||
dat=op|(addr&ADDR_MASK);
|
||||
SPI2_ReadWriteByte(dat);
|
||||
dat=SPI2_ReadWriteByte(0xFF);
|
||||
//datasheet p.29, read two times to get MAC/MII register value
|
||||
if(addr&0x80)dat=SPI2_ReadWriteByte(0xFF);
|
||||
ENC28J60_cs_delayms();
|
||||
ENC28J60_NO_SELECT();
|
||||
return dat;
|
||||
}
|
||||
//Write ENC28J60 register
|
||||
//op: command
|
||||
//addr: register address
|
||||
//data: parameter to write
|
||||
void ENC28J60_Write_Op(u8 op,u8 addr,u8 data)
|
||||
{
|
||||
u8 dat = 0;
|
||||
ENC28J60_SELECT();
|
||||
ENC28J60_cs_delayms();
|
||||
dat=op|(addr&ADDR_MASK);
|
||||
SPI2_ReadWriteByte(dat);
|
||||
SPI2_ReadWriteByte(data);
|
||||
ENC28J60_cs_delayms();
|
||||
ENC28J60_NO_SELECT();
|
||||
}
|
||||
//Read Rx buffer data from ENC28J60
|
||||
//len: data length to read
|
||||
//data: pointer to store data
|
||||
void ENC28J60_Read_Buf(u32 len,u8* data)
|
||||
{
|
||||
ENC28J60_SELECT();
|
||||
ENC28J60_cs_delayms();
|
||||
SPI2_ReadWriteByte(ENC28J60_READ_BUF_MEM);
|
||||
while(len--) {
|
||||
*data++=(u8)SPI2_ReadWriteByte(0);
|
||||
}
|
||||
ENC28J60_cs_delayms();
|
||||
ENC28J60_NO_SELECT();
|
||||
}
|
||||
//Write data to send via ENC28J60
|
||||
//len: data length to send
|
||||
//data: data pointer
|
||||
void ENC28J60_Write_Buf(u32 len,u8* data)
|
||||
{
|
||||
ENC28J60_SELECT();
|
||||
ENC28J60_cs_delayms();
|
||||
SPI2_ReadWriteByte(ENC28J60_WRITE_BUF_MEM);
|
||||
while(len--)
|
||||
{
|
||||
SPI2_ReadWriteByte(*data++);
|
||||
}
|
||||
ENC28J60_cs_delayms();
|
||||
ENC28J60_NO_SELECT();
|
||||
}
|
||||
//Setup ENC28J60 register bank
|
||||
//ban: Bank to be setup
|
||||
void ENC28J60_Set_Bank(u8 bank)
|
||||
{
|
||||
if((bank&BANK_MASK)!=ENC28J60BANK)
|
||||
{
|
||||
ENC28J60_Write_Op(ENC28J60_BIT_FIELD_CLR,ECON1,(ECON1_BSEL1|ECON1_BSEL0));
|
||||
ENC28J60_Write_Op(ENC28J60_BIT_FIELD_SET,ECON1,(bank&BANK_MASK)>>5);
|
||||
ENC28J60BANK=(bank&BANK_MASK);
|
||||
}
|
||||
}
|
||||
//Read ENC28J60 register
|
||||
//addr: register address
|
||||
//return: read out value
|
||||
u8 ENC28J60_Read(u8 addr)
|
||||
{
|
||||
ENC28J60_Set_Bank(addr);//select bank
|
||||
return ENC28J60_Read_Op(ENC28J60_READ_CTRL_REG,addr);
|
||||
}
|
||||
//Write ENC28J60 register
|
||||
//addr: register address
|
||||
void ENC28J60_Write(u8 addr,u8 data)
|
||||
{
|
||||
ENC28J60_Set_Bank(addr);
|
||||
ENC28J60_Write_Op(ENC28J60_WRITE_CTRL_REG,addr,data);
|
||||
}
|
||||
//Write into PHY register of ENC28J60
|
||||
//addr: register address
|
||||
//data: parameter written into register
|
||||
void ENC28J60_PHY_Write(u8 addr,u32 data)
|
||||
{
|
||||
u16 retry=0;
|
||||
ENC28J60_Write(MIREGADR,addr);
|
||||
ENC28J60_Write(MIWRL,data);
|
||||
ENC28J60_Write(MIWRH,data>>8);
|
||||
while((ENC28J60_Read(MISTAT)&MISTAT_BUSY)&&retry<0XFFF)retry++;//wait until PHY writing finish
|
||||
}
|
||||
//Setup ENC28J60
|
||||
//macaddr: assigned MAC address
|
||||
//return: 0=success, 1=failed
|
||||
u8 ENC28J60_Init(u8* macaddr)
|
||||
{
|
||||
u16 retry=0;
|
||||
ENC28J60_Reset();
|
||||
ENC28J60_Write_Op(ENC28J60_SOFT_RESET,0,ENC28J60_SOFT_RESET); //software reset
|
||||
while(!(ENC28J60_Read(ESTAT)&ESTAT_CLKRDY)&&retry<500) //wait until clock is stable
|
||||
{
|
||||
retry++;
|
||||
ENC28J60_delayms(1);
|
||||
};
|
||||
if(retry>=500)return 1;//initialization failed
|
||||
//set Rx buffer address with 8k capacity
|
||||
NextPacketPtr=RXSTART_INIT;
|
||||
|
||||
//初始化接收缓冲区,设置接收起始地址
|
||||
ENC28J60_Write(ERXSTL,RXSTART_INIT&0xFF);
|
||||
ENC28J60_Write(ERXSTH,RXSTART_INIT>>8);
|
||||
|
||||
//设置接收读指针指向地址
|
||||
ENC28J60_Write(ERXRDPTL, RXSTART_INIT &0xFF);
|
||||
ENC28J60_Write(ERXRDPTH, RXSTART_INIT>>8);
|
||||
|
||||
//设置接收缓冲区的末尾地址
|
||||
ENC28J60_Write(ERXNDL,RXSTOP_INIT&0xFF);
|
||||
ENC28J60_Write(ERXNDH,RXSTOP_INIT>>8);
|
||||
|
||||
//设置发送缓冲区的起始地址
|
||||
ENC28J60_Write(ETXSTL,TXSTART_INIT&0xFF);
|
||||
ENC28J60_Write(ETXSTH,TXSTART_INIT>>8);
|
||||
//setup "eno of tx" byte
|
||||
ENC28J60_Write(ETXNDL,TXSTOP_INIT&0xFF);
|
||||
ENC28J60_Write(ETXNDH,TXSTOP_INIT>>8);
|
||||
|
||||
|
||||
ENC28J60_Write(ERXFCON, ERXFCON_UCEN|ERXFCON_CRCEN|ERXFCON_PMEN);
|
||||
|
||||
ENC28J60_Write(EPMM0,0x3f);
|
||||
ENC28J60_Write(EPMM1,0x30);
|
||||
ENC28J60_Write(EPMCSL,0xf9);
|
||||
ENC28J60_Write(EPMCSH,0xf7);
|
||||
|
||||
//MAC接收使能,下行程序段表示使能MAC接收,使能IEEE流量控制
|
||||
ENC28J60_Write(MACON1,MACON1_MARXEN|MACON1_TXPAUS|MACON1_RXPAUS);
|
||||
// bring MAC out of reset
|
||||
ENC28J60_Write(MACON2,0x00); //MACON2清零,让MAC退出复位状态
|
||||
// enable automatic padding to 60bytes and CRC operations
|
||||
ENC28J60_Write_Op(ENC28J60_BIT_FIELD_SET,MACON3,MACON3_PADCFG0|MACON3_TXCRCEN|MACON3_FRMLNEN|MACON3_FULDPX);
|
||||
// set inter-frame gap (non-back-to-back)
|
||||
ENC28J60_Write(MAIPGL,0x12);
|
||||
ENC28J60_Write(MAIPGH,0x0C);
|
||||
// set inter-frame gap (back-to-back)
|
||||
ENC28J60_Write(MABBIPG,0x15);
|
||||
// Set the maximum packet size which the controller will accept
|
||||
// Do not send packets longer than MAX_FRAMELEN:
|
||||
ENC28J60_Write(MAMXFLL,MAX_FRAMELEN&0xFF);
|
||||
ENC28J60_Write(MAMXFLH,MAX_FRAMELEN>>8);
|
||||
|
||||
// do bank 3 stuff
|
||||
// write MAC address
|
||||
// NOTE: MAC address in ENC28J60 is byte-backward
|
||||
ENC28J60_Write(MAADR5,macaddr[0]);
|
||||
ENC28J60_Write(MAADR4,macaddr[1]);
|
||||
ENC28J60_Write(MAADR3,macaddr[2]);
|
||||
ENC28J60_Write(MAADR2,macaddr[3]);
|
||||
ENC28J60_Write(MAADR1,macaddr[4]);
|
||||
ENC28J60_Write(MAADR0,macaddr[5]);
|
||||
|
||||
//setup PHY as Duplex
|
||||
ENC28J60_PHY_Write(PHCON1,PHCON1_PDPXMD);
|
||||
// no loopback of transmitted frames 禁止环回
|
||||
//HDLDIS:PHY 半双工环回禁止位
|
||||
ENC28J60_PHY_Write(PHCON2,PHCON2_HDLDIS);
|
||||
// switch to bank 0
|
||||
ENC28J60_Set_Bank(ECON1);
|
||||
// enable interrutps
|
||||
ENC28J60_Write_Op(ENC28J60_BIT_FIELD_SET,EIE,EIE_INTIE|EIE_PKTIE);
|
||||
// enable packet reception
|
||||
ENC28J60_Write_Op(ENC28J60_BIT_FIELD_SET,ECON1,ECON1_RXEN);
|
||||
if(ENC28J60_Read(MAADR5)== macaddr[0]) {
|
||||
ENC28J60_delayms(10);
|
||||
return 0;//initialization success
|
||||
}
|
||||
if(ENC28J60_Read(MAADR4)== macaddr[1]) {
|
||||
ENC28J60_delayms(10);
|
||||
return 0;//initialization success
|
||||
}
|
||||
if(ENC28J60_Read(MAADR3)== macaddr[2]) {
|
||||
ENC28J60_delayms(10);
|
||||
return 0;//initialization success
|
||||
}
|
||||
if(ENC28J60_Read(MAADR2)== macaddr[3]) {
|
||||
ENC28J60_delayms(10);
|
||||
return 0;//initialization success
|
||||
}
|
||||
if(ENC28J60_Read(MAADR1)== macaddr[4]) {
|
||||
ENC28J60_delayms(10);
|
||||
return 0;//initialization success
|
||||
}
|
||||
if(ENC28J60_Read(MAADR0)== macaddr[5]) {
|
||||
ENC28J60_delayms(10);
|
||||
return 0;//initialization success
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
//Read EREVID
|
||||
u8 ENC28J60_Get_EREVID(void)
|
||||
{
|
||||
return ENC28J60_Read(EREVID);
|
||||
}
|
||||
|
||||
|
||||
294
network/arpicmplab/start/lib/xnet/enc28j60_device.h
Normal file
294
network/arpicmplab/start/lib/xnet/enc28j60_device.h
Normal file
@@ -0,0 +1,294 @@
|
||||
|
||||
#ifndef _ENC28J60_H
|
||||
#define _ENC28J60_H
|
||||
|
||||
//Include
|
||||
#include "stm32f10x.h"
|
||||
#include "stm32f10x_conf.h"
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// ENC28J60 Control Registers
|
||||
// Control register definitions are a combination of address,
|
||||
// bank number, and Ethernet/MAC/PHY indicator bits.
|
||||
// - Register address (bits 0-4)
|
||||
// - Bank number (bits 5-6)
|
||||
// - MAC/PHY indicator (bit 7)
|
||||
#define ADDR_MASK 0x1F
|
||||
#define BANK_MASK 0x60
|
||||
#define SPRD_MASK 0x80
|
||||
// All-bank registers
|
||||
#define EIE 0x1B
|
||||
#define EIR 0x1C
|
||||
#define ESTAT 0x1D
|
||||
#define ECON2 0x1E
|
||||
#define ECON1 0x1F
|
||||
// Bank 0 registers
|
||||
#define ERDPTL (0x00|0x00)
|
||||
#define ERDPTH (0x01|0x00)
|
||||
#define EWRPTL (0x02|0x00)
|
||||
#define EWRPTH (0x03|0x00)
|
||||
#define ETXSTL (0x04|0x00)
|
||||
#define ETXSTH (0x05|0x00)
|
||||
#define ETXNDL (0x06|0x00)
|
||||
#define ETXNDH (0x07|0x00)
|
||||
#define ERXSTL (0x08|0x00)
|
||||
#define ERXSTH (0x09|0x00)
|
||||
#define ERXNDL (0x0A|0x00)
|
||||
#define ERXNDH (0x0B|0x00)
|
||||
//ERXWRPTH:ERXWRPTL 寄存器定义硬件向FIFO 中
|
||||
//的哪个位置写入其接收到的字节。 指针是只读的,在成
|
||||
//功接收到一个数据包后,硬件会自动更新指针。 指针可
|
||||
//用于判断FIFO 内剩余空间的大小。
|
||||
#define ERXRDPTL (0x0C|0x00)
|
||||
#define ERXRDPTH (0x0D|0x00)
|
||||
#define ERXWRPTL (0x0E|0x00)
|
||||
#define ERXWRPTH (0x0F|0x00)
|
||||
#define EDMASTL (0x10|0x00)
|
||||
#define EDMASTH (0x11|0x00)
|
||||
#define EDMANDL (0x12|0x00)
|
||||
#define EDMANDH (0x13|0x00)
|
||||
#define EDMADSTL (0x14|0x00)
|
||||
#define EDMADSTH (0x15|0x00)
|
||||
#define EDMACSL (0x16|0x00)
|
||||
#define EDMACSH (0x17|0x00)
|
||||
// Bank 1 registers
|
||||
#define EHT0 (0x00|0x20)
|
||||
#define EHT1 (0x01|0x20)
|
||||
#define EHT2 (0x02|0x20)
|
||||
#define EHT3 (0x03|0x20)
|
||||
#define EHT4 (0x04|0x20)
|
||||
#define EHT5 (0x05|0x20)
|
||||
#define EHT6 (0x06|0x20)
|
||||
#define EHT7 (0x07|0x20)
|
||||
#define EPMM0 (0x08|0x20)
|
||||
#define EPMM1 (0x09|0x20)
|
||||
#define EPMM2 (0x0A|0x20)
|
||||
#define EPMM3 (0x0B|0x20)
|
||||
#define EPMM4 (0x0C|0x20)
|
||||
#define EPMM5 (0x0D|0x20)
|
||||
#define EPMM6 (0x0E|0x20)
|
||||
#define EPMM7 (0x0F|0x20)
|
||||
#define EPMCSL (0x10|0x20)
|
||||
#define EPMCSH (0x11|0x20)
|
||||
#define EPMOL (0x14|0x20)
|
||||
#define EPMOH (0x15|0x20)
|
||||
#define EWOLIE (0x16|0x20)
|
||||
#define EWOLIR (0x17|0x20)
|
||||
#define ERXFCON (0x18|0x20)
|
||||
#define EPKTCNT (0x19|0x20)
|
||||
// Bank 2 registers
|
||||
#define MACON1 (0x00|0x40|0x80)
|
||||
#define MACON2 (0x01|0x40|0x80)
|
||||
#define MACON3 (0x02|0x40|0x80)
|
||||
#define MACON4 (0x03|0x40|0x80)
|
||||
#define MABBIPG (0x04|0x40|0x80)
|
||||
#define MAIPGL (0x06|0x40|0x80)
|
||||
#define MAIPGH (0x07|0x40|0x80)
|
||||
#define MACLCON1 (0x08|0x40|0x80)
|
||||
#define MACLCON2 (0x09|0x40|0x80)
|
||||
#define MAMXFLL (0x0A|0x40|0x80)
|
||||
#define MAMXFLH (0x0B|0x40|0x80)
|
||||
#define MAPHSUP (0x0D|0x40|0x80)
|
||||
#define MICON (0x11|0x40|0x80)
|
||||
#define MICMD (0x12|0x40|0x80)
|
||||
#define MIREGADR (0x14|0x40|0x80)
|
||||
#define MIWRL (0x16|0x40|0x80)
|
||||
#define MIWRH (0x17|0x40|0x80)
|
||||
#define MIRDL (0x18|0x40|0x80)
|
||||
#define MIRDH (0x19|0x40|0x80)
|
||||
// Bank 3 registers
|
||||
#define MAADR1 (0x00|0x60|0x80)
|
||||
#define MAADR0 (0x01|0x60|0x80)
|
||||
#define MAADR3 (0x02|0x60|0x80)
|
||||
#define MAADR2 (0x03|0x60|0x80)
|
||||
#define MAADR5 (0x04|0x60|0x80)
|
||||
#define MAADR4 (0x05|0x60|0x80)
|
||||
#define EBSTSD (0x06|0x60)
|
||||
#define EBSTCON (0x07|0x60)
|
||||
#define EBSTCSL (0x08|0x60)
|
||||
#define EBSTCSH (0x09|0x60)
|
||||
#define MISTAT (0x0A|0x60|0x80)
|
||||
#define EREVID (0x12|0x60)
|
||||
#define ECOCON (0x15|0x60)
|
||||
#define EFLOCON (0x17|0x60)
|
||||
#define EPAUSL (0x18|0x60)
|
||||
#define EPAUSH (0x19|0x60)
|
||||
// PHY registers
|
||||
#define PHCON1 0x00
|
||||
#define PHSTAT1 0x01
|
||||
#define PHHID1 0x02
|
||||
#define PHHID2 0x03
|
||||
#define PHCON2 0x10
|
||||
#define PHSTAT2 0x11
|
||||
#define PHIE 0x12
|
||||
#define PHIR 0x13
|
||||
#define PHLCON 0x14
|
||||
// ENC28J60 ERXFCON Register Bit Definitions
|
||||
#define ERXFCON_UCEN 0x80
|
||||
#define ERXFCON_ANDOR 0x40
|
||||
#define ERXFCON_CRCEN 0x20
|
||||
#define ERXFCON_PMEN 0x10
|
||||
#define ERXFCON_MPEN 0x08
|
||||
#define ERXFCON_HTEN 0x04
|
||||
#define ERXFCON_MCEN 0x02
|
||||
#define ERXFCON_BCEN 0x01
|
||||
// ENC28J60 EIE Register Bit Definitions
|
||||
#define EIE_INTIE 0x80
|
||||
#define EIE_PKTIE 0x40
|
||||
#define EIE_DMAIE 0x20
|
||||
#define EIE_LINKIE 0x10
|
||||
#define EIE_TXIE 0x08
|
||||
#define EIE_WOLIE 0x04
|
||||
#define EIE_TXERIE 0x02
|
||||
#define EIE_RXERIE 0x01
|
||||
// ENC28J60 EIR Register Bit Definitions
|
||||
#define EIR_PKTIF 0x40
|
||||
#define EIR_DMAIF 0x20
|
||||
#define EIR_LINKIF 0x10
|
||||
#define EIR_TXIF 0x08
|
||||
#define EIR_WOLIF 0x04
|
||||
#define EIR_TXERIF 0x02
|
||||
#define EIR_RXERIF 0x01
|
||||
// ENC28J60 ESTAT Register Bit Definitions
|
||||
#define ESTAT_INT 0x80
|
||||
#define ESTAT_LATECOL 0x10
|
||||
#define ESTAT_RXBUSY 0x04
|
||||
#define ESTAT_TXABRT 0x02
|
||||
#define ESTAT_CLKRDY 0x01
|
||||
// ENC28J60 ECON2 Register Bit Definitions
|
||||
#define ECON2_AUTOINC 0x80
|
||||
#define ECON2_PKTDEC 0x40
|
||||
#define ECON2_PWRSV 0x20
|
||||
#define ECON2_VRPS 0x08
|
||||
// ENC28J60 ECON1 Register Bit Definitions
|
||||
#define ECON1_TXRST 0x80
|
||||
#define ECON1_RXRST 0x40
|
||||
#define ECON1_DMAST 0x20
|
||||
#define ECON1_CSUMEN 0x10
|
||||
#define ECON1_TXRTS 0x08
|
||||
#define ECON1_RXEN 0x04
|
||||
#define ECON1_BSEL1 0x02
|
||||
#define ECON1_BSEL0 0x01
|
||||
// ENC28J60 MACON1 Register Bit Definitions
|
||||
#define MACON1_LOOPBK 0x10
|
||||
#define MACON1_TXPAUS 0x08
|
||||
#define MACON1_RXPAUS 0x04
|
||||
#define MACON1_PASSALL 0x02
|
||||
#define MACON1_MARXEN 0x01
|
||||
// ENC28J60 MACON2 Register Bit Definitions
|
||||
#define MACON2_MARST 0x80
|
||||
#define MACON2_RNDRST 0x40
|
||||
#define MACON2_MARXRST 0x08
|
||||
#define MACON2_RFUNRST 0x04
|
||||
#define MACON2_MATXRST 0x02
|
||||
#define MACON2_TFUNRST 0x01
|
||||
// ENC28J60 MACON3 Register Bit Definitions
|
||||
#define MACON3_PADCFG2 0x80
|
||||
#define MACON3_PADCFG1 0x40
|
||||
#define MACON3_PADCFG0 0x20
|
||||
#define MACON3_TXCRCEN 0x10
|
||||
#define MACON3_PHDRLEN 0x08
|
||||
#define MACON3_HFRMLEN 0x04
|
||||
#define MACON3_FRMLNEN 0x02
|
||||
#define MACON3_FULDPX 0x01
|
||||
// ENC28J60 MICMD Register Bit Definitions
|
||||
#define MICMD_MIISCAN 0x02
|
||||
#define MICMD_MIIRD 0x01
|
||||
// ENC28J60 MISTAT Register Bit Definitions
|
||||
#define MISTAT_NVALID 0x04
|
||||
#define MISTAT_SCAN 0x02
|
||||
#define MISTAT_BUSY 0x01
|
||||
// ENC28J60 PHY PHCON1 Register Bit Definitions
|
||||
#define PHCON1_PRST 0x8000
|
||||
#define PHCON1_PLOOPBK 0x4000
|
||||
#define PHCON1_PPWRSV 0x0800
|
||||
#define PHCON1_PDPXMD 0x0100
|
||||
// ENC28J60 PHY PHSTAT1 Register Bit Definitions
|
||||
#define PHSTAT1_PFDPX 0x1000
|
||||
#define PHSTAT1_PHDPX 0x0800
|
||||
#define PHSTAT1_LLSTAT 0x0004
|
||||
#define PHSTAT1_JBSTAT 0x0002
|
||||
// ENC28J60 PHY PHCON2 Register Bit Definitions
|
||||
#define PHCON2_FRCLINK 0x4000
|
||||
#define PHCON2_TXDIS 0x2000
|
||||
#define PHCON2_JABBER 0x0400
|
||||
#define PHCON2_HDLDIS 0x0100
|
||||
|
||||
// ENC28J60 Packet Control Byte Bit Definitions
|
||||
#define PKTCTRL_PHUGEEN 0x08
|
||||
#define PKTCTRL_PPADEN 0x04
|
||||
#define PKTCTRL_PCRCEN 0x02
|
||||
#define PKTCTRL_POVERRIDE 0x01
|
||||
|
||||
// SPI operation codes
|
||||
#define ENC28J60_READ_CTRL_REG 0x00
|
||||
#define ENC28J60_READ_BUF_MEM 0x3A
|
||||
#define ENC28J60_WRITE_CTRL_REG 0x40
|
||||
#define ENC28J60_WRITE_BUF_MEM 0x7A
|
||||
#define ENC28J60_BIT_FIELD_SET 0x80
|
||||
#define ENC28J60_BIT_FIELD_CLR 0xA0
|
||||
#define ENC28J60_SOFT_RESET 0xFF
|
||||
|
||||
// The RXSTART_INIT should be zero. See Rev. B4 Silicon Errata
|
||||
// buffer boundaries applied to internal 8K ram
|
||||
// the entire available packet buffer space is allocated
|
||||
//
|
||||
// start with recbuf at 0/
|
||||
#define RXSTART_INIT 0x0
|
||||
// receive buffer end
|
||||
#define RXSTOP_INIT 0x800
|
||||
// start TX buffer at 0x1FFF-0x0600, pace for one full ethernet frame (0~1518 bytes)
|
||||
#define TXSTART_INIT (0x1FFF-3*1518)
|
||||
// stp TX buffer at end of mem
|
||||
#define TXSTOP_INIT 0x1FFF
|
||||
// max frame length which the conroller will accept:
|
||||
#define MAX_FRAMELEN 1518 // (note: maximum ethernet frame length would be 1518)
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
static inline u8 SPI2_ReadWriteByte(u8 TxData)
|
||||
{
|
||||
//Fill output buffer with data
|
||||
SPI2->DR = TxData;
|
||||
|
||||
//Wait for transmission to complete
|
||||
while ((SPI2->SR & (SPI_I2S_FLAG_RXNE | SPI_I2S_FLAG_TXE)) == RESET) {}
|
||||
while (SPI2->SR & SPI_I2S_FLAG_BSY) {}
|
||||
|
||||
//Return data from buffer
|
||||
return SPI2->DR;
|
||||
}
|
||||
|
||||
/*CS Pin Definition*/
|
||||
#define ENC28J60_CS_PORT GPIOB
|
||||
#define ENC28J60_CS_CLK RCC_APB2Periph_GPIOB
|
||||
#define ENC28J60_CS_PIN GPIO_Pin_12
|
||||
#define ENC28J60_NO_SELECT() GPIOB->BSRR = GPIO_Pin_12
|
||||
#define ENC28J60_SELECT() GPIOB->BRR = GPIO_Pin_12
|
||||
|
||||
/*RST Pin Definition*/
|
||||
#define ENC28J60_RST_PORT GPIOC
|
||||
#define ENC28J60_RST_CLK RCC_APB2Periph_GPIOC
|
||||
#define ENC28J60_RST_PIN GPIO_Pin_5
|
||||
#define ENC28J60_RST_SET() GPIOC->BSRR = GPIO_Pin_5
|
||||
#define ENC28J60_RST_CLEAR() GPIOC->BRR = GPIO_Pin_5
|
||||
//SPI1初始化
|
||||
void ENC28J60_Reset(void);
|
||||
u8 ENC28J60_Read_Op(u8 op,u8 addr);
|
||||
void ENC28J60_Write_Op(u8 op,u8 addr,u8 data);
|
||||
void ENC28J60_Read_Buf(u32 len,u8* data);
|
||||
void ENC28J60_Write_Buf(u32 len,u8* data);
|
||||
void ENC28J60_Set_Bank(u8 bank);
|
||||
u8 ENC28J60_Read(u8 addr);
|
||||
void ENC28J60_Write(u8 addr,u8 data);
|
||||
void ENC28J60_PHY_Write(u8 addr,u32 data);
|
||||
u8 ENC28J60_Init(u8* macaddr);
|
||||
u8 ENC28J60_Get_EREVID(void);
|
||||
void ENC28J60_Packet_Send(u32 len,u8* packet);
|
||||
u32 ENC28J60_Packet_Receive(u32 maxlen,u8* packet);
|
||||
|
||||
extern int NextPacketPtr;
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
298
network/arpicmplab/start/lib/xnet/pcap_device.c
Normal file
298
network/arpicmplab/start/lib/xnet/pcap_device.c
Normal file
@@ -0,0 +1,298 @@
|
||||
#include <memory.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <net/if.h>
|
||||
#include "pcap_device.h"
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#define PCAP_MAC_ADDR_LEN 6
|
||||
|
||||
static int mac_is_zero(const uint8_t* mac_addr) {
|
||||
if (mac_addr == NULL) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < PCAP_MAC_ADDR_LEN; i++) {
|
||||
if (mac_addr[i]) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int read_interface_mac(const char* if_name, uint8_t* mac_addr) {
|
||||
if ((if_name == NULL) || (mac_addr == NULL)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int fd = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (fd < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct ifreq ifr;
|
||||
memset(&ifr, 0, sizeof(ifr));
|
||||
strncpy(ifr.ifr_name, if_name, IFNAMSIZ - 1);
|
||||
if (ioctl(fd, SIOCGIFHWADDR, &ifr) < 0) {
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
memcpy(mac_addr, (uint8_t*)ifr.ifr_hwaddr.sa_data, PCAP_MAC_ADDR_LEN);
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int load_pcap_lib() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 找到指定IP地址的网卡名
|
||||
* @param ip 物理网卡或者由虚拟软件生成的虚拟刚卡, 字符串形式,如"192.168.1.1"
|
||||
* @param name_buf 找到的对应网卡名称
|
||||
*/
|
||||
static int pcap_find_device(const char* ip, char* name_buf) {
|
||||
char err_buf[PCAP_ERRBUF_SIZE];
|
||||
pcap_if_t* pcap_if_list = NULL;
|
||||
struct in_addr dest_ip;
|
||||
pcap_if_t* item;
|
||||
|
||||
inet_pton(AF_INET, ip, &dest_ip);
|
||||
|
||||
int err = pcap_findalldevs(&pcap_if_list, err_buf);
|
||||
if (err < 0) {
|
||||
pcap_freealldevs(pcap_if_list);
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (item = pcap_if_list; item != NULL; item = item->next) {
|
||||
if (item->addresses == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (struct pcap_addr* pcap_addr = item->addresses; pcap_addr != NULL; pcap_addr = pcap_addr->next) {
|
||||
struct sockaddr_in* curr_addr;
|
||||
struct sockaddr* sock_addr = pcap_addr->addr;
|
||||
|
||||
if (sock_addr->sa_family != AF_INET) {
|
||||
continue;
|
||||
}
|
||||
|
||||
curr_addr = ((struct sockaddr_in*)sock_addr);
|
||||
if (curr_addr->sin_addr.s_addr == dest_ip.s_addr) {
|
||||
strcpy(name_buf, item->name);
|
||||
pcap_freealldevs(pcap_if_list);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pcap_freealldevs(pcap_if_list);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* 显示所有的网络接口列表
|
||||
*/
|
||||
static int pcap_show_list(void) {
|
||||
char err_buf[PCAP_ERRBUF_SIZE];
|
||||
pcap_if_t* pcapif_list = NULL;
|
||||
int count = 0;
|
||||
|
||||
// 查找所有的网络接口
|
||||
int err = pcap_findalldevs(&pcapif_list, err_buf);
|
||||
if (err < 0) {
|
||||
fprintf(stderr, "pcap_show_list: find all net list failed:%s\n", err_buf);
|
||||
pcap_freealldevs(pcapif_list);
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf("pcap_show_list: card list\n");
|
||||
|
||||
// 遍历所有的可用接口,输出其信息
|
||||
for (pcap_if_t* item = pcapif_list; item != NULL; item = item->next) {
|
||||
if (item->addresses == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (struct pcap_addr* pcap_addr = item->addresses; pcap_addr != NULL; pcap_addr = pcap_addr->next) {
|
||||
char str[INET_ADDRSTRLEN];
|
||||
struct sockaddr_in* ip_addr;
|
||||
|
||||
struct sockaddr* sockaddr = pcap_addr->addr;
|
||||
if (sockaddr->sa_family != AF_INET) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ip_addr = (struct sockaddr_in*)sockaddr;
|
||||
printf("card %d: IP:%s name: %s, \n\n",
|
||||
count++,
|
||||
item->description == NULL ? "" : item->description,
|
||||
inet_ntop(AF_INET, &ip_addr->sin_addr, str, sizeof(str))
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pcap_freealldevs(pcapif_list);
|
||||
|
||||
if ((pcapif_list == NULL) || (count == 0)) {
|
||||
fprintf(stderr, "pcap_show_list: no available card!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开pcap设备接口
|
||||
* @param ip 打开网卡的指定ip
|
||||
* @param 给网卡设置mac
|
||||
*/
|
||||
pcap_t* pcap_device_open(const char* ip, uint8_t * mac_addr, uint8_t poll_mode) {
|
||||
char err_buf[PCAP_ERRBUF_SIZE];
|
||||
struct bpf_program fp;
|
||||
bpf_u_int32 mask;
|
||||
bpf_u_int32 net;
|
||||
char filter_exp[256];
|
||||
char name_buf[256];
|
||||
pcap_t* pcap;
|
||||
|
||||
if (load_pcap_lib() < 0) {
|
||||
fprintf(stderr, "pcap_open: load pcap dll failed! install it first\n");
|
||||
return (pcap_t*)0;
|
||||
}
|
||||
|
||||
if (pcap_find_device(ip, name_buf) < 0) {
|
||||
fprintf(stderr, "pcap_open: no net card has ip: %s, use the following:\n", ip);
|
||||
pcap_show_list();
|
||||
return (pcap_t*)0;
|
||||
}
|
||||
|
||||
if (mac_addr == NULL) {
|
||||
fprintf(stderr, "pcap_open: mac address buffer is null\n");
|
||||
return (pcap_t*)0;
|
||||
}
|
||||
|
||||
if (mac_is_zero(mac_addr)) {
|
||||
if (read_interface_mac(name_buf, mac_addr) < 0) {
|
||||
fprintf(stderr, "pcap_open: failed to query mac address of %s, please configure it manually.\n", name_buf);
|
||||
return (pcap_t*)0;
|
||||
}
|
||||
printf("pcap_open: detected mac %02x:%02x:%02x:%02x:%02x:%02x on %s\n",
|
||||
mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5],
|
||||
name_buf);
|
||||
fflush(stdout);
|
||||
} else {
|
||||
if (read_interface_mac(name_buf, mac_addr) < 0) {
|
||||
printf("pcap_open: failed to query mac, using configured value %02x:%02x:%02x:%02x:%02x:%02x\n",
|
||||
mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
|
||||
fflush(stdout);
|
||||
} else {
|
||||
printf("pcap_open: overriding configured mac with detected value %02x:%02x:%02x:%02x:%02x:%02x\n",
|
||||
mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
|
||||
if (pcap_lookupnet(name_buf, &net, &mask, err_buf) == -1) {
|
||||
printf("pcap_open: can't find use net card: %s\n", name_buf);
|
||||
net = 0;
|
||||
mask = 0;
|
||||
}
|
||||
|
||||
pcap = pcap_open_live(name_buf, // 设置字符串
|
||||
65536, // 要捕获的最大字节数
|
||||
1, // 混杂模式
|
||||
1, // 读取超时(以毫秒为单位)
|
||||
err_buf);
|
||||
if (pcap == NULL) {
|
||||
fprintf(stderr, "pcap_open: create pcap failed %s\n net card name: %s\n", err_buf, name_buf);
|
||||
fprintf(stderr, "Use the following:\n");
|
||||
pcap_show_list();
|
||||
return (pcap_t*)0;
|
||||
}
|
||||
|
||||
// 非阻塞模式读取,程序中使用查询的方式读
|
||||
if (pcap_setnonblock(pcap, 1, err_buf) != 0) {
|
||||
fprintf(stderr, "pcap_open: set none block failed: %s\n", pcap_geterr(pcap));
|
||||
return (pcap_t*)0;
|
||||
}
|
||||
|
||||
// 只捕获输入,不要捕获自己发出去的
|
||||
// 注:win平台似乎不支持这个选项
|
||||
if (pcap_setdirection(pcap, PCAP_D_IN) != 0) {
|
||||
// fprintf(stderr, "pcap_open: set direction not suppor: %s\n", pcap_geterr(pcap));
|
||||
|
||||
}
|
||||
|
||||
// 只捕获发往本接口与广播的数据帧。相当于只处理发往这张网卡的包
|
||||
sprintf(filter_exp,
|
||||
"(ether dst %02x:%02x:%02x:%02x:%02x:%02x or ether broadcast) and (not ether src %02x:%02x:%02x:%02x:%02x:%02x)",
|
||||
mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5],
|
||||
mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
|
||||
printf("pcap_open: BPF filter: %s\n", filter_exp);
|
||||
fflush(stdout);
|
||||
if (pcap_compile(pcap, &fp, filter_exp, 0, net) == -1) {
|
||||
printf("pcap_open: couldn't parse filter %s: %s\n", filter_exp, pcap_geterr(pcap));
|
||||
return (pcap_t*)0;
|
||||
}
|
||||
if (pcap_setfilter(pcap, &fp) == -1) {
|
||||
printf("pcap_open: couldn't install filter %s: %s\n", filter_exp, pcap_geterr(pcap));
|
||||
return (pcap_t*)0;
|
||||
}
|
||||
printf("pcap_open: Filter installed successfully\n");
|
||||
fflush(stdout);
|
||||
|
||||
return pcap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭Pcapif接口
|
||||
*/
|
||||
void pcap_device_close(pcap_t* pcap) {
|
||||
if (pcap == (pcap_t *)0) {
|
||||
fprintf(stderr, "pcap = 0");
|
||||
pcap_show_list();
|
||||
return;
|
||||
}
|
||||
pcap_close(pcap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 向网络接口发送数据包
|
||||
*/
|
||||
uint32_t pcap_device_send(pcap_t* pcap, const uint8_t* buffer, uint32_t length) {
|
||||
if (pcap_sendpacket(pcap, buffer, length) == -1) {
|
||||
fprintf(stderr, "pcap send: send packet failed!:%s\n", pcap_geterr(pcap));
|
||||
fprintf(stderr, "pcap send: pcaket size %d\n", length);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从网络接口读取数据包
|
||||
*/
|
||||
uint32_t pcap_device_read(pcap_t* pcap, uint8_t* buffer, uint32_t length) {
|
||||
int err;
|
||||
struct pcap_pkthdr* pkthdr;
|
||||
const uint8_t* pkt_data;
|
||||
|
||||
err = pcap_next_ex(pcap, &pkthdr, &pkt_data);
|
||||
if (err == 0) {
|
||||
return 0;
|
||||
} else if (err == 1) { // 1 - 成功读取数据包, 0 - 没有数据包,其它值-出错
|
||||
memcpy(buffer, pkt_data, pkthdr->len);
|
||||
return pkthdr->len;
|
||||
}
|
||||
|
||||
fprintf(stderr, "pcap_read: reading packet failed!:%s", pcap_geterr(pcap));
|
||||
return 0;
|
||||
}
|
||||
18
network/arpicmplab/start/lib/xnet/pcap_device.h
Normal file
18
network/arpicmplab/start/lib/xnet/pcap_device.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#ifndef PCAP_DRIVER_H
|
||||
#define PCAP_DRIVER_H
|
||||
|
||||
#include <pcap.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// 主-次版本号
|
||||
#define NPCAP_VERSION_M 0
|
||||
#define NPCAP_VERSION_N 9986
|
||||
|
||||
typedef void (*irq_handler_t)(void* arg, uint8_t is_rx, const uint8_t* data, uint32_t size);
|
||||
|
||||
pcap_t* pcap_device_open(const char* ip, uint8_t *mac_addr, uint8_t poll_mode);
|
||||
void pcap_device_close(pcap_t* pcap);
|
||||
uint32_t pcap_device_send(pcap_t* pcap, const uint8_t* buffer, uint32_t length);
|
||||
uint32_t pcap_device_read(pcap_t* pcap, uint8_t* buffer, uint32_t length);
|
||||
|
||||
#endif //PCAP_DRIVER_H
|
||||
Reference in New Issue
Block a user