So I figured this out. To communicate with PHY device, one needs to use
HAL_StatusTypeDef HAL_ETH_ReadPHYRegister(ETH_HandleTypeDef *heth, uint32_t PHYAddr, uint32_t PHYReg, uint32_t *pRegValue)
and
HAL_StatusTypeDef HAL_ETH_WritePHYRegister(const ETH_HandleTypeDef *heth, uint32_t PHYAddr, uint32_t PHYReg, uint32_t RegValue)
functions. The PHY address in ICS1894-33 I am using depends on the state of P0, P1, P2 and P3 pins during reset or power-up. I have high state on P0 and low on P1, P2 and P3 and therefore my PHY address is 0b001. This means that to read my PHY device's registers I need to use code:
uint32_t phyRegs[32]={0x0000};
for (uint8_t i=0; i<32; i++) {
if ( HAL_ETH_ReadPHYRegister(&heth, 0b0001, i, phyRegs+i) != HAL_OK) {
asm("NOP");
}
}
Since the ICS1894-33's pins are connected on my PCB in such a way that autonegotiation enabled, my initialisation process in low_level_init() in ethernetif.c file looks like this:
/* USER CODE BEGIN low_level_init Code 1 for User BSP */
HAL_ETH_MspInit(&heth);
HAL_ETH_Start(&heth);
netif_set_up(netif);
netif_set_link_up(netif);
/* USER CODE END low_level_init Code 1 for User BSP */
There is a bug in STM32CubeIDE that prevents HAL_ETH_MspInit() from being generated when BSP module in LwIP tab in STM32CubeMX is not selected. I had to select it, generate code, copy HAL_ETH_MspInit() function, then uncheck the BSP module in LwIP tab and paste the copied code.