Referee Lee Probert pointed to the spot after England goalkeeper David James clattered into Blues midfielder Sebastian Larsson in the 92nd minute.
Up stepped McFadden to drill the ball past the 39-year-old, who had earlier denied the home side with fine saves from Larsson and veteran striker Kevin Phillips.
James’ efforts looked to have earned his side a valuable point
It was 35 minutes before Portsmouth managed a worthwhile effort on target and then on-loan Joe Hart was well positioned to hold onto a long-range attempt from Niko Kranjcar.
Portsmouth were forced into a substitution after 42 minutes when Papa Bouba Diop limped out of the action to be replaced by Angelos Basinas.
Hart parried a low effort around the post from Frederic Piquionne although the shot was probably heading just wide before his intervention.
McFadden made a good break down the left before releasing Jerome but he lacked the necessary composure and curled his shot well wide.
Vignal looked to have picked up a knock and he made way for Blues defender Stuart Parnaby after 49 minutes. Then Jerome was bundled off the ball by Bassinas, who was yellow carded, and after pitch-side treatment he was replaced by Phillips.
City tried to build up some momentum and James turned over a curling free-kick from Larsson after Phillips had been brought down by Mullins.
At the other end, City stopper Hart had little trouble in holding onto an effort from a similar range by Kranjcar.
James made a fine save to turn a curling shot from Phillips past the post after O’Connor had headed the ball into his path. But St Andrews erupted after McFadden’s late decider.
BIRMINGHAM: Hart, Carr, Roger Johnson, Queudrue, Vignal (Parnaby 49), Larsson, Ferguson, Fahey, McFadden, O’Connor (Benitez 81), Jerome (Phillips 54). PORTSMOUTH: James, Kaboul, Wilson, Distin, Belhadj, Vanden Borre (Utaka 46), Diop (Basinas 42), Mokoena, Mullins, Kranjcar, Piquionne (Kanu 79). Referee: Lee Probert (Wiltshire).