新建几个头文件
Include/lwipopts.h
Include/arch/cc.h
Include/arch/perf.h
Include/arch/sys_arch.h
除头文件外还需要添加一个C文件:sys_arch.c。
说明在doc/sys_arch.txt中。
修改netif/Ethernetif.c。
结构对齐的几个宏
对于一个结构下载下来的LWIP的通用定义如下: PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT 对于vc的编译器就郁闷了,vc做结构体对齐是这样做的 #ifdef WIN32
这样一改在VC下和GCC都可以了,不过每个结构上都要修改一下,这个是黑郁闷黑郁闷啊
|
“轻量级”保护
"lightweight" synchronization mechanisms -
SYS_ARCH_DECL_PROTECT(x) - declare a protection state variable.
SYS_ARCH_PROTECT(x) - enterprotection mode.
SYS_ARCH_UNPROTECT(x) - leaveprotection mode.
这三个宏定义一个快速的“保护”和“解除保护”操作。例如进入保护可以是屏蔽中断或使用一个信号量或mutex。注意:进入保护后还允许再次进入保护,旧的保护标志通过lev返回,退出保护时再恢复。
如果没有定义这三个宏,Sys.h中有一段代码进行了判断。
#ifndef SYS_ARCH_PROTECT
如果没有定义SYS_ARCH_PROTECT,那么可以在lwipopts.h中定义宏SYS_LIGHTWEIGHT_PROT,并在sys_arch.c中定义函数sys_arch_protect()和sys_arch_unprotect(lev) #if SYS_LIGHTWEIGHT_PROT
#define SYS_ARCH_DECL_PROTECT(lev) sys_prot_t lev /** SYS_ARCH_PROTECT * Perform a "fast" protect. This could be implemented by * disabling interrupts for an embedded system or by using a semaphore or * mutex. The implementation should allow calling SYS_ARCH_PROTECT when * already protected. The old protection level is returned in the variable * "lev". This macro will default to calling the sys_arch_protect() function * which should be implemented in sys_arch.c. If a particular port needs a * different implementation, then this macro may be defined in sys_arch.h */ #define SYS_ARCH_PROTECT(lev) lev = sys_arch_protect() /** SYS_ARCH_UNPROTECT * Perform a "fast" set of the protection level to "lev". This could be * implemented by setting the interrupt level to "lev" within the MACRO or by * using a semaphore or mutex. This macro will default to calling the * sys_arch_unprotect() function which should be implemented in * sys_arch.c. If a particular port needs a different implementation, then * this macro may be defined in sys_arch.h */ #define SYS_ARCH_UNPROTECT(lev) sys_arch_unprotect(lev) sys_prot_t sys_arch_protect(void); void sys_arch_unprotect(sys_prot_t pval);
#else
#define SYS_ARCH_DECL_PROTECT(lev) #define SYS_ARCH_PROTECT(lev) #define SYS_ARCH_UNPROTECT(lev)
#endif /* SYS_LIGHTWEIGHT_PROT */
#endif /* SYS_ARCH_PROTECT */ |
LWIP_COMPAT_MUTEX
定义此宏表示用信号量来代替mutex。
Init.c
不定义NO_SYS和“#define NO_SYS 0”的效果是一样的。
下面这些宏对代码有影响:
LWIP_SOCKET
LWIP_ARP
LWIP_RAW
LWIP_UDP
LWIP_TCP
LWIP_SNMP
LWIP_AUTOIP
LWIP_IGMP
LWIP_DNS
LWIP_TIMERS
void lwip_init(void) { /* Sanity check user-configurable values */ lwip_sanity_check();
/* Modules initialization */ stats_init(); #if !NO_SYS sys_init(); #endif /* !NO_SYS */ mem_init(); memp_init(); pbuf_init(); netif_init(); #if LWIP_SOCKET lwip_socket_init(); #endif /* LWIP_SOCKET */ ip_init(); #if LWIP_ARP etharp_init(); #endif /* LWIP_ARP */ #if LWIP_RAW raw_init(); #endif /* LWIP_RAW */ #if LWIP_UDP udp_init(); #endif /* LWIP_UDP */ #if LWIP_TCP tcp_init(); #endif /* LWIP_TCP */ #if LWIP_SNMP snmp_init(); #endif /* LWIP_SNMP */ #if LWIP_AUTOIP autoip_init(); #endif /* LWIP_AUTOIP */ #if LWIP_IGMP igmp_init(); #endif /* LWIP_IGMP */ #if LWIP_DNS dns_init(); #endif /* LWIP_DNS */
#if LWIP_TIMERS sys_timeouts_init(); #endif /* LWIP_TIMERS */ } |
netif_add函数
它添加一个网络接口到lwip,一个网卡应该是一个网络接口。本地回环也是一个网络接口,它已经在tcpip_init中的lwip_init调用netif_init被添加。
/** * Add a network interface to the list of lwIP netifs. * * @param netif a pre-allocated netif structure * @param ipaddr IP address for the new netif * @param netmask network mask for the new netif * @param gw default gateway IP address for the new netif * @param state opaque data passed to the new netif * @param init callback function that initializes the interface * @param input callback function that is called to pass * ingress packets up in the protocol layer stack. * * @return netif, or NULL if failed. */ struct netif * netif_add(struct netif *netif, ip_addr_t *ipaddr, ip_addr_t *netmask, ip_addr_t *gw, void *state, netif_init_fn init, netif_input_fn input) |
Struct net_if
在初始化用netif_add添加到lwip中。
/** Generic data structure used for all lwIP network interfaces. * The following fields should be filled in by the initialization * function for the device driver: hwaddr_len, hwaddr[], mtu, flags */ struct netif { /** pointer to next in linked list */ struct netif *next;
/** IP address configuration in network byte order */ ip_addr_t ip_addr; ip_addr_t netmask; ip_addr_t gw;
/** This function is called by the network device driver * to pass a packet up the TCP/IP stack. */ netif_input_fn input; // 网卡数据接收函数,自定义设置 /** This function is called by the IP module when it wants * to send a packet on the interface. This function typically * first resolves the hardware address, then sends the packet. */ netif_output_fn output; //发送无连接的数据包,如广播包,多播包,最终还是调用下面的linkoutput函数。固定设置为etharp_output。 /** This function is called by the ARP module when it wants * to send a packet on the interface. This function outputs * the pbuf as-is on the link medium. */ netif_linkoutput_fn linkoutput; //发送有连接的数据包,已经包含MAC,类型等数据。 #if LWIP_NETIF_STATUS_CALLBACK /** This function is called when the netif state is set to up or down */ netif_status_callback_fn status_callback; #endif /* LWIP_NETIF_STATUS_CALLBACK */ #if LWIP_NETIF_LINK_CALLBACK /** This function is called when the netif link is set to up or down */ netif_status_callback_fn link_callback; #endif /* LWIP_NETIF_LINK_CALLBACK */ /** This field can be set by the device driver and could point * to state information for the device. */ void *state; //一般设置为网卡的私有数据,如netif->state = ethernetif; #if LWIP_DHCP /** the DHCP client state information for this netif */ struct dhcp *dhcp; #endif /* LWIP_DHCP */ #if LWIP_AUTOIP /** the AutoIP client state information for this netif */ struct autoip *autoip; #endif #if LWIP_NETIF_HOSTNAME /* the hostname for this netif, NULL is a valid value */ char* hostname; #endif /* LWIP_NETIF_HOSTNAME */ /** maximum transfer unit (in bytes) */ u16_t mtu; /** number of bytes used in hwaddr */ u8_t hwaddr_len; /** link level hardware address of this interface */ u8_t hwaddr[NETIF_MAX_HWADDR_LEN]; /** flags (see NETIF_FLAG_ above) */ u8_t flags; /** descriptive abbreviation */ char name[2]; /** number of this interface */ u8_t num; #if LWIP_SNMP /** link type (from "snmp_ifType" enum from snmp.h) */ u8_t link_type; /** (estimate) link speed */ u32_t link_speed; /** timestamp at last change made (up/down) */ u32_t ts; /** counters */ u32_t ifinoctets; u32_t ifinucastpkts; u32_t ifinnucastpkts; u32_t ifindiscards; u32_t ifoutoctets; u32_t ifoutucastpkts; u32_t ifoutnucastpkts; u32_t ifoutdiscards; #endif /* LWIP_SNMP */ #if LWIP_IGMP /** This function could be called to add or delete a entry in the multicast filter table of the ethernet MAC.*/ netif_igmp_mac_filter_fn igmp_mac_filter; #endif /* LWIP_IGMP */ #if LWIP_NETIF_HWADDRHINT u8_t *addr_hint; #endif /* LWIP_NETIF_HWADDRHINT */ #if ENABLE_LOOPBACK /* List of packets to be queued for ourselves. */ struct pbuf *loop_first; struct pbuf *loop_last; #if LWIP_LOOPBACK_MAX_PBUFS u16_t loop_cnt_current; #endif /* LWIP_LOOPBACK_MAX_PBUFS */ #endif /* ENABLE_LOOPBACK */ }; |
struct pbuf
一个网络包可能由多个pbuf组成,字段tot_len指定这个网络包的大小,字段len是当前pbuf包含数据的大小。
判断一个网络包的最后一个包不能以next字段等于空来判断,它可能不为空,下一个pbuf为下一个网络包的内容。应该计算tot_len来判断一个包的结束。
struct pbuf { /** next pbuf in singly linked pbuf chain */ struct pbuf *next; //下一个pbuf
/** pointer to the actual data in the buffer */ void *payload; // 当前结构数据内容
/** * total length of this buffer and all next buffers in chain * belonging to the same packet. * * For non-queue packet chains this is the invariant: * p->tot_len == p->len + (p->next? p->next->tot_len: 0) */ u16_t tot_len;
/** length of this buffer */ u16_t len;
/** pbuf_type as u8_t instead of enum to save space */ u8_t /*pbuf_type*/ type;
/** misc flags */ u8_t flags;
/** * the reference count always equals the number of pointers * that refer to this pbuf. This can be pointers from an application, * the stack itself, or pbuf->next pointers from a chain. */ u16_t ref; }; |
Ethernetif.c
它是你要移植的网卡驱动程序与lwip连接的文件。源代码提供了一个模板。
struct ethernetif结构
它是网卡私有数据结构
struct ethernetif { struct eth_addr *ethaddr; /* Add whatever per-interface state that is needed here. */在这里添加自定义的数据 }; |
ethernetif_input
网卡有数据时应该调用这个函数。过程大致如下:
网卡读任务 |
中断处理 |
(1)信号量pend(等待数据到来中断) (2)pend成功,调用ethernetif_input (3)回到步骤1. |
如果有中断表明数据包到达,给“网卡读任务”发送信号量。 |
ethernetif_input做的工作有:
(1) 调用low_level_input读取数据包
(2) 判断数据包的以太网类型并调用netif->input。netif->input是在协议栈初始化添加的。如netif_add(mLocalNetif, &ipaddr, &netmask, &gw, NULL,ethernetif_init, tcpip_input),那么它是tcpip_input,tcpip_input做的工作发送消息给tcpip主线线程,它把数据包交给上层处理。
/** * This function should be called when a packet is ready to be read * from the interface. It uses the function low_level_input() that * should handle the actual reception of bytes from the network * interface. Then the type of the received packet is determined and * the appropriate input function is called. * * @param netif the lwip network interface structure for this ethernetif */ static void ethernetif_input(struct netif *netif) { struct ethernetif *ethernetif; struct eth_hdr *ethhdr; struct pbuf *p;
ethernetif = netif->state;
/* move received packet into a new pbuf */ p = low_level_input(netif); /* no packet could be read, silently ignore this */ if (p == NULL) return; /* points to packet payload, which starts with an Ethernet header */ ethhdr = p->payload;
switch (htons(ethhdr->type)) { /* IP or ARP packet? */ case ETHTYPE_IP: case ETHTYPE_ARP: #if PPPOE_SUPPORT /* PPPoE packet? */ case ETHTYPE_PPPOEDISC: case ETHTYPE_PPPOE: #endif /* PPPOE_SUPPORT */ /* full packet send to tcpip_thread to process */ if (netif->input(p, netif)!=ERR_OK) { LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_input: IP input error\n")); pbuf_free(p); p = NULL; } break;
default: pbuf_free(p); p = NULL; break; } } |
ethernetif_init
在初始化协议栈时,netif_add(mLocalNetif,&ipaddr, &netmask, &gw, NULL, ethernetif_init, tcpip_input)。
ethernetif_init做的工作:
(1) 初始化struct ethernetif
(2) 初始化struct net_if
(3) 调用硬件初始化low_level_init(netif);
/** * Should be called at the beginning of the program to set up the * network interface. It calls the function low_level_init() to do the * actual setup of the hardware. * * This function should be passed as a parameter to netif_add(). * * @param netif the lwip network interface structure for this ethernetif * @return ERR_OK if the loopif is initialized * ERR_MEM if private data couldn't be allocated * any other err_t on error */ err_t ethernetif_init(struct netif *netif) { struct ethernetif *ethernetif;
LWIP_ASSERT("netif != NULL", (netif != NULL));
ethernetif = mem_malloc(sizeof(struct ethernetif)); if (ethernetif == NULL) { LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_init: out of memory\n")); return ERR_MEM; }
#if LWIP_NETIF_HOSTNAME /* Initialize interface hostname */ netif->hostname = "lwip"; #endif /* LWIP_NETIF_HOSTNAME */
/* * Initialize the snmp variables and counters inside the struct netif. * The last argument should be replaced with your link speed, in units * of bits per second. */ NETIF_INIT_SNMP(netif, snmp_ifType_ethernet_csmacd, LINK_SPEED_OF_YOUR_NETIF_IN_BPS);
netif->state = ethernetif; netif->name[0] = IFNAME0; netif->name[1] = IFNAME1; /* We directly use etharp_output() here to save a function call. * You can instead declare your own function an call etharp_output() * from it if you have to do some checks before sending (e.g. if link * is available...) */ netif->output = etharp_output; netif->linkoutput = low_level_output;
ethernetif->ethaddr = (struct eth_addr *)&(netif->hwaddr[0]);
/* initialize the hardware */ low_level_init(netif);
return ERR_OK; } |
low_level_init
它是网卡硬件初始化的代码。它被ethernetif_init调用。
/** * In this function, the hardware should be initialized. * Called from ethernetif_init(). * * @param netif the already initialized lwip network interface structure * for this ethernetif */ static void low_level_init(struct netif *netif) { struct ethernetif *ethernetif = netif->state;
/* set MAC hardware address length */ netif->hwaddr_len = ETHARP_HWADDR_LEN;
/* set MAC hardware address */ netif->hwaddr[0] = 0x00; netif->hwaddr[1] = 0x50; netif->hwaddr[2] = 0x50; netif->hwaddr[3] = 0x50; netif->hwaddr[4] = 0x50; netif->hwaddr[5] = 0x01;
/* maximum transfer unit */ netif->mtu = 1500;
/* device capabilities */ /* don't set NETIF_FLAG_ETHARP if this device is not an ethernet one */ netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP;
/* Do whatever else is needed to initialize interface. */ } |
low_level_output
它是实现网卡发送数据的函数。
它发送链表structpbuf中的所有数据。
在代码:
netif->linkoutput = low_level_output;
中,low_level_output被赋于netif->linkoutput。
/** * This function should do the actual transmission of the packet. The packet is * contained in the pbuf that is passed to the function. This pbuf * might be chained. * * @param netif the lwip network interface structure for this ethernetif * @param p the MAC packet to send (e.g. IP packet including MAC addresses and type) * @return ERR_OK if the packet could be sent * an err_t value if the packet couldn't be sent * * @note Returning ERR_MEM here if a DMA queue of your MAC is full can lead to * strange results. You might consider waiting for space in the DMA queue * to become availale since the stack doesn't retry to send a packet * dropped because of memory failure (except for the TCP timers). */
static err_t low_level_output(struct netif *netif, struct pbuf *p) { struct ethernetif *ethernetif = netif->state; struct pbuf *q;
initiate transfer();
#if ETH_PAD_SIZE pbuf_header(p, -ETH_PAD_SIZE); /* drop the padding word */ #endif
for(q = p; q != NULL; q = q->next) { /* Send the data from the pbuf to the interface, one pbuf at a time. The size of the data in each pbuf is kept in the ->len variable. */ send data from(q->payload, q->len); }
signal that packet should be sent();
#if ETH_PAD_SIZE pbuf_header(p, ETH_PAD_SIZE); /* reclaim the padding word */ #endif
LINK_STATS_INC(link.xmit);
return ERR_OK; } |
low_level_input
它是实现网卡接收数据的函数。
它由ethernetif_input调用,具体参考ethernetif_input。
/** * Should allocate a pbuf and transfer the bytes of the incoming * packet from the interface into the pbuf. * * @param netif the lwip network interface structure for this ethernetif * @return a pbuf filled with the received packet (including MAC header) * NULL on memory error */ static struct pbuf * low_level_input(struct netif *netif) { struct ethernetif *ethernetif = netif->state; struct pbuf *p, *q; u16_t len;
/* Obtain the size of the packet and put it into the "len" variable. */ len = ;
#if ETH_PAD_SIZE len += ETH_PAD_SIZE; /* allow room for Ethernet padding */ #endif
/* We allocate a pbuf chain of pbufs from the pool. */ p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);
if (p != NULL) {
#if ETH_PAD_SIZE pbuf_header(p, -ETH_PAD_SIZE); /* drop the padding word */ #endif
/* We iterate over the pbuf chain until we have read the entire * packet into the pbuf. */ for(q = p; q != NULL; q = q->next) { /* Read enough bytes to fill this pbuf in the chain. The * available data in the pbuf is given by the q->len * variable. * This does not necessarily have to be a memcpy, you can also preallocate * pbufs for a DMA-enabled MAC and after receiving truncate it to the * actually received size. In this case, ensure the tot_len member of the * pbuf is the sum of the chained pbuf len members. */ read data into(q->payload, q->len); } acknowledge that packet has been read();
#if ETH_PAD_SIZE pbuf_header(p, ETH_PAD_SIZE); /* reclaim the padding word */ #endif
LINK_STATS_INC(link.recv); } else { drop packet(); LINK_STATS_INC(link.memerr); LINK_STATS_INC(link.drop); }
return p; } |
有关MEM
Opt.h中的配置宏
/* ------------------------------------ ---------- Memory options ---------- ------------------------------------ */ /** * MEM_LIBC_MALLOC==1: Use malloc/free/realloc provided by your C-library * instead of the lwip internal allocator. Can save code size if you * already use it. */ MEM_LIBC_MALLOC==1: 使用C库的malloc/free/realloc #ifndef MEM_LIBC_MALLOC #define MEM_LIBC_MALLOC 0 #endif
/** * MEMP_MEM_MALLOC==1: Use mem_malloc/mem_free instead of the lwip pool allocator. * Especially useful with MEM_LIBC_MALLOC but handle with care regarding execution * speed and usage from interrupts! */ #ifndef MEMP_MEM_MALLOC #define MEMP_MEM_MALLOC 0 #endif
/** * MEM_ALIGNMENT: should be set to the alignment of the CPU * 4 byte alignment -> #define MEM_ALIGNMENT 4 * 2 byte alignment -> #define MEM_ALIGNMENT 2 */ 设置内存对齐 #ifndef MEM_ALIGNMENT #define MEM_ALIGNMENT 1 #endif
/** * MEM_SIZE: the size of the heap memory. If the application will send * a lot of data that needs to be copied, this should be set high. */ #ifndef MEM_SIZE #define MEM_SIZE 1600 #endif
/** * MEMP_SEPARATE_POOLS: if defined to 1, each pool is placed in its own array. * This can be used to individually change the location of each pool. * Default is one big array for all pools */ #ifndef MEMP_SEPARATE_POOLS #define MEMP_SEPARATE_POOLS 0 #endif
/** * MEMP_OVERFLOW_CHECK: memp overflow protection reserves a configurable * amount of bytes before and after each memp element in every pool and fills * it with a prominent default value. * MEMP_OVERFLOW_CHECK == 0 no checking * MEMP_OVERFLOW_CHECK == 1 checks each element when it is freed * MEMP_OVERFLOW_CHECK >= 2 checks each element in every pool every time * memp_malloc() or memp_free() is called (useful but slow!) */ #ifndef MEMP_OVERFLOW_CHECK #define MEMP_OVERFLOW_CHECK 0 #endif
/** * MEMP_SANITY_CHECK==1: run a sanity check after each memp_free() to make * sure that there are no cycles in the linked lists. */ #ifndef MEMP_SANITY_CHECK #define MEMP_SANITY_CHECK 0 #endif
/** * MEM_USE_POOLS==1: Use an alternative to malloc() by allocating from a set * of memory pools of various sizes. When mem_malloc is called, an element of * the smallest pool that can provide the length needed is returned. * To use this, MEMP_USE_CUSTOM_POOLS also has to be enabled. */ #ifndef MEM_USE_POOLS #define MEM_USE_POOLS 0 #endif
/** * MEM_USE_POOLS_TRY_BIGGER_POOL==1: if one malloc-pool is empty, try the next * bigger pool - WARNING: THIS MIGHT WASTE MEMORY but it can make a system more * reliable. */ #ifndef MEM_USE_POOLS_TRY_BIGGER_POOL #define MEM_USE_POOLS_TRY_BIGGER_POOL 0 #endif
/** * MEMP_USE_CUSTOM_POOLS==1: whether to include a user file lwippools.h * that defines additional pools beyond the "standard" ones required * by lwIP. If you set this to 1, you must have lwippools.h in your * inlude path somewhere. */ #ifndef MEMP_USE_CUSTOM_POOLS #define MEMP_USE_CUSTOM_POOLS 0 #endif
/** * Set this to 1 if you want to free PBUF_RAM pbufs (or call mem_free()) from * interrupt context (or another context that doesn't allow waiting for a * semaphore). * If set to 1, mem_malloc will be protected by a semaphore and SYS_ARCH_PROTECT, * while mem_free will only use SYS_ARCH_PROTECT. mem_malloc SYS_ARCH_UNPROTECTs * with each loop so that mem_free can run. * * ATTENTION: As you can see from the above description, this leads to dis-/ * enabling interrupts often, which can be slow! Also, on low memory, mem_malloc * can need longer. * * If you don't want that, at least for NO_SYS=0, you can still use the following * functions to enqueue a deallocation call which then runs in the tcpip_thread * context: * - pbuf_free_callback(p); * - mem_free_callback(m); */ #ifndef LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT #define LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT 0 #endif |
Mem.h中的几个宏、
/** Calculate memory size for an aligned buffer - returns the next highest * multiple of MEM_ALIGNMENT (e.g. LWIP_MEM_ALIGN_SIZE(3) and * LWIP_MEM_ALIGN_SIZE(4) will both yield 4 for MEM_ALIGNMENT == 4). */ 计算内存对齐状态下size的实际大小。例如4字节对齐时,MEM_ALIGNMENT = 4,这时LWIP_MEM_ALIGN_SIZE(3或4)的结果都是4 #ifndef LWIP_MEM_ALIGN_SIZE #define LWIP_MEM_ALIGN_SIZE(size) (((size) + MEM_ALIGNMENT - 1) & ~(MEM_ALIGNMENT-1)) #endif
/** Calculate safe memory size for an aligned buffer when using an unaligned * type as storage. This includes a safety-margin on (MEM_ALIGNMENT - 1) at the * start (e.g. if buffer is u8_t[] and actual data will be u32_t*) */ 当申请一个内存大小时,假如当前是4字节对齐,要申请3个字节的空间,但使用过程中地址要对齐,为使用对齐的地址仍然在安全地址空间,申请的内存大小要在一个安全范围内。 #ifndef LWIP_MEM_ALIGN_BUFFER #define LWIP_MEM_ALIGN_BUFFER(size) (((size) + MEM_ALIGNMENT - 1)) #endif
/** Align a memory pointer to the alignment defined by MEM_ALIGNMENT * so that ADDR % MEM_ALIGNMENT == 0 */ 当一个内存不对齐时,返回一个对齐的内存地址(不对齐时,去掉不对齐的空间,取得的对齐地址大于参数ADDR) #ifndef LWIP_MEM_ALIGN #define LWIP_MEM_ALIGN(addr) ((void *)(((mem_ptr_t)(addr) + MEM_ALIGNMENT - 1) & ~(mem_ptr_t)(MEM_ALIGNMENT-1))) #endif |