#include <bits/stdc++.h>
using namespace std;
int main() {
  cin.tie(0);
  ios::sync_with_stdio(false);
  int H, W;
  cin >> H >> W;
  vector<string> map(H);
  for (int i = 0; i < H; i++) {
    string M;
    cin >> M;
    map.at(i) = M;
  }
  queue<tuple<int, int, int>> moves;
  int answer = 0;
  for (int i = 0; i < H; i++) {
    for (int j = 0; j < W; j++) {
      if (map.at(i).at(j) == '#') {
        continue;
      }
      vector<vector<bool>> alreadyMoved(H, vector<bool>(W, false));
      moves.push(make_tuple(i, j, 0));
      while (moves.size()) {
        tuple<int, int, int> move = moves.front();
        moves.pop();
        int curH, curW, count;
        tie(curH, curW, count) = move;
        answer = max(answer, count);
        alreadyMoved.at(curH).at(curW) = true;
        int nextH, nextW;
        nextH = curH - 1;
        nextW = curW;
        if (0 <= nextH && map.at(nextH).at(nextW) == '.' &&
            !alreadyMoved.at(nextH).at(nextW)) {
          moves.push(make_tuple(nextH, nextW, count + 1));
        }
        nextH = curH + 1;
        nextW = curW;
        if (nextH < H && map.at(nextH).at(nextW) == '.' &&
            !alreadyMoved.at(nextH).at(nextW)) {
          moves.push(make_tuple(nextH, nextW, count + 1));
        }
        nextH = curH;
        nextW = curW - 1;
        if (0 <= nextW && map.at(nextH).at(nextW) == '.' &&
            !alreadyMoved.at(nextH).at(nextW)) {
          moves.push(make_tuple(nextH, nextW, count + 1));
        }
        nextH = curH;
        nextW = curW + 1;
        if (nextW < W && map.at(nextH).at(nextW) == '.' &&
            !alreadyMoved.at(nextH).at(nextW)) {
          moves.push(make_tuple(nextH, nextW, count + 1));
        }
      }
    }
  }
  cout << answer << endl;
  return 0;
}
